summaryrefslogtreecommitdiffstats
path: root/docs/source/framework.rst
blob: 35d68fb59e658d7234f5bf3585baf6f25e4e00ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
Framework 
*********
The goal of the CCPi Framework is to allow the user to simply create iterative reconstruction methods which
go beyond the standard filter back projection technique and which better suit the data characteristics.
The framework comprises: 

* :code:`ccpi.framework` module which allows to simply translate real world CT systems into software. 
* :code:`ccpi.optimisation` module allows the user to quickly create iterative methods to reconstruct acquisition data applying different types of regularisation, which better suit the data characteristics.
* :code:`ccpi.io` module which provides a number of loaders for real CT machines, e.g. Nikon. It also provides reader and writer to save to NeXuS file format.

CT Geometry
===========

Please refer to `this <https://github.com/vais-ral/CIL-Demos/blob/v19.10.1/Notebooks/00_building_blocks.ipynb>`_ notebook on the CIL-Demos 
repository for full description.


In conventional CT systems, an object is placed between a source emitting X-rays and a detector array 
measuring the X-ray transmission images of the incident X-rays. Typically, either the object is placed 
on a rotating sample stage and rotates with respect to the source-detector assembly, or the 
source-detector gantry rotates with respect to the stationary object. 
This arrangement results in so-called circular scanning trajectory. Depending on source and detector 
types, there are three conventional data acquisition geometries:

* parallel geometry (2D or 3D),
* fan-beam geometry, and
* cone-beam geometry.

Parallel geometry
-----------------

Parallel beams of X-rays are emitted onto 1D (single pixel row) or 2D detector array. This geometry 
is common for synchrotron sources. 2D parrallel geometry is illustrated below.

.. figure:: images/parallel.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    2D Parallel geometry

.. figure:: images/parallel3d.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    3D Parallel geometry

Fan-beam geometry
-----------------

A single point-like X-ray source emits a cone beam onto 1D detector pixel row. Cone-beam is typically
 collimated to imaging field of view. Collimation allows greatly reduce amount of scatter radiation 
 reaching the detector. Fan-beam geometry is used when scattering has significant influence on image 
 quality or single-slice reconstruction is sufficient.

.. figure:: images/fan.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Fan beam geometry

Cone-beam geometry
------------------
A single point-like X-ray source emits a cone beam onto 2D detector array. 
Cone-beam geometry is mainly used in lab-based CT instruments. Depending on where the sample
is placed between the source and the detector one can achieve a different magnification factor :math:`F`:

.. math::
  
  F = \frac{r_1 + r_2}{r_1}

where :math:`r_1` and :math:`r_2` are the distance from the source to the center of the sample and 
the distance from the center of the sample to the detector, respectively.

.. figure:: images/cone.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Cone beam geometry

AcquisitonGeometry and AcquisitionData
======================================

In the Framework, we implemented :code:`AcquisitionGeometry` class to hold acquisition parameters and 
:code:`ImageGeometry` to hold geometry of a reconstructed volume. Corresponding data arrays are wrapped
as :code:`AcquisitionData` and :code:`ImageData` classes, respectively.

The simplest (of course from image processing point of view, not from physical implementation) geometry 
is the parallel geometry. Geometrical parameters for parallel geometry are depicted below:

.. figure:: images/parallel_geometry.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Parallel geometry

In the Framework, we define :code:`AcquisitionGeometry` as follows.

.. code:: python

  # imports
  from ccpi.framework import AcquisitionGeometry
  import numpy as np

  # acquisition angles
  n_angles = 90
  angles = np.linspace(0, np.pi, n_angles, dtype=np.float32)

  # number of pixels in detector row
  N = 256

  # pixel size
  pixel_size_h = 1

  # # create AcquisitionGeometry
  ag_par = AcquisitionGeometry(geom_type='parallel',
                             dimension='2D',
                             angles=angles,
                             pixel_num_h=N,
                             pixel_size_h=pixel_size_h)


