Commit 04807474 authored by Pierre Paleo's avatar Pierre Paleo

Merge branch 'release_2020.4' into 'master'

Release 2020.4.0

Closes #183, #186, and #184

See merge request !79
parents 4a159813 fd59f606
Pipeline #35269 passed with stages
in 12 minutes and 52 seconds
# Change Log # Change Log
## 2020.4.0
This is a version adds a number of features.
### Application
- Automatic center of rotation estimation, working for both half-acquisition and regular scans
- Command line tool for splitting a NXTomo file by "z" series
- Volume histogram. Command line tool for merging histogram of multiple volumes.
- Enable to perform flat-field normalization with darks/flats from another dataset
- Sinogram normalization (baseline subtraction)
- Nabu does not need `tomwer_processes.h5` to get the "final" darks/refs anymore.
- Fix `fbp_filter_type = none`: now it will actually disable any filtering
- Fix auto-CoR not working when flatfield is disabled
### Library
- Add `misc.histogram` for computing partial histograms and merging them
- Add `preproc.sinogram.SinoNormalization`
- Fix double flat-field when `dff_sigma > 0`  which was giving nonsense results.
- Half-tomography: add support for center of rotation on the left side
## 2020.3.0 ## 2020.3.0
This is a release for ESRF User Service Mode restart. This is a release for ESRF User Service Mode restart.
......
nabu.misc.histogram module
==========================
.. automodule:: nabu.misc.histogram
:members:
:undoc-members:
:show-inheritance:
...@@ -9,6 +9,7 @@ Submodules ...@@ -9,6 +9,7 @@ Submodules
nabu.misc.binning nabu.misc.binning
nabu.misc.fourier_filters nabu.misc.fourier_filters
nabu.misc.histogram
nabu.misc.unsharp nabu.misc.unsharp
nabu.misc.unsharp_cuda nabu.misc.unsharp_cuda
nabu.misc.unsharp_opencl nabu.misc.unsharp_opencl
......
nabu.resources.cli.histogram module
===================================
.. automodule:: nabu.resources.cli.histogram
:members:
:undoc-members:
:show-inheritance:
nabu.resources.cli.nx\_z\_splitter module
=========================================
.. automodule:: nabu.resources.cli.nx_z_splitter
:members:
:undoc-members:
:show-inheritance:
...@@ -9,6 +9,8 @@ Submodules ...@@ -9,6 +9,8 @@ Submodules
nabu.resources.cli.bootstrap nabu.resources.cli.bootstrap
nabu.resources.cli.cli_configs nabu.resources.cli.cli_configs
nabu.resources.cli.histogram
nabu.resources.cli.nx_z_splitter
nabu.resources.cli.reconstruct nabu.resources.cli.reconstruct
nabu.resources.cli.utils nabu.resources.cli.utils
nabu.resources.cli.validate nabu.resources.cli.validate
......
nabu.resources.nxflatfield module
=================================
.. automodule:: nabu.resources.nxflatfield
:members:
:undoc-members:
:show-inheritance:
...@@ -23,6 +23,7 @@ Submodules ...@@ -23,6 +23,7 @@ Submodules
nabu.resources.logger nabu.resources.logger
nabu.resources.machinesdb nabu.resources.machinesdb
nabu.resources.nabu_config nabu.resources.nabu_config
nabu.resources.nxflatfield
nabu.resources.params nabu.resources.params
nabu.resources.processconfig nabu.resources.processconfig
nabu.resources.utils nabu.resources.utils
......
...@@ -22,7 +22,7 @@ copyright = '2020, ESRF' ...@@ -22,7 +22,7 @@ copyright = '2020, ESRF'
author = 'Pierre Paleo' author = 'Pierre Paleo'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '2020.3.0' #release = '2020.4.0'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
......
...@@ -56,6 +56,18 @@ Configuration file: section `[preproc]`: `take_logarithm = 1`, `log_min_clip = 1 ...@@ -56,6 +56,18 @@ Configuration file: section `[preproc]`: `take_logarithm = 1`, `log_min_clip = 1
API: [CCDProcessing.Log](apidoc/nabu.preproc.ccd.rst#nabu.preproc.ccd.Log) and [CudaLog](apidoc/nabu.preproc.ccd_cuda.rst#nabu.preproc.ccd_cuda.CudaLog) API: [CCDProcessing.Log](apidoc/nabu.preproc.ccd.rst#nabu.preproc.ccd.Log) and [CudaLog](apidoc/nabu.preproc.ccd_cuda.rst#nabu.preproc.ccd_cuda.CudaLog)
### Sinogram normalization
Sinograms can sometimes be "messy" for various reasons. For example, a synchrotron beam refill can take place during the acquisition, and not be compensated properly by flats.
In this case, you can "normalize" the sinogram to get rid of defects. Currently, only a baseline removal is implemented.
Mind that you probably lose quantativeness by using this additional normalization !
Configuration file: `[preproc]` : `sino_normalization = chebyshev`.
API: [SinoNormalization](apidoc/nabu.preproc.sinogram.rst#nabu.preproc.sinogram.SinoNormalization)
## Phase retrieval ## Phase retrieval
Phase retrieval is still part of the "pre-processing", although it has a dedicated section in the [configuration file](nabu_config_file). Phase retrieval is still part of the "pre-processing", although it has a dedicated section in the [configuration file](nabu_config_file).
...@@ -85,6 +97,7 @@ Configuration file: section `[phase]`: `unsharp_coeff = 1.` and `unsharp_sigma ...@@ -85,6 +97,7 @@ Configuration file: section `[phase]`: `unsharp_coeff = 1.` and `unsharp_sigma
Setting `coeff` to zero (default) disables unsharp masking. Setting `coeff` to zero (default) disables unsharp masking.
## Reconstruction ## Reconstruction
Tomographic reconstruction is the process of reconstructing the volume from projections/sinograms. Tomographic reconstruction is the process of reconstructing the volume from projections/sinograms.
...@@ -107,6 +120,16 @@ The purpose of this class is to quickly reconstruct slices over the three main a ...@@ -107,6 +120,16 @@ The purpose of this class is to quickly reconstruct slices over the three main a
The Reconstructor enables to reconstruct slices/region of interest without reconstructing the whole volume. The Reconstructor enables to reconstruct slices/region of interest without reconstructing the whole volume.
## Post-processing
### Histogram
Nabu can compute the histogram of the reconstucted (sub-) volume. As the volume usually does not fit in memory, the histogram is computed by parts, and the final histogram is obtained by merging partial histograms.
Configuration file: section `[postproc]`: `output_histogram = 1`, `histogram_bins = 1000000`.
API : [PartialHistogram](apidoc/nabu.misc.histogram.rst#nabu.misc.histogram.PartialHistogram) and [VolumeHistogram](apidoc/nabu.misc.histogram.rst#nabu.misc.histogram.VolumeHistogram)
## File formats ## File formats
### HDF5 ### HDF5
...@@ -118,7 +141,7 @@ When a [multi-stage reconstruction](nabu_cli.md) is performed, the volume is rec ...@@ -118,7 +141,7 @@ When a [multi-stage reconstruction](nabu_cli.md) is performed, the volume is rec
### Tiff ### Tiff
Reconstruction can be saved as tiff files by specifying `file_format = tiff` in the configuration `[output]` section. Reconstruction can be saved as tiff files by specifying `file_format = tiff` in the configuration `[output]` section.
In the current version (2020.3), tiff support has the following limitations: Mind that tiff support currently has the following limitations:
- One file per slice - One file per slice
- Data is saved as `float32` data type, no normalization - Data is saved as `float32` data type, no normalization
- No metadata (configuration used to obtain the reconstruction, date, version,...) - No metadata (configuration used to obtain the reconstruction, date, version,...)
...@@ -126,7 +149,7 @@ In the current version (2020.3), tiff support has the following limitations: ...@@ -126,7 +149,7 @@ In the current version (2020.3), tiff support has the following limitations:
### Jpeg2000 ### Jpeg2000
Reconstruction can be saved as jpeg2000 files by specifying `file_format = jpeg2000` in the configuration `[output]` section. Reconstruction can be saved as jpeg2000 files by specifying `file_format = jpeg2000` in the configuration `[output]` section.
In the current version (2020.3), jpeg2000 support has the following limitations: Mind that jpeg2000 support currently has the following limitations:
- When exporting to `uint16` data type (mandatory for jpeg2000), the normalization from `float32` to `uint16` is done slice-wise instead of volume-wise. This is slightly less accurate. - When exporting to `uint16` data type (mandatory for jpeg2000), the normalization from `float32` to `uint16` is done slice-wise instead of volume-wise. This is slightly less accurate.
- Only lossless compression is supported. In the future, compression will be tunable through Nabu configuration. - Only lossless compression is supported. In the future, compression will be tunable through Nabu configuration.
- No metadata (configuration used to obtain the reconstruction, date, version,...) - No metadata (configuration used to obtain the reconstruction, date, version,...)
...@@ -138,6 +161,14 @@ In the current version (2020.3), jpeg2000 support has the following limitations: ...@@ -138,6 +161,14 @@ In the current version (2020.3), jpeg2000 support has the following limitations:
Nabu provides a method to find the half-shift between two images. The center of axial vertical rotation is obtained when the fist image is a radiography at the rotation angle 0 and the second image is given by the radiography at the rotation angle 180 after flipping the image horizontally. The rotation axis position is the center of the image plus the found shift. Nabu provides a method to find the half-shift between two images. The center of axial vertical rotation is obtained when the fist image is a radiography at the rotation angle 0 and the second image is given by the radiography at the rotation angle 180 after flipping the image horizontally. The rotation axis position is the center of the image plus the found shift.
Configuration file: section `[reconstruction]`, key `rotation_axis_position`. Values can be:
- A number (known CoR)
- Empty: the CoR is set to the middle of the detector
- `centered` (or `auto`): this searches for a CoR around the center, but does not work for half-acquisition
- `global`: new method, should return a CoR in any setting.
API: [CenterOfRotation](apidoc/nabu.preproc.alignment.rst#nabu.preproc.alignment.CenterOfRotation) API: [CenterOfRotation](apidoc/nabu.preproc.alignment.rst#nabu.preproc.alignment.CenterOfRotation)
### Detector Translation Along the Beam ### Detector Translation Along the Beam
......
...@@ -16,6 +16,7 @@ Quick links: ...@@ -16,6 +16,7 @@ Quick links:
Latest news: Latest news:
* October 2020: `Version 2020.4.0 released <v2020_4_0.md>`_
* August 2020: `Version 2020.3.0 released <v2020_3_0.md>`_ * August 2020: `Version 2020.3.0 released <v2020_3_0.md>`_
* June 2020: `Version 2020.2.0 released <v2020_2_0.md>`_ * June 2020: `Version 2020.2.0 released <v2020_2_0.md>`_
...@@ -82,6 +83,7 @@ Release history ...@@ -82,6 +83,7 @@ Release history
.. toctree:: .. toctree::
:maxdepth: 3 :maxdepth: 3
v2020_4_0.md
v2020_3_0.md v2020_3_0.md
v2020_2_0.md v2020_2_0.md
......
...@@ -14,18 +14,8 @@ pip install git+https://gitlab.esrf.fr/tomotools/nabu.git ...@@ -14,18 +14,8 @@ pip install git+https://gitlab.esrf.fr/tomotools/nabu.git
The above `pip install` command should automatically install the nabu dependencies. The above `pip install` command should automatically install the nabu dependencies.
For the record, the mandatory dependencies are the following:
- numpy
- pytest
- psutil
- [silx](http://www.silx.org/)
- [tomoscan](https://gitlab.esrf.fr/tomotools/tomoscan)
All of them can be installed with `pip`.
There are optional dependencides enabling various features: There are optional dependencides enabling various features:
- Computations acceleration (GPU/manycore CPU): `pycuda`, `pyopencl` - Computations acceleration (GPU/manycore CPU): `pycuda`, `pyopencl`
- Computations distribution: `distributed`, `dask_jobqueue` - Computations distribution: `distributed`, `dask_jobqueue`
Please note that Nabu supports Python >= 3.5. Please note that Nabu supports Python >= 3.5.
...@@ -52,6 +52,7 @@ Each section describe a usual processing step. In the current version, the avail ...@@ -52,6 +52,7 @@ Each section describe a usual processing step. In the current version, the avail
- `preproc`: phase retrieval, CCD corrections, ... - `preproc`: phase retrieval, CCD corrections, ...
- `phase`: phase retrieval - `phase`: phase retrieval
- `reconstruction`: tomography reconstruction - `reconstruction`: tomography reconstruction
- `postproc`: post-processing: histogram
- `output`: output data description - `output`: output data description
- `resources`: computing resources description - `resources`: computing resources description
- `about`: extra information about the current configuration file - `about`: extra information about the current configuration file
...@@ -71,6 +72,6 @@ Yes. Nabu is foremost a library, meaning that all its component can be accessed ...@@ -71,6 +72,6 @@ Yes. Nabu is foremost a library, meaning that all its component can be accessed
### Compatibility policy ### Compatibility policy
At this early development stage, it is not entirely clear which keys should be in the configuration file. In the future, some keys might be removed, added, or have their name changed. Also, with new features coming, new keys/sections might appear in the configuration file. During the development of Nabu, some features will be added, leading to new keys in the configuration file. Besides, some keys might be renamed or even deleted if deemed necessary.
Thus, the configuration file might change according to the version of nabu. For this reason, there is a section `[about]` in the configuration file, which defines a `nabu_version` and a `nabu_config_version`. A given version of the nabu software (`nabu_version`) will be compatible with a certain range of nabu configuration files (`nabu_config_version`). In any case, **a configuration file from an "old" version of nabu will be supported by newer versions** for some time, with a deprecation warning when using obsolete keys.
\ No newline at end of file
# Version 2020.4.0
Version 2020.4.0 adds a number of features, notably command line tools for manipulating datasets.
``` note:: The changelog is available at https://gitlab.esrf.fr/tomotools/nabu/-/blob/master/CHANGELOG.md
```
## Highlights
This section highlights some of the available [features](features.md).
### Fully automatic center of rotation estimation
Various methods exist for estimating automatically the center of rotation (CoR). Nabu had one, but it did not work for half tomography acquisitions. Another method is now available and should work with any kind of acquisition.
The paramater is `rotation_axis_position` (in section`[reconstruction]`), the value can be:
- A number (known CoR)
- Empty: the CoR is set to the middle of the detector
- `centered` (was `auto`): this searches for a CoR around the center, but does not work for half-acquisition
- `global`: new method, should return a CoR in any setting.
### Histograms
Nabu can now compute a histogram of the reconstructed volume. This histogram can serve for further volume processing (conversion to uint16 for example). The computation is done while the data is still in memory to avoid extraneous disk reads/writes.
The option is available in a new section `[postproc]` with the parameter `output_histogram = 1`.
A command line tool is also available to merge the histogram of multiple volumes: `nabu-histogram <file1> <file2> ... <output>`.
### Use darks/flats from another dataset
Sometimes, flats cannot be acquired properly during an acquisition. In order to still have the ability to reconstruct the data, a common workaround is to use flats from another dataset.
Suppose you want to reconstruct a dataset which does not have flats/darks. In this case, nabu will detect that these images are missing and won't do flat-field correction. However you can force the flat-field normalization with `flatfield_enabled = forced` in the `[preproc]` section, and by providing `processes_file = /path/to/nabu_processes.h5`. This `nabu_processes.h5` file (or `tomwer_processes.h5`) can be generated either by tomwer or nabu.
### Sinogram normalization
A sinogram normalization is now available in the section `[preproc]` : `sino_normalization = chebyshev`. If enabled, each line of the sinogram undergoes a baseline removal. This can be useful to correct a "refill" taking place during the scan. See [this discussion](https://gitlab.esrf.fr/tomotools/nabu/-/issues/118#note_74284) for more details.
### Split a NXTomo file by "z series"
Z-series is the name given to a multi-stage scan, i.e scanning a volume in several stages by moving the sample vertically after each stage (this is different from helical tomography).
For the time being, the CLI tool `nxtomomill` merges everything in a single file.
Projections have a varying "z" identified by the `entry/sample/z_translation` key (in conformity to the Nexus-Tomo standard).
The command line tool `nabu-zsplit` enables to split a file into distinct "z" to reconstruct each volume individually.
``` warning:: This is a temporary solution. This will be natively be handled by nxtomomill in the future.
```
\ No newline at end of file
__version__ = "2020.3.1" __version__ = "2020.4.0"
__nabu_modules__ = [ __nabu_modules__ = [
"app", "app",
"cuda", "cuda",
......
...@@ -358,12 +358,13 @@ class FullFieldPipeline: ...@@ -358,12 +358,13 @@ class FullFieldPipeline:
@use_options("double_flatfield", "double_flatfield") @use_options("double_flatfield", "double_flatfield")
def _init_double_flatfield(self): def _init_double_flatfield(self):
options = self.processing_options["double_flatfield"] options = self.processing_options["double_flatfield"]
avg_is_on_log = (options["sigma"] is not None)
self.double_flatfield = self.DoubleFlatFieldClass( self.double_flatfield = self.DoubleFlatFieldClass(
self._get_shape("double_flatfield"), self._get_shape("double_flatfield"),
result_url=None, result_url=None,
input_is_mlog=False, input_is_mlog=False,
output_is_mlog=False, output_is_mlog=False,
average_is_on_log=False, average_is_on_log=avg_is_on_log,
sigma_filter=options["sigma"] sigma_filter=options["sigma"]
) )
...@@ -395,7 +396,7 @@ class FullFieldPipeline: ...@@ -395,7 +396,7 @@ class FullFieldPipeline:
margin=margin, margin=margin,
) )
if "unsharp_mask" not in self.processing_steps: if "unsharp_mask" not in self.processing_steps:
self.register_callback("phase", self._reshape_radios_after_phase) self.register_callback("phase", FullFieldPipeline._reshape_radios_after_phase)
@use_options("unsharp_mask", "unsharp_mask") @use_options("unsharp_mask", "unsharp_mask")
def _init_unsharp(self): def _init_unsharp(self):
...@@ -405,7 +406,7 @@ class FullFieldPipeline: ...@@ -405,7 +406,7 @@ class FullFieldPipeline:
options["unsharp_sigma"], options["unsharp_coeff"], options["unsharp_sigma"], options["unsharp_coeff"],
mode="reflect", method="gaussian" mode="reflect", method="gaussian"
) )
self.register_callback("unsharp_mask", self._reshape_radios_after_phase) self.register_callback("unsharp_mask", FullFieldPipeline._reshape_radios_after_phase)
@use_options("take_log", "mlog") @use_options("take_log", "mlog")
def _init_mlog(self): def _init_mlog(self):
...@@ -458,6 +459,7 @@ class FullFieldPipeline: ...@@ -458,6 +459,7 @@ class FullFieldPipeline:
self._rec_roi = (x_s, x_e, y_s, y_e) self._rec_roi = (x_s, x_e, y_s, y_e)
self._allocate_recs(y_e - y_s, x_e - x_s) self._allocate_recs(y_e - y_s, x_e - x_s)
@use_options("reconstruction", "reconstruction") @use_options("reconstruction", "reconstruction")
def _init_reconstruction(self): def _init_reconstruction(self):
options = self.processing_options["reconstruction"] options = self.processing_options["reconstruction"]
...@@ -471,6 +473,8 @@ class FullFieldPipeline: ...@@ -471,6 +473,8 @@ class FullFieldPipeline:
rot_center = options["rotation_axis_position"] rot_center = options["rotation_axis_position"]
if options.get("cor_estimated_auto", False): if options.get("cor_estimated_auto", False):
self.logger.info("Estimated center of rotation: %.2f" % rot_center) self.logger.info("Estimated center of rotation: %.2f" % rot_center)
if self.sino_builder._halftomo_flip:
rot_center = self.sino_builder.rot_center
self.reconstruction = self.FBPClass( self.reconstruction = self.FBPClass(
self._get_shape("reconstruction"), self._get_shape("reconstruction"),
...@@ -484,7 +488,7 @@ class FullFieldPipeline: ...@@ -484,7 +488,7 @@ class FullFieldPipeline:
"axis_correction": options["axis_correction"], "axis_correction": options["axis_correction"],
} }
) )
if options["fbp_filter_type"] == "none": if options["fbp_filter_type"] is None:
self.reconstruction.fbp = self.reconstruction.backproj self.reconstruction.fbp = self.reconstruction.backproj
@use_options("histogram", "histogram") @use_options("histogram", "histogram")
......
...@@ -91,11 +91,11 @@ class CudaFullFieldPipeline(FullFieldPipeline): ...@@ -91,11 +91,11 @@ class CudaFullFieldPipeline(FullFieldPipeline):
def _register_callbacks(self): def _register_callbacks(self):
self.register_callback("read_chunk", self._read_data_callback) self.register_callback("read_chunk", CudaFullFieldPipeline._read_data_callback)
if self.reconstruction is not None: if self.reconstruction is not None:
self.register_callback("reconstruction", self._rec_callback) self.register_callback("reconstruction", CudaFullFieldPipeline._rec_callback)
if self.writer is not None: if self.writer is not None:
self.register_callback("save", self._saving_callback) self.register_callback("save", CudaFullFieldPipeline._saving_callback)
......
...@@ -33,7 +33,7 @@ def pipeline_step(step_attr, step_desc): ...@@ -33,7 +33,7 @@ def pipeline_step(step_attr, step_desc):
self.logger.debug("End " + step_desc) self.logger.debug("End " + step_desc)
callback = self._callbacks.get(self._steps_component2name[step_attr], None) callback = self._callbacks.get(self._steps_component2name[step_attr], None)
if callback is not None: if callback is not None:
callback() callback(self)
return res return res
return wrapper return wrapper
return decorator return decorator
......
...@@ -32,18 +32,18 @@ class MedianFilter(CudaProcessing): ...@@ -32,18 +32,18 @@ class MedianFilter(CudaProcessing):
Size of the median filter, in the format (y, x). Size of the median filter, in the format (y, x).
mode: str mode: str
Boundary handling mode. Available modes are: Boundary handling mode. Available modes are:
"reflect": cba|abcd|dcb - "reflect": cba|abcd|dcb
"nearest": aaa|abcd|ddd - "nearest": aaa|abcd|ddd
"wrap": bcd|abcd|abc - "wrap": bcd|abcd|abc
"constant": 000|abcd|000 - "constant": 000|abcd|000
Default is "reflect". Default is "reflect".
threshold: float, optional threshold: float, optional
Threshold for the "thresholded median filter". Threshold for the "thresholded median filter".
A thresholded median filter only replaces a pixel value by the median A thresholded median filter only replaces a pixel value by the median
if this pixel value is greater or equal than median + threshold. if this pixel value is greater or equal than median + threshold.
Cuda Parameters Notes
--------------- ------
Please refer to the documentation of the CudaProcessing class for Please refer to the documentation of the CudaProcessing class for
the other parameters. the other parameters.
""" """
......
...@@ -86,3 +86,20 @@ __global__ void nlog(float* array, int Nx, int Ny, int Nz, float clip_min, float ...@@ -86,3 +86,20 @@ __global__ void nlog(float* array, int Nx, int Ny, int Nz, float clip_min, float
#endif #endif
array[pos] = -logf(val); array[pos] = -logf(val);
} }
// Reverse elements of a 2D array along "x", i.e:
// arr = arr[:, ::-1]
// launched with grid (Nx/2, Ny)
__global__ void reverse2D_x(float* array, int Nx, int Ny) {
uint x = blockDim.x * blockIdx.x + threadIdx.x;
uint y = blockDim.y * blockIdx.y + threadIdx.y;
if ((x >= Nx/2) || (y >= Ny)) return;
uint pos = y*Nx + x;
uint pos2 = y*Nx + (Nx - 1 - x);
float tmp = array[pos];
array[pos] = array[pos2];
array[pos2] = tmp;
}
...@@ -105,6 +105,11 @@ def get_first_hdf5_entry(fname): ...@@ -105,6 +105,11 @@ def get_first_hdf5_entry(fname):
return entry return entry
def hdf5_entry_exists(fname, entry):
with HDF5File(fname, "r") as fid:
res = fid.get(entry, None) is not None
return res
def get_h5_value(fname, h5_path, default_ret=None): def get_h5_value(fname, h5_path, default_ret=None):
with HDF5File(fname, "r") as fid: with HDF5File(fname, "r") as fid:
try: try:
......
...@@ -63,8 +63,12 @@ class CudaDoubleFlatField(DoubleFlatField, CudaProcessing): ...@@ -63,8 +63,12 @@ class CudaDoubleFlatField(DoubleFlatField, CudaProcessing):
return o return o
@staticmethod @staticmethod
def _proc_mlog(x, o): def _proc_mlog(x, o, min_clip=None):
cumath.log(x, out=o) if min_clip is not None:
garray.maximum(x, min_clip, output=o)
cumath.log(o, out=o)
else:
cumath.log(x, out=o)
o *= -1 o *= -1
return o return o
......
...@@ -71,7 +71,15 @@ class SinoProcessing(object): ...@@ -71,7 +71,15 @@ class SinoProcessing(object):
) )
self.rot_center = rot_center self.rot_center = rot_center
self._rot_center_int = int(round(self.rot_center)) self._rot_center_int = int(round(self.rot_center))
# If CoR is on the left: "flip" the logic
self._halftomo_flip = False
rc = self._rot_center_int
if rc < (self.n_x - 1)/2:
rc = self.n_x - 1 - rc
self._rot_center_int = rc
self.rot_center = self.n_x - self.rot_center
self._halftomo_flip = True
#
def _set_halftomo(self, halftomo): def _set_halftomo(self, halftomo):
self.halftomo = halftomo self.halftomo = halftomo
...@@ -123,7 +131,12 @@ class SinoProcessing(object): ...@@ -123,7 +131,12 @@ class SinoProcessing(object):
else: else:
sinos = np.zeros(self.sinos_halftomo_shape, dtype=np.float32) sinos = np.zeros(self.sinos_halftomo_shape, dtype=np.float32)
for i in range(n_z): for i in range(n_z):
sinos[i][:] = convert_halftomo(radios[:, i, :], self._rot_center_int) radio = radios[:, i, :]
if self._halftomo_flip:
radio = radio[:, ::-1]
sinos[i][:] = convert_halftomo(radio, self._rot_center_int)
if self._halftomo_flip:
sinos[i][:] = sinos[i][:, ::-1]
return sinos return sinos
......
...@@ -42,6 +42,20 @@ class CudaSinoProcessing(SinoProcessing, CudaProcessing): ...@@ -42,6 +42,20 @@ class CudaSinoProcessing(SinoProcessing, CudaProcessing):
d = self.n_x - rc # will have to be adapted for varying axis pos d = self.n_x - rc # will have to be adapted for varying axis pos
self.halftomo_weights = np.linspace(0, 1, d, endpoint=True, dtype="f") self.halftomo_weights = np.linspace(0, 1, d, endpoint=True, dtype="f")
self.d_halftomo_weights = garray.to_gpu(self.halftomo_weights) self.d_halftomo_weights = garray.to_gpu(self.halftomo_weights)
if self._halftomo_flip:
self.xflip_kernel = CudaKernel(
"reverse2D_x",
get_cuda_srcfile("ElementOp.cu"),
signature="Pii"
)
blk = (32, 32, 1)
self._xflip_blksize = blk
self._xflip_gridsize_1 = (
updiv(self.n_x, blk[0]),
updiv(self.n_angles, blk[1]),
1
)
self._xflip_gridsize_2 = self._halftomo_gridsize