Metadata-Version: 2.4
Name: ctrecon
Version: 0.0.2
Summary: an interface and some tools for computed tomography reconstruction
Author-email: "J. Hoffman" <contact@jmh.lol>
Project-URL: Homepage, https://gitlab.com/hoffman-lab/ctrecon
Project-URL: Bug Tracker, https://gitlab.com/hoffman-lab/ctrecon/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: numpy
Requires-Dist: matplotlib

# CTRECON: Some basic simulation tools for CT scanning

More than "fast" or "efficient", I'm trying to bolt an interface on that feels
intuitive and reusable. Something I want to reach for when developing a
question. The implemented math that backs this stuff should be swappable over
time as better implementations are developed.

## Environment setup

The package is available exclusively via pip.  We strongly recommend using an
environment manager (virtualenv, conda, micromamba, uv, etc.) to track and
manage your dependencies.

```
pip install ctrecon
```

If you get a warning that matplotlib cannot show the window, you may also need
to install a different backend for matplotlib (e.g., `pip install PtQt5`).
Refer to the [matplotlib
documentation](https://matplotlib.org/stable/users/explain/figure/backends.html)
if you encounter any issues.

## Hello world example

This example comes from the package's "unit tests".  They're not actually unit tests
because they're interactive, but they're great sanity checks to see if and how
the package runs on your computer:


```python
from ctrecon import phantoms, scanners, tools, images


if __name__ == "__main__":
    target_support_size = 50  # cm

    # These parameters are for a rough approximation
    # of a clinical Definition AS 64
    R_si = 59.5  # cm
    R_sd = 109.5  # cm

    n_channels = 736  # number of detector elements
    det_size = target_support_size / n_channels

    phantom = phantoms.DebugPhantom()
    scanner = scanners.PencilBeamScanner(59.5, 109.5, n_channels, det_size)

    sinogram, projection_metadata = scanner.scan(phantom)

    center_x = 0.0
    center_y = 0.0
    fov = 30.0
    N = 256

    sinogram_noisy = tools.add_noise(sinogram, 1000)

    image = images.DebugImage(center_x, center_x, fov, N)
    image.set_debug_info(phantom, sinogram, sinogram_noisy, scanner)

    image.reconstruct(scanner, sinogram, projection_metadata)
    image.display()
```

The idea is to apply some sane logical structure the many components required
to go from simulated phantom, to CT sinogram, and back to image.  My hope is
that using the library feels like describing the experiment you're doing with
normal language, and your code makes it feel obvious what experiment is being
done.

- `phantoms` contains prebaked phantoms, as well as the tools to build your own
- `scanners` contains prebaked scan geometries, and the interface classes
  (`ScanGeometry`) to build your own (and preserve compatibility with the rest
  of the toolchain).
- `images` contains different image geometries
- `tools` only contains `add_noise` for now, which may migrate elsewhere at some point

## BYO Phantom

This is the full definition of our `DebugPhantom`:

```python
class DebugPhantom(Phantom):
    def __init__(self):

        # tuneable elements, units are cm
        bg_attenuation = 0.0192
        ph_radius = 10.0

        elements = []

        bg = Circle(0, 0, ph_radius, bg_attenuation)

        findable_insert_1 = Circle(0, 5, 3.0, 0.04)
        findable_insert_2 = Circle(5, 0, 1.5, 0.04)

        # Intuitively, we draw from "bg" -> "fg"
        elements.append(bg)
        elements.append(findable_insert_1)
        elements.append(findable_insert_2)

        # When raycasting, its most efficient to intersect the fg first
        elements.reverse()

        # Save the elements of the phantom
        self.circles = elements
```

The concept is that we basically draw objects into the world space.
We calculate sinograms via raycasting and each step iterates through 
the list of objects to check if we're inside.  For that increment, 
we utilize the attenuation of the first object in the list that we
encounter and move on.

The same phantom can be completely described via CSV as well.  Note that the
row order sets the object "precedence" in the sinogram generation process.

`debug_phantom.csv`:

```csv
5,0,1.5,0.04
0,5,3.0,0.04
0,0,10.0,0.0192
```

You can save a phantom a CSV file:

```
from ctrecon import phantoms

ph = phantoms.DebugPhantom()
ph.to_csv('debug_phantom.csv')
```

A phantom can be instantiate from such a CSV:

```python
debug_ph = Phantom.from_csv("debug_phantom.csv")
```

This at least describes phantoms in a *mostly* portable manner.  Free to use
in other places and between research groups.

Only circles are supported for now.  I hope to extend to other shapes in the
near future.