:code:`AcquisitionGeometry` contains only metadata, the actual data is wrapped in :code:`AcquisitionData` 
class. :code:`AcquisitionGeometry` class also holds information about arrangement of the actual 
acquisition data array. \
We use attribute :code:`dimension_labels` to label axis. The expected dimension labels are shown below.
The default order of dimensions for :code:`AcquisitionData` is :code:`[angle, horizontal]`, meaning that 
the number of elements along 0 and 1 axes in the acquisition data array is expected to be :code:`n_angles` 
and :code:`N`, respectively.

.. figure:: images/parallel_data.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Parallel data

To have consistent :code:`AcquisitionData` and :code:`AcquisitionGeometry`, we recommend to allocate
:code:`AcquisitionData` using :code:`allocate` method of the :code:`AcquisitionGeometry` instance:

.. code:: python

  # allocate AcquisitionData
  ad_par = ag_par.allocate()


ImageGeometry and ImageData
===========================

To store reconstruction results, we implemented two classes: :code:`ImageGeometry` and :code:`ImageData` classes.
Similar to :code:`AcquisitionData` and :code:`AcquisitionGeometry`, we first define 2D :code:`ImageGeometry`
and then allocate :code:`ImageData`.

.. code:: python

  # imports
  from ccpi.framework import ImageData, ImageGeometry

  # define 2D ImageGeometry
  # given AcquisitionGeometry ag_par, default parameters for corresponding ImageData
  ig_par = ImageGeometry(voxel_num_y=ag_par.pixel_num_h, 
                       voxel_size_x=ag_par.pixel_size_h,
                       voxel_num_x=ag_par.pixel_num_h,
                       voxel_size_y=ag_par.pixel_size_h)

  # allocate ImageData filled with 0 values with the specific geometry
  im_data1 = ig_par.allocate()
  # allocate ImageData filled with random values with the specific geometry
  im_data2 = ig_par.allocate('random', seed=5)

3D parallel, fan-beam and cone-beam geometries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Fan-beam, cone-beam and 3D (multi-slice) parallel geometry can be set-up similar to 2D parallel geometry.

3D parallel geometry
--------------------
.. figure:: images/parallel3d_geometry.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Geometrical parameters and dimension labels for 3D parallel beam geometry


3D parallel beam :code:`AcquisitionGeometry` and default :code:`ImageGeometry` parameters can be set up
as follows:

.. code:: python

  # set-up 3D parallel beam AcquisitionGeometry
  # physical pixel size
  pixel_size_h = 1
  ag_par_3d = AcquisitionGeometry(geom_type='parallel', 
                                dimension='3D', 
                                angles=angles, 
                                pixel_num_h=N, 
                                pixel_size_h=pixel_size_h, 
                                pixel_num_v=N, 
                                pixel_size_v=pixel_size_h)
  # set-up 3D parallel beam ImageGeometry
  ig_par_3d = ImageGeometry(voxel_num_x=ag_par_3d.pixel_num_h,
                          voxel_size_x=ag_par_3d.pixel_size_h,
                          voxel_num_y=ag_par_3d.pixel_num_h,
                          voxel_size_y=ag_par_3d.pixel_size_h,
                          voxel_num_z=ag_par_3d.pixel_num_v,
                          voxel_size_z=ag_par_3d.pixel_size_v)



Fan-beam geometry
-----------------

.. figure:: images/fan_geometry.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Geometrical parameters and dimension labels for fan-beam geometry. 


.. figure:: images/fan_data.png
    :align: center
    :alt: alternate text
    :figclass: align-center

    Geometrical parameters and dimension labels for fan-beam data. 


Fan-beam :code:`AcquisitionGeometry` and 
default :code:`ImageGeometry` parameters can be set up as follows:


