Code Monkey home page Code Monkey logo

tomophantom's Introduction


TomoPhantom [1] is a toolbox to generate customisable 2D-4D phantoms (with a temporal capability) and their analytical tomographic projection data for parallel-beam geometry. It can be used for testing various tomographic reconstruction methods, as well as image processing methods, such as, denoising, deblurring, segmentation, and machine/deep learning tasks.



NEW! VERSION 3.0

TomoPhantom has been refactored, please see changes. Everyone is welcome to Documentation page.

About TomoPhantom

TomoPhantom is recommended for various image processing tasks that require extensive numerical testing: image reconstruction, denoising, deblurring, etc. In particular, TomoPhantom is best-suited for testing various tomographic image reconstruction (TIR) methods. For TIR algorithms testing, the popular Shepp-Logan phantom is not always a good choice due to its piecewise-constant nature. This toolbox provides a simple modular approach to efficiently build customisable 2D-4D phantoms consisting of piecewise-constant, piecewise-smooth, and smooth analytical objects as well as their analytical Radon transforms .

What TomoPhantom can do:

  • Generate 2D and 3D synthetic phantoms made of Gaussians, parabolas, ellipses, cones and rectangulars.
  • Generate simple temporal extensions of 2D and 3D phantoms.
  • Calculate analytical Radon transforms of 2D-4D models and also their numerical projections.
  • Model a variety of tomographic data artefacts (noise models, zingers, rings, shifts, partial volume effect and others).

Installation:

Tomophantom is distributed as a Python conda package for Linux/Windows/Mac OS's:

conda install -c httomo tomophantom

Please see more detailed information on Installation and development environments.

Related software projects on GitHub:

  • xdesign XDesign is an open-source Python package for generating configurable simulation phantoms for benchmarking tomographic image reconstruction.
  • syris Syris (synchrotron radiation imaging simulation) is a framework for simulations of X-ray absorption and phase contrast dynamic imaging experiments, like time-resolved radiography, tomography or laminography.

References:

[1] D. Kazantsev et al. 2018, TomoPhantom, a software package to generate 2D-4D analytical phantoms for CT image reconstruction algorithm benchmarks, Software X, Volume 7, January–June 2018, Pages 150–155

[2] D. Kazantsev, V. Pickalov "New iterative reconstruction methods for fan-beam tomography", IPSE, 2017

Applications:

Software related questions/comments please e-mail to Daniil Kazantsev at [email protected]

tomophantom's People

Contributors

decarlof avatar dimitriosbellos avatar dkazanc avatar gfardell avatar paskino avatar srikanthnagella avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tomophantom's Issues

3D Projection of phantoms with shapes of elliptical cylinder and cuboid don't have tilting rectangular shapes whatever Euler angles set

Code that randomly generates 3D phantoms including elliptical cylinders only

# Import the necessary libraries.
# ~~~python
import os
import numpy as np
import matplotlib.pyplot as plt
import tifffile
import random
from copy import deepcopy
import numpy as np

from tomophantom import TomoP3D

# Define the path and name of the `.dat` file that will contain the phantoms model description. If the file exists, the new models will be appended.python

path_1 = '/home/mli/tomograms/pycharm_demos/single_density/without_Gaussian_shapes/normal_test/without_noise/1024*1024_with_oversampling=2/Generate_Training_Data/Phantom3DLibrary_For_att_without_Gaussian_3.dat'
path_2 = '/home/mli/tomograms/pycharm_demos/single_density/without_Gaussian_shapes/normal_test/without_noise/1024*1024_with_oversampling=2/Generate_Training_Data/Phantom3DLibrary_For_phase_without_Gaussian_3.dat'

# Generate the models:

number_of_phantoms = 1000

# components = ['gaussian','paraboloid', 'ellipsoid', 'cone', 'cuboid', 'elliptical_cylinder']
# the unused components are not uniform in density.

components = ['elliptical_cylinder']
mu = [0.328]
delta = [481]

