nabu 2024.1.10__py3-none-any.whl → 2024.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nabu/__init__.py +1 -1
- nabu/app/bootstrap.py +2 -3
- nabu/app/cast_volume.py +4 -2
- nabu/app/cli_configs.py +5 -0
- nabu/app/composite_cor.py +1 -1
- nabu/app/create_distortion_map_from_poly.py +5 -6
- nabu/app/diag_to_pix.py +7 -19
- nabu/app/diag_to_rot.py +14 -29
- nabu/app/double_flatfield.py +32 -44
- nabu/app/parse_reconstruction_log.py +3 -0
- nabu/app/reconstruct.py +53 -15
- nabu/app/reconstruct_helical.py +2 -2
- nabu/app/stitching.py +27 -13
- nabu/app/tests/__init__.py +0 -0
- nabu/app/tests/test_reduce_dark_flat.py +4 -1
- nabu/cuda/kernel.py +11 -2
- nabu/cuda/processing.py +2 -2
- nabu/cuda/src/cone.cu +77 -0
- nabu/cuda/src/hierarchical_backproj.cu +271 -0
- nabu/cuda/utils.py +0 -6
- nabu/estimation/alignment.py +5 -19
- nabu/estimation/cor.py +173 -599
- nabu/estimation/cor_sino.py +356 -26
- nabu/estimation/focus.py +63 -11
- nabu/estimation/tests/test_cor.py +124 -58
- nabu/estimation/tests/test_focus.py +6 -6
- nabu/estimation/tilt.py +2 -1
- nabu/estimation/utils.py +5 -33
- nabu/io/__init__.py +1 -1
- nabu/io/cast_volume.py +1 -1
- nabu/io/reader.py +416 -21
- nabu/io/tests/test_readers.py +422 -0
- nabu/io/tests/test_writers.py +1 -102
- nabu/io/writer.py +4 -433
- nabu/opencl/kernel.py +14 -3
- nabu/opencl/processing.py +8 -0
- nabu/pipeline/config_validators.py +5 -2
- nabu/pipeline/datadump.py +12 -5
- nabu/pipeline/estimators.py +162 -188
- nabu/pipeline/fullfield/chunked.py +168 -92
- nabu/pipeline/fullfield/chunked_cuda.py +7 -3
- nabu/pipeline/fullfield/computations.py +2 -7
- nabu/pipeline/fullfield/dataset_validator.py +0 -4
- nabu/pipeline/fullfield/nabu_config.py +37 -13
- nabu/pipeline/fullfield/processconfig.py +22 -13
- nabu/pipeline/fullfield/reconstruction.py +13 -9
- nabu/pipeline/helical/helical_chunked_regridded.py +1 -1
- nabu/pipeline/helical/helical_chunked_regridded_cuda.py +1 -0
- nabu/pipeline/helical/helical_reconstruction.py +1 -1
- nabu/pipeline/params.py +21 -1
- nabu/pipeline/processconfig.py +1 -12
- nabu/pipeline/reader.py +146 -0
- nabu/pipeline/tests/test_estimators.py +44 -72
- nabu/pipeline/utils.py +4 -2
- nabu/pipeline/writer.py +10 -2
- nabu/preproc/ccd_cuda.py +1 -1
- nabu/preproc/ctf.py +14 -7
- nabu/preproc/ctf_cuda.py +2 -3
- nabu/preproc/double_flatfield.py +5 -12
- nabu/preproc/double_flatfield_cuda.py +2 -2
- nabu/preproc/flatfield.py +5 -1
- nabu/preproc/flatfield_cuda.py +5 -1
- nabu/preproc/phase.py +24 -73
- nabu/preproc/phase_cuda.py +5 -8
- nabu/preproc/tests/test_ctf.py +11 -7
- nabu/preproc/tests/test_flatfield.py +67 -122
- nabu/preproc/tests/test_paganin.py +54 -30
- nabu/processing/azim.py +206 -0
- nabu/processing/convolution_cuda.py +1 -1
- nabu/processing/fft_cuda.py +15 -17
- nabu/processing/histogram.py +2 -0
- nabu/processing/histogram_cuda.py +2 -1
- nabu/processing/kernel_base.py +3 -0
- nabu/processing/muladd_cuda.py +1 -0
- nabu/processing/padding_opencl.py +1 -1
- nabu/processing/roll_opencl.py +1 -0
- nabu/processing/rotation_cuda.py +2 -2
- nabu/processing/tests/test_fft.py +17 -10
- nabu/processing/unsharp_cuda.py +1 -1
- nabu/reconstruction/cone.py +104 -40
- nabu/reconstruction/fbp.py +3 -0
- nabu/reconstruction/fbp_base.py +7 -2
- nabu/reconstruction/filtering.py +20 -7
- nabu/reconstruction/filtering_cuda.py +7 -1
- nabu/reconstruction/hbp.py +424 -0
- nabu/reconstruction/mlem.py +99 -0
- nabu/reconstruction/reconstructor.py +2 -0
- nabu/reconstruction/rings_cuda.py +19 -19
- nabu/reconstruction/sinogram_cuda.py +1 -0
- nabu/reconstruction/sinogram_opencl.py +3 -1
- nabu/reconstruction/tests/test_cone.py +10 -5
- nabu/reconstruction/tests/test_deringer.py +7 -6
- nabu/reconstruction/tests/test_fbp.py +124 -10
- nabu/reconstruction/tests/test_filtering.py +13 -11
- nabu/reconstruction/tests/test_halftomo.py +30 -4
- nabu/reconstruction/tests/test_mlem.py +91 -0
- nabu/reconstruction/tests/test_reconstructor.py +8 -3
- nabu/resources/dataset_analyzer.py +142 -92
- nabu/resources/gpu.py +1 -0
- nabu/resources/nxflatfield.py +134 -125
- nabu/resources/templates/id16a_fluo.conf +42 -0
- nabu/resources/tests/test_extract.py +10 -0
- nabu/resources/tests/test_nxflatfield.py +2 -2
- nabu/stitching/alignment.py +80 -24
- nabu/stitching/config.py +105 -68
- nabu/stitching/definitions.py +1 -0
- nabu/stitching/frame_composition.py +68 -60
- nabu/stitching/overlap.py +91 -51
- nabu/stitching/single_axis_stitching.py +32 -0
- nabu/stitching/slurm_utils.py +6 -6
- nabu/stitching/stitcher/__init__.py +0 -0
- nabu/stitching/stitcher/base.py +124 -0
- nabu/stitching/stitcher/dumper/__init__.py +3 -0
- nabu/stitching/stitcher/dumper/base.py +94 -0
- nabu/stitching/stitcher/dumper/postprocessing.py +356 -0
- nabu/stitching/stitcher/dumper/preprocessing.py +60 -0
- nabu/stitching/stitcher/post_processing.py +555 -0
- nabu/stitching/stitcher/pre_processing.py +1068 -0
- nabu/stitching/stitcher/single_axis.py +484 -0
- nabu/stitching/stitcher/stitcher.py +0 -0
- nabu/stitching/stitcher/y_stitcher.py +13 -0
- nabu/stitching/stitcher/z_stitcher.py +45 -0
- nabu/stitching/stitcher_2D.py +278 -0
- nabu/stitching/tests/test_config.py +12 -37
- nabu/stitching/tests/test_frame_composition.py +33 -59
- nabu/stitching/tests/test_overlap.py +149 -7
- nabu/stitching/tests/test_utils.py +1 -1
- nabu/stitching/tests/test_y_preprocessing_stitching.py +132 -0
- nabu/stitching/tests/{test_z_stitching.py → test_z_postprocessing_stitching.py} +167 -561
- nabu/stitching/tests/test_z_preprocessing_stitching.py +431 -0
- nabu/stitching/utils/__init__.py +1 -0
- nabu/stitching/utils/post_processing.py +281 -0
- nabu/stitching/utils/tests/test_post-processing.py +21 -0
- nabu/stitching/{utils.py → utils/utils.py} +79 -52
- nabu/stitching/y_stitching.py +27 -0
- nabu/stitching/z_stitching.py +32 -2281
- nabu/testutils.py +1 -152
- nabu/thirdparty/tomocupy_remove_stripe.py +43 -9
- nabu/utils.py +158 -61
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/METADATA +24 -17
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/RECORD +145 -121
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/WHEEL +1 -1
- nabu/io/tiffwriter_zmm.py +0 -99
- nabu/pipeline/fallback_utils.py +0 -149
- nabu/pipeline/helical/tests/test_accumulator.py +0 -158
- nabu/pipeline/helical/tests/test_pipeline_elements_full.py +0 -355
- nabu/pipeline/helical/tests/test_strategy.py +0 -61
- nabu/pipeline/helical/utils.py +0 -51
- nabu/pipeline/tests/test_chunk_reader.py +0 -74
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/LICENSE +0 -0
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/entry_points.txt +0 -0
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/top_level.txt +0 -0
nabu/pipeline/fallback_utils.py
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
""" This module is meant to contain classes which are in the process of being superseed by new classes depending on recent packages
|
2
|
-
with fast development cycles in order to be able to fall-back in two cases :
|
3
|
-
-- the new packages, or one of their parts, break from one version to another.
|
4
|
-
-- For parts of Nabu which need some extra time to adapt.
|
5
|
-
"""
|
6
|
-
from ..resources.logger import LoggerOrPrint
|
7
|
-
from ..utils import check_supported
|
8
|
-
from ..io.writer import Writers, LegacyNXProcessWriter
|
9
|
-
from ..resources.utils import is_hdf5_extension
|
10
|
-
from os import path, mkdir
|
11
|
-
from .params import files_formats
|
12
|
-
|
13
|
-
|
14
|
-
class WriterConfigurator:
|
15
|
-
"""No dependency on tomoscan for this class. The new class would be WriterManager which depend on tomoscan."""
|
16
|
-
|
17
|
-
_overwrite_warned = False
|
18
|
-
|
19
|
-
def __init__(
|
20
|
-
self,
|
21
|
-
output_dir,
|
22
|
-
file_prefix,
|
23
|
-
file_format="hdf5",
|
24
|
-
overwrite=False,
|
25
|
-
start_index=None,
|
26
|
-
logger=None,
|
27
|
-
nx_info=None,
|
28
|
-
write_histogram=False,
|
29
|
-
histogram_entry="entry",
|
30
|
-
writer_options=None,
|
31
|
-
extra_options=None,
|
32
|
-
):
|
33
|
-
"""
|
34
|
-
Create a Writer from a set of parameters.
|
35
|
-
|
36
|
-
Parameters
|
37
|
-
----------
|
38
|
-
output_dir: str
|
39
|
-
Directory where the file(s) will be written.
|
40
|
-
file_prefix: str
|
41
|
-
File prefix (without leading path)
|
42
|
-
start_index: int, optional
|
43
|
-
Index to start the files numbering (filename_0123.ext).
|
44
|
-
Default is 0.
|
45
|
-
Ignored for HDF5 extension.
|
46
|
-
logger: nabu.resources.logger.Logger, optional
|
47
|
-
Logger object
|
48
|
-
nx_info: dict, optional
|
49
|
-
Dictionary containing the nexus information.
|
50
|
-
write_histogram: bool, optional
|
51
|
-
Whether to also write a histogram of data. If set to True, it will configure
|
52
|
-
an additional "writer".
|
53
|
-
histogram_entry: str, optional
|
54
|
-
Name of the HDF5 entry for the output histogram file, if write_histogram is True.
|
55
|
-
Ignored if the output format is already HDF5 : in this case, nx_info["entry"] is taken.
|
56
|
-
writer_options: dict, optional
|
57
|
-
Other advanced options to pass to Writer class.
|
58
|
-
"""
|
59
|
-
self.logger = LoggerOrPrint(logger)
|
60
|
-
self.start_index = start_index
|
61
|
-
self.write_histogram = write_histogram
|
62
|
-
self.overwrite = overwrite
|
63
|
-
writer_options = writer_options or {}
|
64
|
-
self.extra_options = extra_options or {}
|
65
|
-
|
66
|
-
check_supported(file_format, list(Writers.keys()), "output file format")
|
67
|
-
|
68
|
-
self._set_output_dir(output_dir)
|
69
|
-
self._set_file_name(file_prefix, file_format)
|
70
|
-
|
71
|
-
# Init Writer
|
72
|
-
writer_cls = Writers[file_format]
|
73
|
-
writer_args = [self.fname]
|
74
|
-
writer_kwargs = self._get_initial_writer_kwarg()
|
75
|
-
self._writer_exec_args = []
|
76
|
-
self._writer_exec_kwargs = {}
|
77
|
-
self._is_hdf5_output = is_hdf5_extension(file_format)
|
78
|
-
|
79
|
-
if self._is_hdf5_output:
|
80
|
-
writer_kwargs["entry"] = nx_info["entry"]
|
81
|
-
writer_kwargs["filemode"] = "a"
|
82
|
-
writer_kwargs["overwrite"] = overwrite
|
83
|
-
self._writer_exec_args.append(nx_info["process_name"])
|
84
|
-
self._writer_exec_kwargs["processing_index"] = nx_info["processing_index"]
|
85
|
-
self._writer_exec_kwargs["config"] = nx_info["config"]
|
86
|
-
else:
|
87
|
-
writer_kwargs["start_index"] = self.start_index
|
88
|
-
if writer_options.get("tiff_single_file", False) and "tif" in file_format:
|
89
|
-
do_append = writer_options.get("single_tiff_initialized", False)
|
90
|
-
writer_kwargs.update({"multiframe": True, "append": do_append})
|
91
|
-
|
92
|
-
if files_formats.get(file_format, None) == "jp2":
|
93
|
-
cratios = self.extra_options.get("jpeg2000_compression_ratio", None)
|
94
|
-
if cratios is not None:
|
95
|
-
cratios = [cratios]
|
96
|
-
writer_kwargs["cratios"] = cratios
|
97
|
-
writer_kwargs["float_clip_values"] = self.extra_options.get("float_clip_values", None)
|
98
|
-
self.writer = writer_cls(*writer_args, **writer_kwargs)
|
99
|
-
|
100
|
-
if self.write_histogram and not (self._is_hdf5_output):
|
101
|
-
self._init_separate_histogram_writer(histogram_entry)
|
102
|
-
|
103
|
-
def _get_initial_writer_kwarg(self):
|
104
|
-
return {}
|
105
|
-
|
106
|
-
def _set_output_dir(self, output_dir):
|
107
|
-
self.output_dir = output_dir
|
108
|
-
if path.exists(self.output_dir):
|
109
|
-
if not (path.isdir(self.output_dir)):
|
110
|
-
raise ValueError(
|
111
|
-
"Unable to create directory %s: already exists and is not a directory" % self.output_dir
|
112
|
-
)
|
113
|
-
else:
|
114
|
-
self.logger.debug("Creating directory %s" % self.output_dir)
|
115
|
-
mkdir(self.output_dir)
|
116
|
-
|
117
|
-
def _set_file_name(self, file_prefix, file_format):
|
118
|
-
self.file_prefix = file_prefix
|
119
|
-
self.file_format = file_format
|
120
|
-
self.fname = path.join(self.output_dir, file_prefix + "." + file_format)
|
121
|
-
if path.exists(self.fname):
|
122
|
-
err = "File already exists: %s" % self.fname
|
123
|
-
if self.overwrite:
|
124
|
-
if not (WriterConfigurator._overwrite_warned):
|
125
|
-
self.logger.warning(err + ". It will be overwritten as requested in configuration")
|
126
|
-
WriterConfigurator._overwrite_warned = True
|
127
|
-
else:
|
128
|
-
self.logger.fatal(err)
|
129
|
-
raise ValueError(err)
|
130
|
-
|
131
|
-
def _init_separate_histogram_writer(self, hist_entry):
|
132
|
-
hist_fname = path.join(self.output_dir, "histogram_%06d.hdf5" % self.start_index)
|
133
|
-
self.histogram_writer = LegacyNXProcessWriter(
|
134
|
-
hist_fname,
|
135
|
-
entry=hist_entry,
|
136
|
-
filemode="w",
|
137
|
-
overwrite=True,
|
138
|
-
)
|
139
|
-
|
140
|
-
def get_histogram_writer(self):
|
141
|
-
if not (self.write_histogram):
|
142
|
-
return None
|
143
|
-
if self._is_hdf5_output:
|
144
|
-
return self.writer
|
145
|
-
else:
|
146
|
-
return self.histogram_writer
|
147
|
-
|
148
|
-
def write_data(self, data):
|
149
|
-
self.writer.write(data, *self._writer_exec_args, **self._writer_exec_kwargs)
|
@@ -1,158 +0,0 @@
|
|
1
|
-
from nabu.pipeline.helical import gridded_accumulator, span_strategy
|
2
|
-
from nabu.testutils import get_data, __do_long_tests__
|
3
|
-
|
4
|
-
import pytest
|
5
|
-
import numpy as np
|
6
|
-
import os
|
7
|
-
import h5py
|
8
|
-
|
9
|
-
|
10
|
-
@pytest.fixture(scope="class")
|
11
|
-
def bootstrap(request):
|
12
|
-
cls = request.cls
|
13
|
-
|
14
|
-
# This is a helical dataset derived
|
15
|
-
# from "crayon" dataset, using 5 slices and covering 2.5 x 360 angular span
|
16
|
-
# in halftomo, with vertical translations.
|
17
|
-
|
18
|
-
helical_dataset = get_data("small_sparse_helical_dataset.npz")
|
19
|
-
|
20
|
-
helical_dataset = dict([item for item in helical_dataset.items()])
|
21
|
-
# the radios, in the dataset file, are stored by swapping angular and x dimension
|
22
|
-
# so that the fast running dimension runs over the projections.
|
23
|
-
# Due to the sparsity of the dataset, where only an handful of slices
|
24
|
-
# has signal, this gives a much better compression even when the axis is translating
|
25
|
-
# vertically
|
26
|
-
helical_dataset["radios"] = np.array(np.swapaxes(helical_dataset["radios_transposed"], 0, 2))
|
27
|
-
del helical_dataset["radios_transposed"]
|
28
|
-
|
29
|
-
# adding members: radios, dark, flats, z_pix_per_proj, x_pix_per_proj, projection_angles_deg, pixel_size_mm, phase_margin_pix,
|
30
|
-
# weigth_field=weigth_field, double_flat
|
31
|
-
dataset_keys = [
|
32
|
-
"dark",
|
33
|
-
"flats",
|
34
|
-
"z_pix_per_proj",
|
35
|
-
"x_pix_per_proj",
|
36
|
-
"projection_angles_deg",
|
37
|
-
"pixel_size_mm",
|
38
|
-
"phase_margin_pix",
|
39
|
-
"weights_field",
|
40
|
-
"double_flat",
|
41
|
-
"rotation_axis_position",
|
42
|
-
"detector_shape_vh",
|
43
|
-
"result_inset",
|
44
|
-
"radios",
|
45
|
-
]
|
46
|
-
for key in dataset_keys:
|
47
|
-
setattr(cls, key, helical_dataset[key])
|
48
|
-
|
49
|
-
cls.rtol_regridded = 1.0e-6
|
50
|
-
|
51
|
-
|
52
|
-
@pytest.mark.skipif(not (__do_long_tests__), reason="need environment variable NABU_LONG_TESTS=1")
|
53
|
-
@pytest.mark.usefixtures("bootstrap")
|
54
|
-
class TestGriddedAccumulator:
|
55
|
-
"""
|
56
|
-
Test the GriddedAccumulator.
|
57
|
-
Rebuilds the sinogram for some selected slices of the crayon dataset
|
58
|
-
"""
|
59
|
-
|
60
|
-
def test_regridding(self):
|
61
|
-
span_info = span_strategy.SpanStrategy(
|
62
|
-
z_pix_per_proj=self.z_pix_per_proj,
|
63
|
-
x_pix_per_proj=self.x_pix_per_proj,
|
64
|
-
detector_shape_vh=self.detector_shape_vh,
|
65
|
-
phase_margin_pix=self.phase_margin_pix,
|
66
|
-
projection_angles_deg=self.projection_angles_deg,
|
67
|
-
require_redundancy=True,
|
68
|
-
pixel_size_mm=self.pixel_size_mm,
|
69
|
-
logger=None,
|
70
|
-
)
|
71
|
-
|
72
|
-
# I would like to reconstruct from feaseable height 15 to feaseable height 18
|
73
|
-
# relatively to the first doable slice in the vertical translation direction
|
74
|
-
# I get the heights in the detector frame of the first and of the last
|
75
|
-
|
76
|
-
reconstruction_space = gridded_accumulator.get_reconstruction_space(
|
77
|
-
span_info=span_info, min_scanwise_z=15, end_scanwise_z=18, phase_margin_pix=self.phase_margin_pix
|
78
|
-
)
|
79
|
-
|
80
|
-
chunk_info = span_info.get_chunk_info((reconstruction_space.my_z_min, reconstruction_space.my_z_end))
|
81
|
-
|
82
|
-
sub_region = (
|
83
|
-
reconstruction_space.my_z_min - self.phase_margin_pix,
|
84
|
-
reconstruction_space.my_z_end + self.phase_margin_pix,
|
85
|
-
)
|
86
|
-
|
87
|
-
## useful projections
|
88
|
-
proj_num_start, proj_num_end = chunk_info.angle_index_span
|
89
|
-
|
90
|
-
# the first of the chunk angular range
|
91
|
-
my_first_pnum = proj_num_start
|
92
|
-
|
93
|
-
self.accumulator = gridded_accumulator.GriddedAccumulator(
|
94
|
-
gridded_radios=reconstruction_space.gridded_radios,
|
95
|
-
gridded_weights=reconstruction_space.gridded_cumulated_weights,
|
96
|
-
diagnostic_radios=reconstruction_space.diagnostic_radios,
|
97
|
-
diagnostic_weights=reconstruction_space.diagnostic_weights,
|
98
|
-
diagnostic_angles=reconstruction_space.diagnostic_proj_angle,
|
99
|
-
diagnostic_searched_angles_rad_clipped=reconstruction_space.diagnostic_searched_angles_rad_clipped,
|
100
|
-
diagnostic_zpix_transl=reconstruction_space.diagnostic_zpix_transl,
|
101
|
-
dark=self.dark,
|
102
|
-
flat_indexes=[0, 7501],
|
103
|
-
flats=self.flats,
|
104
|
-
weights=self.weights_field,
|
105
|
-
double_flat=self.double_flat,
|
106
|
-
diag_zpro_run=0,
|
107
|
-
)
|
108
|
-
|
109
|
-
# splitting in sub ranges of 100 projections
|
110
|
-
n_granularity = 100
|
111
|
-
pnum_start_list = list(np.arange(proj_num_start, proj_num_end, n_granularity))
|
112
|
-
pnum_end_list = pnum_start_list[1:] + [proj_num_end]
|
113
|
-
|
114
|
-
for pnum_start, pnum_end in zip(pnum_start_list, pnum_end_list):
|
115
|
-
start_in_chunk = pnum_start - my_first_pnum
|
116
|
-
end_in_chunk = pnum_end - my_first_pnum
|
117
|
-
|
118
|
-
self._read_data_and_apply_flats(
|
119
|
-
slice(pnum_start, pnum_end), slice(start_in_chunk, end_in_chunk), chunk_info, sub_region, span_info
|
120
|
-
)
|
121
|
-
|
122
|
-
res = reconstruction_space.gridded_radios / reconstruction_space.gridded_cumulated_weights
|
123
|
-
|
124
|
-
# check only a sub part to avoid further increasing of the file on edna site
|
125
|
-
errmax = np.max(np.abs(res[:200, 1, -500:] - self.result_inset) / np.max(res))
|
126
|
-
assert errmax < self.rtol_regridded, "Max error is too high"
|
127
|
-
|
128
|
-
# uncomment this to see
|
129
|
-
# h5py.File("processed_sinogram.h5","w")["sinogram"] = res
|
130
|
-
|
131
|
-
def _read_data_and_apply_flats(self, sub_total_prange_slice, subchunk_slice, chunk_info, sub_region, span_info):
|
132
|
-
my_integer_shifts_v = chunk_info.integer_shift_v[subchunk_slice]
|
133
|
-
fract_complement_shifts_v = chunk_info.fract_complement_to_integer_shift_v[subchunk_slice]
|
134
|
-
x_shifts_list = chunk_info.x_pix_per_proj[subchunk_slice]
|
135
|
-
subr_start_z, subr_end_z = sub_region
|
136
|
-
|
137
|
-
subr_start_z_list = subr_start_z - my_integer_shifts_v
|
138
|
-
subr_end_z_list = subr_end_z - my_integer_shifts_v + 1
|
139
|
-
|
140
|
-
dtasrc_start_z = max(0, subr_start_z_list.min())
|
141
|
-
dtasrc_end_z = min(span_info.detector_shape_vh[0], subr_end_z_list.max())
|
142
|
-
|
143
|
-
data_raw = self.radios[sub_total_prange_slice, slice(dtasrc_start_z, dtasrc_end_z), :]
|
144
|
-
|
145
|
-
subsampling_file_slice = sub_total_prange_slice
|
146
|
-
|
147
|
-
# my_subsampled_indexes = self.chunk_reader._sorted_files_indices[subsampling_file_slice]
|
148
|
-
my_subsampled_indexes = (np.arange(10000))[subsampling_file_slice]
|
149
|
-
|
150
|
-
self.accumulator.extract_preprocess_with_flats(
|
151
|
-
subchunk_slice,
|
152
|
-
my_subsampled_indexes,
|
153
|
-
chunk_info,
|
154
|
-
np.array((subr_start_z, subr_end_z), "i"),
|
155
|
-
np.array((dtasrc_start_z, dtasrc_end_z), "i"),
|
156
|
-
data_raw,
|
157
|
-
sub_total_prange_slice,
|
158
|
-
)
|
@@ -1,355 +0,0 @@
|
|
1
|
-
from nabu.pipeline.helical import gridded_accumulator, span_strategy
|
2
|
-
from nabu.testutils import get_data, __do_long_tests__
|
3
|
-
import os
|
4
|
-
import numpy as np
|
5
|
-
import pytest
|
6
|
-
from nabu.preproc.ccd import Log, CCDFilter
|
7
|
-
from nabu.preproc.phase import PaganinPhaseRetrieval
|
8
|
-
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
9
|
-
from nabu.pipeline.helical import gridded_accumulator, span_strategy
|
10
|
-
from nabu.pipeline.helical.weight_balancer import WeightBalancer
|
11
|
-
from nabu.pipeline.helical.helical_utils import find_mirror_indexes
|
12
|
-
|
13
|
-
from nabu.cuda.utils import get_cuda_context, __has_pycuda__, __pycuda_error_msg__, replace_array_memory
|
14
|
-
|
15
|
-
if __has_pycuda__:
|
16
|
-
import pycuda.gpuarray as garray
|
17
|
-
from nabu.pipeline.helical.fbp import BackprojectorHelical as FBPClass
|
18
|
-
|
19
|
-
|
20
|
-
@pytest.fixture(scope="class")
|
21
|
-
def bootstrap(request):
|
22
|
-
cls = request.cls
|
23
|
-
|
24
|
-
# This is a helical dataset derived
|
25
|
-
# from "crayon" dataset, using 5 slices and covering 2.5 x 360 angular span
|
26
|
-
# in halftomo, with vertical translations.
|
27
|
-
|
28
|
-
# >>> d=load("small_sparse_helical_dataset.npz")
|
29
|
-
# dd=dict( d.items() )
|
30
|
-
# >>> dd["median_clip_threshold"] = 0.04
|
31
|
-
# >>> savez("retouched_test.npz",**dd)
|
32
|
-
|
33
|
-
helical_dataset = get_data("small_sparse_helical_dataset.npz")
|
34
|
-
|
35
|
-
helical_dataset = dict(list(helical_dataset.items()))
|
36
|
-
# the radios, in the dataset file, are stored by swapping angular and x dimension
|
37
|
-
# so that the fast running dimension runs over the projections.
|
38
|
-
# Due to the sparsity of the dataset, where only an handful of slices
|
39
|
-
# has signal, this gives a much better compression even when the axis is translating
|
40
|
-
# vertically
|
41
|
-
helical_dataset["radios"] = np.array(np.swapaxes(helical_dataset["radios_transposed"], 0, 2))
|
42
|
-
del helical_dataset["radios_transposed"]
|
43
|
-
|
44
|
-
# adding members: radios, dark, flats, z_pix_per_proj, x_pix_per_proj, projection_angles_deg,
|
45
|
-
# pixel_size_mm, phase_margin_pix,
|
46
|
-
# weigth_field=weigth_field, double_flat
|
47
|
-
dataset_keys = [
|
48
|
-
"dark",
|
49
|
-
"flats",
|
50
|
-
"z_pix_per_proj",
|
51
|
-
"x_pix_per_proj",
|
52
|
-
"projection_angles_deg",
|
53
|
-
"pixel_size_mm",
|
54
|
-
"phase_margin_pix",
|
55
|
-
"weights_field",
|
56
|
-
"double_flat",
|
57
|
-
"rotation_axis_position",
|
58
|
-
"detector_shape_vh",
|
59
|
-
"result_inset",
|
60
|
-
"radios",
|
61
|
-
# further parameters, added on top of the test data which was originally made for gridded_accumulator
|
62
|
-
"median_clip_threshold",
|
63
|
-
"distance_m",
|
64
|
-
"energy_kev",
|
65
|
-
"delta_beta",
|
66
|
-
"pixel_size_m",
|
67
|
-
"padding_type",
|
68
|
-
"phase_margin_for_pag",
|
69
|
-
"rec_reference",
|
70
|
-
"ref_tol",
|
71
|
-
"ref_start",
|
72
|
-
"ref_end",
|
73
|
-
]
|
74
|
-
# the test dataset is the original one from the accumulator test
|
75
|
-
# plus some patched metadeta information for phase retrieval.
|
76
|
-
# The original dataset had phase_margin_pix and had 3 usefule slices.
|
77
|
-
# Here, to test phase retrieval, we redefine the phase margin to the maximum
|
78
|
-
# that we can do with such a small dataset
|
79
|
-
helical_dataset["phase_margin_pix"] = helical_dataset["phase_margin_for_pag"]
|
80
|
-
|
81
|
-
for key in dataset_keys:
|
82
|
-
setattr(cls, key, helical_dataset[key])
|
83
|
-
|
84
|
-
cls.padding_type = str(cls.padding_type)
|
85
|
-
cls.rotation_axis_position = float(cls.rotation_axis_position)
|
86
|
-
cls.rtol_regridded = 1.0e-6
|
87
|
-
|
88
|
-
cls.projection_angles_rad = np.rad2deg(cls.projection_angles_deg)
|
89
|
-
|
90
|
-
|
91
|
-
@pytest.mark.skipif(not (__do_long_tests__), reason="need environment variable NABU_LONG_TESTS=1")
|
92
|
-
@pytest.mark.skipif(not (__has_pycuda__), reason="Needs pycuda for this test")
|
93
|
-
@pytest.mark.usefixtures("bootstrap")
|
94
|
-
class TestGriddedAccumulator:
|
95
|
-
"""
|
96
|
-
Test the GriddedAccumulator.
|
97
|
-
Rebuilds the sinogram for some selected slices of the crayon dataset
|
98
|
-
"""
|
99
|
-
|
100
|
-
def test_regridding(self):
|
101
|
-
span_info = span_strategy.SpanStrategy(
|
102
|
-
z_pix_per_proj=self.z_pix_per_proj,
|
103
|
-
x_pix_per_proj=self.x_pix_per_proj,
|
104
|
-
detector_shape_vh=self.detector_shape_vh,
|
105
|
-
phase_margin_pix=self.phase_margin_pix,
|
106
|
-
projection_angles_deg=self.projection_angles_deg,
|
107
|
-
require_redundancy=True,
|
108
|
-
pixel_size_mm=self.pixel_size_mm,
|
109
|
-
logger=None,
|
110
|
-
)
|
111
|
-
|
112
|
-
# I would like to reconstruct from feaseable height 15 to feaseable height 18
|
113
|
-
# relatively to the first doable slice in the vertical translation direction
|
114
|
-
# I get the heights in the detector frame of the first and of the last
|
115
|
-
|
116
|
-
self.reconstruction_space = gridded_accumulator.get_reconstruction_space(
|
117
|
-
span_info=span_info, min_scanwise_z=15, end_scanwise_z=18, phase_margin_pix=self.phase_margin_pix
|
118
|
-
)
|
119
|
-
|
120
|
-
chunk_info = span_info.get_chunk_info((self.reconstruction_space.my_z_min, self.reconstruction_space.my_z_end))
|
121
|
-
|
122
|
-
sub_region = (
|
123
|
-
self.reconstruction_space.my_z_min - self.phase_margin_pix,
|
124
|
-
self.reconstruction_space.my_z_end + self.phase_margin_pix,
|
125
|
-
)
|
126
|
-
|
127
|
-
# useful projections
|
128
|
-
proj_num_start, proj_num_end = chunk_info.angle_index_span
|
129
|
-
|
130
|
-
# the first of the chunk angular range
|
131
|
-
my_first_pnum = proj_num_start
|
132
|
-
|
133
|
-
self.accumulator = gridded_accumulator.GriddedAccumulator(
|
134
|
-
gridded_radios=self.reconstruction_space.gridded_radios,
|
135
|
-
gridded_weights=self.reconstruction_space.gridded_cumulated_weights,
|
136
|
-
diagnostic_radios=self.reconstruction_space.diagnostic_radios,
|
137
|
-
diagnostic_weights=self.reconstruction_space.diagnostic_weights,
|
138
|
-
diagnostic_angles=self.reconstruction_space.diagnostic_proj_angle,
|
139
|
-
dark=self.dark,
|
140
|
-
flat_indexes=[0, 7501],
|
141
|
-
flats=self.flats,
|
142
|
-
weights=self.weights_field,
|
143
|
-
double_flat=self.double_flat,
|
144
|
-
)
|
145
|
-
|
146
|
-
# splitting in sub ranges of 100 projections
|
147
|
-
n_granularity = 100
|
148
|
-
pnum_start_list = list(np.arange(proj_num_start, proj_num_end, n_granularity))
|
149
|
-
pnum_end_list = pnum_start_list[1:] + [proj_num_end]
|
150
|
-
|
151
|
-
for pnum_start, pnum_end in zip(pnum_start_list, pnum_end_list):
|
152
|
-
start_in_chunk = pnum_start - my_first_pnum
|
153
|
-
end_in_chunk = pnum_end - my_first_pnum
|
154
|
-
|
155
|
-
self._read_data_and_apply_flats(
|
156
|
-
slice(pnum_start, pnum_end), slice(start_in_chunk, end_in_chunk), chunk_info, sub_region, span_info
|
157
|
-
)
|
158
|
-
|
159
|
-
res_flatfielded = self.reconstruction_space.gridded_radios / self.reconstruction_space.gridded_cumulated_weights
|
160
|
-
|
161
|
-
# but in real pipeline the radio_shape is obtained from the pipeline get_shape utility method
|
162
|
-
self._init_ccd_corrections(res_flatfielded.shape[1:])
|
163
|
-
|
164
|
-
# but in the actual pipeline the argument is not given, and the processed stack is the one internally
|
165
|
-
# kept by the pipeline object ( self.gridded_radios in the pipeline )
|
166
|
-
self._ccd_corrections(res_flatfielded)
|
167
|
-
|
168
|
-
self._init_phase(res_flatfielded.shape[1:])
|
169
|
-
processed_radios = self._retrieve_phase(res_flatfielded)
|
170
|
-
|
171
|
-
self._init_mlog(processed_radios.shape)
|
172
|
-
self._take_log(processed_radios)
|
173
|
-
|
174
|
-
top_margin = -self.phase_margin_pix if self.phase_margin_pix else None
|
175
|
-
processed_weights = self.reconstruction_space.gridded_cumulated_weights[
|
176
|
-
:, self.phase_margin_pix : top_margin, :
|
177
|
-
]
|
178
|
-
|
179
|
-
self._init_weight_balancer()
|
180
|
-
self._balance_weights(processed_weights)
|
181
|
-
|
182
|
-
self._init_reconstructor(processed_radios.shape)
|
183
|
-
|
184
|
-
i_slice = 0
|
185
|
-
|
186
|
-
self.d_radios_slim.set(processed_radios[:, i_slice, :])
|
187
|
-
self._filter()
|
188
|
-
|
189
|
-
self._apply_weights(i_slice, processed_weights)
|
190
|
-
|
191
|
-
res = self._reconstruct()
|
192
|
-
|
193
|
-
test_slicing = slice(self.ref_start, self.ref_end)
|
194
|
-
tested_inset = res[test_slicing, test_slicing]
|
195
|
-
assert np.max(np.abs(tested_inset - self.rec_reference)) < self.ref_tol
|
196
|
-
|
197
|
-
# uncomment the four following lines to get the slice image
|
198
|
-
# import fabio
|
199
|
-
# edf = fabio.edfimage.edfimage()
|
200
|
-
# edf.data = res
|
201
|
-
# edf.write("reconstructed_slice.edf")
|
202
|
-
|
203
|
-
# put the test here
|
204
|
-
|
205
|
-
def _reconstruct(self):
|
206
|
-
axis_corrections = np.zeros_like(self.reconstruction_space.gridded_angles_rad)
|
207
|
-
self.reconstruction.set_custom_angles_and_axis_corrections(
|
208
|
-
self.reconstruction_space.gridded_angles_rad, axis_corrections
|
209
|
-
)
|
210
|
-
|
211
|
-
self.reconstruction.backprojection(self.d_radios_slim, output=self.d_rec_res)
|
212
|
-
|
213
|
-
self.d_rec_res.get(self.rec_res)
|
214
|
-
|
215
|
-
return self.rec_res
|
216
|
-
|
217
|
-
def _apply_weights(self, i_slice, weights):
|
218
|
-
"""d_radios_slim is on gpu"""
|
219
|
-
n_provided_angles = self.d_radios_slim.shape[0]
|
220
|
-
|
221
|
-
for first_angle_index in range(0, n_provided_angles, self.num_weight_radios_per_app):
|
222
|
-
end_angle_index = min(n_provided_angles, first_angle_index + self.num_weight_radios_per_app)
|
223
|
-
self._d_radios_weights[: end_angle_index - first_angle_index].set(
|
224
|
-
weights[first_angle_index:end_angle_index, i_slice]
|
225
|
-
)
|
226
|
-
self.d_radios_slim[first_angle_index:end_angle_index] *= self._d_radios_weights[
|
227
|
-
: end_angle_index - first_angle_index
|
228
|
-
]
|
229
|
-
|
230
|
-
def _filter(self):
|
231
|
-
self.mirror_angle_relative_indexes = find_mirror_indexes(self.reconstruction_space.gridded_angles_deg)
|
232
|
-
|
233
|
-
self.reconstruction.sino_filter.filter_sino(
|
234
|
-
self.d_radios_slim,
|
235
|
-
mirror_indexes=self.mirror_angle_relative_indexes,
|
236
|
-
rot_center=self.rotation_axis_position,
|
237
|
-
output=self.d_radios_slim,
|
238
|
-
)
|
239
|
-
|
240
|
-
def _init_reconstructor(self, processed_radios_shape):
|
241
|
-
one_slice_data_shape = processed_radios_shape[:1] + processed_radios_shape[2:]
|
242
|
-
|
243
|
-
self.d_radios_slim = garray.zeros(one_slice_data_shape, np.float32)
|
244
|
-
|
245
|
-
# let's make room for loading chunck of weights without necessarily doubling the memory footprint.
|
246
|
-
# The weights will be used to multiplied the d_radios_slim.
|
247
|
-
# We will proceed by bunches
|
248
|
-
self.num_weight_radios_per_app = 200
|
249
|
-
self._d_radios_weights = garray.zeros((self.num_weight_radios_per_app,) + one_slice_data_shape[1:], np.float32)
|
250
|
-
|
251
|
-
pixel_size_cm = self.pixel_size_m * 100
|
252
|
-
|
253
|
-
radio_size_h = processed_radios_shape[-1]
|
254
|
-
|
255
|
-
assert (
|
256
|
-
2 * self.rotation_axis_position > radio_size_h
|
257
|
-
), """The code of this test is adapted for HA on the right. This seems to be a case
|
258
|
-
of HA on the left because self.rotation_axis_position={self.rotation_axis_position} and radio_size_h = {radio_size_h}
|
259
|
-
"""
|
260
|
-
|
261
|
-
rec_dim = int(round(2 * self.rotation_axis_position))
|
262
|
-
|
263
|
-
self.reconstruction = FBPClass(
|
264
|
-
one_slice_data_shape,
|
265
|
-
angles=np.zeros(processed_radios_shape[0], "f"),
|
266
|
-
rot_center=self.rotation_axis_position,
|
267
|
-
filter_name=None,
|
268
|
-
slice_roi=(0, rec_dim, 0, rec_dim),
|
269
|
-
extra_options={
|
270
|
-
"scale_factor": 1.0 / pixel_size_cm,
|
271
|
-
"axis_correction": np.zeros(processed_radios_shape[0], "f"),
|
272
|
-
"padding_mode": "edge",
|
273
|
-
},
|
274
|
-
)
|
275
|
-
self.reconstruction.fbp = self.reconstruction.backproj
|
276
|
-
|
277
|
-
self.d_rec_res = garray.zeros((rec_dim, rec_dim), np.float32)
|
278
|
-
self.rec_res = np.zeros((rec_dim, rec_dim), np.float32)
|
279
|
-
|
280
|
-
def _init_weight_balancer(self):
|
281
|
-
self.weight_balancer = WeightBalancer(self.rotation_axis_position, self.reconstruction_space.gridded_angles_rad)
|
282
|
-
|
283
|
-
def _balance_weights(self, weights):
|
284
|
-
self.weight_balancer.balance_weights(weights)
|
285
|
-
|
286
|
-
def _retrieve_phase(self, radios):
|
287
|
-
processed_radios = np.zeros(
|
288
|
-
(radios.shape[0],) + (radios.shape[1] - 2 * self.phase_margin_pix,) + (radios.shape[2],), radios.dtype
|
289
|
-
)
|
290
|
-
for i in range(radios.shape[0]):
|
291
|
-
processed_radios[i] = self.phase_retrieval.apply_filter(radios[i])
|
292
|
-
return processed_radios
|
293
|
-
|
294
|
-
def _read_data_and_apply_flats(self, sub_total_prange_slice, subchunk_slice, chunk_info, sub_region, span_info):
|
295
|
-
my_integer_shifts_v = chunk_info.integer_shift_v[subchunk_slice]
|
296
|
-
|
297
|
-
subr_start_z, subr_end_z = sub_region
|
298
|
-
|
299
|
-
subr_start_z_list = subr_start_z - my_integer_shifts_v
|
300
|
-
subr_end_z_list = subr_end_z - my_integer_shifts_v + 1
|
301
|
-
|
302
|
-
dtasrc_start_z = max(0, subr_start_z_list.min())
|
303
|
-
dtasrc_end_z = min(span_info.detector_shape_vh[0], subr_end_z_list.max())
|
304
|
-
|
305
|
-
data_raw = self.radios[sub_total_prange_slice, slice(dtasrc_start_z, dtasrc_end_z), :]
|
306
|
-
|
307
|
-
subsampling_file_slice = sub_total_prange_slice
|
308
|
-
|
309
|
-
# my_subsampled_indexes = self.chunk_reader._sorted_files_indices[subsampling_file_slice]
|
310
|
-
my_subsampled_indexes = (np.arange(10000))[subsampling_file_slice]
|
311
|
-
|
312
|
-
self.accumulator.extract_preprocess_with_flats(
|
313
|
-
subchunk_slice,
|
314
|
-
my_subsampled_indexes,
|
315
|
-
chunk_info,
|
316
|
-
np.array((subr_start_z, subr_end_z), "i"),
|
317
|
-
np.array((dtasrc_start_z, dtasrc_end_z), "i"),
|
318
|
-
data_raw,
|
319
|
-
)
|
320
|
-
|
321
|
-
def _init_ccd_corrections(self, radio_shape):
|
322
|
-
# but in real pipeline the radio_shape is obtained from the pipeline get_shape utility method
|
323
|
-
self.ccd_correction = CCDFilter(radio_shape, median_clip_thresh=self.median_clip_threshold)
|
324
|
-
|
325
|
-
def _ccd_corrections(self, radios):
|
326
|
-
_tmp_radio = np.empty_like(radios[0])
|
327
|
-
for i in range(radios.shape[0]):
|
328
|
-
self.ccd_correction.median_clip_correction(radios[i], output=_tmp_radio)
|
329
|
-
radios[i][:] = _tmp_radio[:]
|
330
|
-
|
331
|
-
def _take_log(self, radios):
|
332
|
-
self.mlog.take_logarithm(radios)
|
333
|
-
|
334
|
-
def _init_mlog(self, radios_shape):
|
335
|
-
log_shape = radios_shape
|
336
|
-
clip_min = 1.0e-6
|
337
|
-
clip_max = 1.1
|
338
|
-
self.mlog = Log(log_shape, clip_min=clip_min, clip_max=clip_max)
|
339
|
-
|
340
|
-
def _init_phase(self, raw_shape):
|
341
|
-
self.phase_retrieval = PaganinPhaseRetrieval(
|
342
|
-
raw_shape,
|
343
|
-
distance=self.distance_m,
|
344
|
-
energy=self.energy_kev,
|
345
|
-
delta_beta=self.delta_beta,
|
346
|
-
pixel_size=self.pixel_size_m,
|
347
|
-
padding=self.padding_type,
|
348
|
-
margin=((self.phase_margin_pix,) * 2, (0, 0)),
|
349
|
-
use_R2C=True,
|
350
|
-
fftw_num_threads=True, # TODO tune in advanced params of nabu config file
|
351
|
-
)
|
352
|
-
if self.phase_retrieval.use_fftw:
|
353
|
-
self.logger.debug(
|
354
|
-
"PaganinPhaseRetrieval using FFTW with %d threads" % self.phase_retrieval.fftw.num_threads
|
355
|
-
)
|