.. code :: python

  # set-up fan-beam AcquisitionGeometry
  # distance from source to center of rotation
  dist_source_center = 200.0
  # distance from center of rotation to detector
  dist_center_detector = 300.0
  # physical pixel size
  pixel_size_h = 2
  ag_fan = AcquisitionGeometry(geom_type='cone', 
                             dimension='2D', 
                             angles=angles, 
                             pixel_num_h=N, 
                             pixel_size_h=pixel_size_h, 
                             dist_source_center=dist_source_center, 
                             dist_center_detector=dist_center_detector)
  # calculate geometrical magnification
  mag = (ag_fan.dist_source_center + ag_fan.dist_center_detector) / ag_fan.dist_source_center

  ig_fan = ImageGeometry(voxel_num_x=ag_fan.pixel_num_h,
                       voxel_size_x=ag_fan.pixel_size_h / mag,
                       voxel_num_y=ag_fan.pixel_num_h,
                       voxel_size_y=ag_fan.pixel_size_h / mag)







.. autoclass:: ccpi.framework.ImageGeometry
   :members:
.. autoclass:: ccpi.framework.AcquisitionGeometry
   :members:
.. autoclass:: ccpi.framework.VectorGeometry
   :members:


=======

``DataContainer`` and subclasses ``AcquisitionData`` and ``ImageData`` are 
meant to contain data and meta-data in ``AcquisitionGeometry`` and 
``ImageGeometry`` respectively.

DataContainer and subclasses
============================


:code:`AcquisiionData` and :code:`ImageData` inherit from the same parent :code:`DataContainer` class, 
therefore they largely behave the same way.

There are algebraic operations defined for both :code:`AcquisitionData` and :code:`ImageData`. 
Following operations are defined:

* binary operations (between two DataContainers or scalar and DataContainer)

  * :code:`+` addition
  * :code:`-` subtraction
  * :code:`/` division
  * :code:`*` multiplication
  * :code:`**` power
  * :code:`maximum`
  * :code:`minimum`

* in-place operations

  * :code:`+=`
  * :code:`-=`
  * :code:`*=`
  * :code:`**=`
  * :code:`/=`

* unary operations

  * :code:`abs`
  * :code:`sqrt`
  * :code:`sign`
  * :code:`conjugate`

* reductions

  * :code:`sum`
  * :code:`norm`
  * :code:`dot` product

:code:`AcquisitionData` and :code:`ImageData` provide a simple method to transpose the data and to 
produce a subset of itself based on the axis we would like to have. This method is based on the label of
the axes of the data rather than the way it is stored. We think that the user should describe what she 
wants and not bother with knowing the actual layout of the data in the memory.

.. code:: python

  # transpose data using subset method
  data_transposed = data.subset(['horizontal_y', 'horizontal_x'])
  # extract single row
  data_profile = data_subset.subset(horizontal_y=100)



.. autoclass:: ccpi.framework.DataContainer
   :members:
   :private-members:
   :special-members:
.. autoclass:: ccpi.framework.ImageData
   :members:
.. autoclass:: ccpi.framework.AcquisitionData
   :members:
.. autoclass:: ccpi.framework.VectorData
   :members:


Multi channel data
------------------

Both :code:`AcquisitionGeometry`, :code:`AcquisitionData` and :code:`ImageGeometry`, :code:`ImageData`
can be defined for multi-channel (spectral) CT data using :code:`channels` attribute.

.. code:: python
  
  # multi-channel fan-beam geometry
  ag_fan_mc = AcquisitionGeometry(geom_type='cone', 
                                 dimension='2D', 
                                 angles=angles, 
                                 pixel_num_h=N, 
                                 pixel_size_h=1, 
                                 dist_source_center=200, 
                                 dist_center_detector=300,
                                 channels=10)

  # define multi-channel 2D ImageGeometry
  ig3 = ImageGeometry(voxel_num_y=5, 
                      voxel_num_x=4, 
                      channels=2)


Block Framework 
===============

The block framework allows writing more advanced `optimisation problems`_. Consider the typical 
`Tikhonov regularisation <https://en.wikipedia.org/wiki/Tikhonov_regularization>`_:

.. math:: 

  \underset{u}{\mathrm{argmin}}\begin{Vmatrix}A u - b \end{Vmatrix}^2_2 + \alpha^2\|Lu\|^2_2