for n in range(1, number_of_phantoms + 1):
    num_of_components = random.randint(1, 5)

    objects_1 = []
    objects_2 = []
    component_subset = random.choices(components, k=random.randint(1, 3))
    for i in range(num_of_components):
        obj_1 = random.choice(component_subset)
        obj_2 = deepcopy(obj_1)
        upper_lim_half_width = random.choice([0.5])

        c0 = 0
        m0 = mu[c0]
        d0 = delta[c0]

        if obj_1 == 'elliptical_cylinder' or 'cuboid':

            p_or_n_x = random.choices([-1, 1], k=1)
            p_or_n_y = random.choices([-1, 1], k=1)
            p_or_n_z = random.choices([-1, 1], k=1)

            x0 = round(random.uniform(-1.0*p_or_n_x[0], -0.75*p_or_n_x[0]), 2)
            y0 = round(random.uniform(-1.0*p_or_n_y[0], -0.75*p_or_n_y[0]), 2)
            z0 = round(random.uniform(-1.0*p_or_n_z[0], -0.75*p_or_n_z[0]), 2)
            a = round(random.uniform(0.3, upper_lim_half_width), 3)
            b = round(random.uniform(0.3, upper_lim_half_width), 3)
            c = round(random.uniform(0.3, upper_lim_half_width), 3)
            # alpha = round(random.uniform(-180, 180), 2)
            alpha = 125
            # beta = round(random.uniform(-180, 180), 2)
            beta = 70
            # gamma = round(random.uniform(-180, 180), 2)
            gamma = 50
            print('ellip')

        else:
            x0 = round(random.uniform(-0.5, 0.5), 2)
            y0 = round(random.uniform(-0.5, 0.5), 2)
            z0 = round(random.uniform(-0.5, 0.5), 2)
            a = round(random.uniform(0.01, upper_lim_half_width), 3)
            b = round(random.uniform(0.01, upper_lim_half_width), 3)
            c = round(random.uniform(0.01, upper_lim_half_width), 3)
            alpha = round(random.uniform(-180, 180), 2)
            beta = round(random.uniform(-180, 180), 2)
            gamma = round(random.uniform(-180, 180), 2)

        obj_1 += ' {c0} {x0} {y0} {x0} {a} {b} {c} {alpha} {beta} {gamma}'.format(c0=m0,
                                                                                  # round(random.uniform(0.1,1),2),
                                                                                  x0=x0,
                                                                                  y0=y0,
                                                                                  z0=z0,
                                                                                  a=a,
                                                                                  b=b,
                                                                                  c=c,
                                                                                  alpha=alpha,
                                                                                  beta=beta,
                                                                                  gamma=gamma)

        obj_2 += ' {c0} {x0} {y0} {x0} {a} {b} {c} {alpha} {beta} {gamma}'.format(c0=d0,
                                                                                  # round(random.uniform(0.1,1),2),
                                                                                  x0=x0,
                                                                                  y0=y0,
                                                                                  z0=z0,
                                                                                  a=a,
                                                                                  b=b,
                                                                                  c=c,
                                                                                  alpha=alpha,
                                                                                  beta=beta,
                                                                                  gamma=gamma)

        objects_1.append(obj_1)
        objects_2.append(obj_2)

    phantom_string_1 = '''#----------------------------------------------------
# random phantom number {num}
Model : {num};
Components : {comp};
TimeSteps : 1;
'''.format(num=n, comp=num_of_components)

    phantom_string_2 = '''#----------------------------------------------------
# random phantom number {num}
Model : {num};
Components : {comp};
TimeSteps : 1;
'''.format(num=n, comp=num_of_components)

    for obj in objects_1:
        phantom_string_1 += 'Object : ' + obj + '\n'

    with open(path_1, 'a') as file:
        file.write(phantom_string_1)

    for obj in objects_2:
        phantom_string_2 += 'Object : ' + obj + '\n'

    with open(path_2, 'a') as file:
        file.write(phantom_string_2)

Code that make projections of 3D randomly generated phantoms

import os
import numpy as np
import matplotlib.pyplot as plt
import tifffile
from sklearn import preprocessing
from tomophantom import TomoP3D

# Define the path and name of the `.dat` file that will contain the phantoms model description. If the file exists, the new models will be appended.python

path_1 = '/home/mli/tomograms/pycharm_demos/single_density/without_Gaussian_shapes/normal_test/without_noise/1024*1024_with_oversampling=2/Generate_Training_Data/Phantom3DLibrary_For_att_without_Gaussian_3.dat'
path_2 = '/home/mli/tomograms/pycharm_demos/single_density/without_Gaussian_shapes/normal_test/without_noise/1024*1024_with_oversampling=2/Generate_Training_Data/Phantom3DLibrary_For_phase_without_Gaussian_3.dat'


# Define the image size.

N_size = 1024

#Horiz_det = int(np.sqrt(2)*N_size) # detector column count (horizontal)
Horiz_det = N_size # detector column count (horizontal)
print("Horiz_det: ", Horiz_det)

Vert_det = N_size # detector row count (vertical) (no reason for it to be > N)
print("Vert_det: ", Vert_det)