where,

* :math:`A` is the projection operator
* :math:`b` is the acquired data
* :math:`u` is the unknown image to be solved for
* :math:`\alpha` is the regularisation parameter
* :math:`L` is a regularisation operator

The first term measures the fidelity of the solution to the data. The second term meausures the 
fidelity to the prior knowledge we have imposed on the system, operator :math:`L`.  

This can be re-written equivalently in the block matrix form:

.. math::
  \underset{u}{\mathrm{argmin}}\begin{Vmatrix}\binom{A}{\alpha L} u - \binom{b}{0}\end{Vmatrix}^2_2

With the definitions:

* :math:`\tilde{A} = \binom{A}{\alpha L}`
* :math:`\tilde{b} = \binom{b}{0}`

this can now be recognised as a least squares problem which can be solved by any algorithm in the :code:`ccpi.optimisation`
which can solve least squares problem, e.g. CGLS.

.. math:: 

  \underset{u}{\mathrm{argmin}}\begin{Vmatrix}\tilde{A} u - \tilde{b}\end{Vmatrix}^2_2

To be able to express our optimisation problems in the matrix form above, we developed the so-called, 
Block Framework comprising 4 main actors: :code:`BlockGeometry`, :code:`BlockDataContainer`, 
:code:`BlockFunction` and :code:`BlockOperator`.

A :code:`BlockDataContainer` can be instantiated from a number of :code:`DataContainer` and subclasses
represents a column vector of :code:`DataContainer`s.

.. code:: python

  bdc = BlockDataContainer(DataContainer0, DataContainer1)

. These 
classes are required for it to work. They provide a base class that will 
behave as normal ``DataContainer``.

.. autoclass:: ccpi.framework.BlockDataContainer
   :members:
   :private-members:
   :special-members:

.. autoclass:: ccpi.framework.BlockGeometry
   :members:
   :private-members:
   :special-members:

DataProcessor
=============

A :code:`DataProcessor` takes as an input a :code:`DataContainer` or subclass and returns either 
another :code:`DataContainer` or some number. The aim of this class is to simplify the writing of 
processing pipelines.

.. autoclass:: ccpi.framework.DataProcessor
   :members:
   :private-members:
   :special-members:


Resizer
-------

Quite often we need either crop or downsample data; the :code:`Resizer` provides a convenient way to 
perform these operations for both :code:`ImageData` and :code:`AcquisitionData`. 


.. code:: python
  
  # imports
  from ccpi.processors import Resizer
  # crop ImageData along 1st dimension
  # initialise Resizer
  resizer_crop = Resizer(binning = [1, 1], roi = [-1, (20,180)])
  # pass DataContainer
  resizer_crop.input = data
  data_cropped = resizer_crop.process()
  # get new ImageGeometry
  ig_data_cropped = data_cropped.geometry

.. autoclass:: ccpi.processors.Resizer
   :members:
   :private-members:
   :special-members:



Calculation of Center of Rotation
---------------------------------

In the ideal alignment of a CT instrument, orthogonal projection of an axis of rotation onto a 
detector has to coincide with a vertical midline of the detector. This is barely feasible in practice
due to misalignment and/or kinematic errors in positioning of CT instrument components. 
A slight offset of the center of rotation with respect to the theoretical position will contribute 
to the loss of resolution; in more severe cases, it will cause severe artifacts in the reconstructed 
volume (double-borders). :code:`CenterOfRotationFinder` allows to estimate offset of center of rotation 
from theoretical. In the current release :code:`CenterOfRotationFinder` supports only parallel geometry. 

:code:`CenterOfRotationFinder` is based on Nghia Vo's `method <https://doi.org/10.1364/OE.22.019078>`_.

.. autoclass:: ccpi.processors.CenterOfRotationFinder
   :members:
   :private-members:
   :special-members:


:ref:`Return Home <mastertoc>`

.. _optimisation problems: optimisation.html