# angles_num = int(0.5*np.pi*N_size); # angles number
angles_num = 5
print("angles_num: ", angles_num)

angles = np.linspace(0.0, 179.9, angles_num, dtype='float32') # in degrees
print("angles: ", angles)


# Created files
os.makedirs('../projection_2', exist_ok=True)
os.makedirs('../projection_2/tif_format', exist_ok=True)
os.makedirs('../projection_2/npy_format', exist_ok=True)

# Create the directories to where the files will be saved in tif format. For attenuation
os.makedirs('../projection_2/tif_format/attenuation_projection_in_tif_format', exist_ok=True)

# Create the directories to where the files will be saved in tif format. For phase
os.makedirs('../projection_2/tif_format/phase_projection_in_tif_format', exist_ok=True)

# Create the directories to where the files will be saved in npy format. For attenuation
os.makedirs('../projection_2/npy_format/attenuation_projection_in_npy_format', exist_ok=True)

# Create the directories to where the files will be saved in npy format. For phase
os.makedirs('../projection_2/npy_format/phase_projection_in_npy_format', exist_ok=True)

# Create the directories to where the files contain the stacks of attenuation and phase together and save in .npy format
os.makedirs('../projection_2/npy_format/projection_stack_in_npy_format', exist_ok=True)


# Generate the phantoms from models *a* to *b* from the *Phantom3DLibrary3.dat* library using TomoP3D.ModelSino.
pixel_size = 0.7e-6
scaling_factor = pixel_size * 100

# Projection
N = 1024
# For attenuation
projData3D_analyt_list_For_attenuation = []
for model in range(901, 1001):  # For phantom No.1 ~ 1000
    projData3D_analyt_attenuation = TomoP3D.ModelSino(model, N_size, Horiz_det, Vert_det, angles,
                                                      path_1) * scaling_factor
    print('projection shape: ', projData3D_analyt_attenuation.shape)
    projData3D_analyt_list_For_attenuation.append(projData3D_analyt_attenuation)

# For phase
projData3D_analyt_list_For_phase = []
for model in range(901, 1001):  # For phantom No.1 ~ 99
    projData3D_analyt_phase = TomoP3D.ModelSino(model, N_size, Horiz_det, Vert_det, angles, path_2) * scaling_factor
    projData3D_analyt_list_For_phase.append(projData3D_analyt_phase)



# At this step you may: extract one slice for each projection sinogram, extract one angle
# Save in tiff picure format

# For attenuation
for model in range(1, 101):      # For phantom No.1 ~ 1000
    projected_3D = projData3D_analyt_list_For_attenuation[model-1]
    projected_slice = projected_3D[::, 0, ::]
    tifffile.imsave('../projection_2/tif_format/attenuation_projection_in_tif_format/{:05d}.tif'.format(model+900), projected_slice.astype(np.float32))
    np.save('../projection_2/npy_format/attenuation_projection_in_npy_format/{:05d}.npy'.format(model+900), projected_slice.astype(np.float32))

# For phase
for model in range(1, 101):      # For phantom No.1 ~ 1000
    projected_3D = projData3D_analyt_list_For_phase[model-1]
    projected_slice = projected_3D[::, 0, ::]
    tifffile.imsave('../projection_2/tif_format/phase_projection_in_tif_format/{:05d}.tif'.format(model+900), projected_slice.astype(np.float32))
    np.save('../projection_2/npy_format/phase_projection_in_npy_format/{:05d}.npy'.format(model+900), projected_slice.astype(np.float32))


# At this step you may: extract one slice for each projection sinogram (both attenuation and phase), extract one angle
# Stack them together and save in .npy format

for model in range(1, 101):      # For phantom No.1 ~ 1000
    projected_slice = [0, 0]
    projected_3D_attenuation = projData3D_analyt_list_For_attenuation[model-1]
    projected_3D_phase = projData3D_analyt_list_For_phase[model-1]
    projected_slice[0] = projected_3D_attenuation[::, 0, ::]
    projected_slice[1] = projected_3D_phase[::, 0, ::]
    projected_slice = np.array(projected_slice)
    np.save('../projection_2/npy_format/projection_stack_in_npy_format/{:05d}.npy'.format(model+900), projected_slice.astype(np.float32))

Refactoring

Why should we have a directory supp? Since it is part of the package I don't think it should be called supplemental.

The ArtifactsClass's instance methods are in facts static methods: it should be changed to that.

Negative amplitude?

Thanks for this amazing work! I have a question of how to set the linear attenuation numbers.

I guess the amplitude in the paper indicates the attenuation values, right? If then, I see some negative values in some data (e.g., in 3d Shepp-Logan -0.8 below) Can I know if it's fine to set negative values?

#----------------------------------------------------
#  3D Shepp-Logan
Model : 13;
Components : 10;
TimeSteps : 1;
Object : ellipsoid 1.0 0.0 0.0 0.0 0.69 0.92 0.81 0.0 0.0 0.0
Object : ellipsoid -0.8 0.0184 0.0 0.0 0.6624 0.874 0.78 0.0 0.0 0.0

Cmaking of TomoPhantom

Would be a great advantage for debugging stage to be able to compile C-code independently of conda-build.

libraryToDict with Python 2.7

Script DemoModel2.py fails with error:
TypeError: str() takes at most 1 argument (2 given)
I think it has to do with extracting parameters from *dat file using libraryToDict function. Check 'Obj' in 'objlist', there should be names of the objects?

location of Phantom library files

For Python they can be located as

import tomophantom
import os
path = os.path.dirname(tomophantom.__file__)

Demos should be updated

Enable partial objects cutoffs

Much more complicated phantoms and projection data can be generated by enabling cutoffs to the objects, e.g. half of the ellipse, gaussian etc.

Change of the interface

I've changed the interface to something more comprehensive: def Model - consists of multiple objects and def Object - just one geometrical being. Similarly def ModelSino or def ObjectSino for sinograms.

So now the model can be called as TomoP2D.Model or TomoP3D.Model for 3D Version.
@paskino Regarding a new capability of creating separate objects I think @srikanthnagella already did quite a lot on that here:

def Object(int phantom_size, object_2d[:] obj_params):

So I'm just want to test it and prepare an example.

Correct geometry for sinogram input/output

I've changed the geometry handling in core functions slightly [obj_append branch]. Matlab demos work well, however Python demo (DemoTomoPhantom.py) produces sinogram of a wrong content. Changing sinogram dimensions in Cython code does not change the output dimensions.

cdef np.ndarray[np.float32_t, ndim=2, mode="c"] sinogram = np.zeros([detector_size,angles.shape[0]], dtype='float32')

Also when incorrect sinogram obtained you can the right dimensions back after reshape/transpose:
sinoR = sinogram.reshape(P,angles.shape[0]).transpose()

User defined modelling of the temporal behaviour

Long term plans - to ensure ability to model dynamic behaviour of phantoms by providing descriptors (vectors for describing the motion). This insures a capability of creating complex motion patterns. Convenient syntax needs to be established. Tentative - a text file with vectors for each object in the model.

An option to create non-cubic 3D phantoms

moving from:
a) TomoP3DModel(ModelNo,N,pathTP) where N is a scalar leading to N x N x N phantom
to:
b) TomoP3DModel(ModelNo, DIM ,pathTP) where DIM is a tuple [N1, N1, N2] leading to N1 x N1 x N2 phantom. Version a) needs to be supported as well with DIM[1] = N

Problem with installation for Windows via Anaconda (No module named 'tomophantom')

Hello,

I am trying to install Tomophantom to our windows PC, but, the module can not be found, and there are not any files in the Lib\site-packages . Nevertheless, the steps I went through are as follows, create new anaconda environment with python 3.6 then install CMake and visual studios followed by TomoPhantom. I do receive a few files namely "tomophantom-1.4-np114py36_0.json" and its two specified files.

Best regards,
Philip

Create a new release?

As the code has received new functionality, I think we should tag it with a new release version 1.2.0.

Then I can try to distribute it on the ccpi channel.

cythonizing phantom2D

Trying to cythonize phantom2 (following phantom3 pattern) in newSino3D branch... Although I'm able to compile and run, the result is gibberish. Possibly some issues with phantom2D.pyx? help is appreciated!

can't create cylinder objects

Hi!
I'm trying to make a phantom with several cylinders, but somehow they just don't show up, even though I don't get an error message. I've also noticed that the name in the Objects3D class is apparently not elliptical_cylinder (as suggested in the model examples) but rather ELLIPCYLINDER?

Here's a snippet of my code:

`import sys
import os
sys.path.append(os.path.dirname(os.path.realpath(file))+"\Functions")
import helpers_data
import numpy as np
import matplotlib.pyplot as plt
from tomophantom import TomoP3D
from tomophantom.TomoP3D import Objects3D
import tomophantom

N3D_size = 128*5
obj3D_1 = {'Obj': Objects3D.ELLIPCYLINDER,
'C0' : 1,
'x0' : 0,
'y0' : 0,
'z0' : 0,
'a' : 0.5,
'b' : 0.5,
'c' : 1,
'phi1' : 0}

Object3D = TomoP3D.Object(N3D_size, obj3D_1)

print ("Building 3D object using TomoPhantom software")

sliceSel = int(0.5*N3D_size)

plt.figure()
plt.subplot(131)
plt.imshow(Object3D[sliceSel,:,:],vmin=0, vmax=1)
plt.title('3D Object, axial view')

plt.subplot(132)
plt.imshow(Object3D[:,sliceSel,:],vmin=0, vmax=1)
plt.title('3D Object, coronal view')

plt.subplot(133)
plt.imshow(Object3D[:,:,sliceSel],vmin=0, vmax=1)
plt.title('3D Object, sagittal view')
plt.show()`

Error with "compile_mex_windows.m"

Hello,

I have been running into issues with building the MATLAB wrapper on windows. When running the file "compile_mex_windows" I'm met with the following error:

"Error using mex
C:\Users\rvs6146\AppData\Local\Temp\mex_1744130824622514_3484\TomoP2DModel.obj:TomoP2DModel.c:(.text+0x7ea): undefined reference to
__imp_TomoP2DObject_core'
C:\Users\rvs6146\AppData\Local\Temp\mex_1744130824622514_3484\TomoP2DModel.obj:TomoP2DModel.c:(.text+0xc5a): undefined reference to
`__imp_TomoP2DObject_core'
collect2.exe: error: ld returned 1 exit status

Error in compile_mex_windows (line 26)
mex TomoP2DModel.c TomoP2DModel_core.c utils.c COMPFLAGS="$COMPFLAGS -fopenmp -Wall -std=c99"

I have tried to debug this myself and used the second approach using TDM-GCC with no luck unfortunately. Do you by any chance have an idea as to why this may be happening? As well as a possible work around? I had seen a similar issue brought up by a user "bort" here: [https://www.mathworks.com/matlabcentral/fileexchange/63603-tomophantom] but was unable to see a solution.

Many thanks in advance :)

Flat-field simulator

  • add a capability to generate flat fields
  • add a capability to model slightly offset flat fields which can create some realistic mismatch and errors in normalisation

Numerical calculation of projections

A capability to generate numerical projection data as an alternative to analytical projections. Data can be generated as:

  • sino_an = TomoP2D.ModelSino(model, N_size, P, angles, pathTP) analytical sinogram
  • sino_num = TomoP2D.SinoNum(Phantom, P, angles) numerical sinogram

import of ArtifactClass is cumbersome

While this is not wrong, it makes syntaxis a bit cumbersome

import tomophantom
import tomophantom.supp.artifacts
from tomophantom.supp.artifacts import ArtifactClass

artifact = ArtifactClass(sino)

while it'd be easier to do

from tomophantom.supp.artifacts import ArtifactClass

artifact = ArtifactClass(sino)

utils.c added, python version requires reassembling

I've done some restructuring and also added utils.c to avoid repetition of code (MATLAB version works). Please help to modify phantom3d.pyx accordingly to compile code for python. I guess it should be added somewhere along those lines?
cdef extern float buildSino3D_core_single(float *A, int N, int P, float *Th, int AngTot, int CenTypeIn, int Object, float C0, float x0, float y0, float z0, float a, float b, float c, float phi_rot)

DemoObject3D.py crashes for Python 2.7

DemoObject3D.py runs from Python 3.5 but crashes with "kernel died" from 2.7. It seems

   if type(objlist) is dict:
       objlist = [objlist]
       
   for obj in objlist:
       if testParamsPY(obj):
           if sys.version_info.major == 3:
               objectName = bytes(obj['Obj'].value, 'ascii')
           elif sys.version_info.major == 2:
               objectName = bytes(obj['Obj'].value)

somewhat responsible in TomoP3D.pyx?

Adding random phantom generator

There is a need for random phantom generator. Specific aim is machine learning applications: artifacts removal, classification etc. Possibly creating phantoms according to some rule, e.g. avoiding significant overlaps.

Print instead of error

Here the code prints a "warning" to the user. However, this may go unnoticed.

The effect is an empty phantom. IMO the code should raise an exception, or possibly return None

Adaptation of TomoP3DModel to MPI

If one needs to build a subset:

TomoP3DModel_sub(ModelNo,DIM,SUB,pathTP) where DIM[3] = [N1,N1,N2] and SUB = 10:20 leading to the N1 x N1 x 10 subset out of N1 x N1 x N2 phantom

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.