nabu 2024.1.0rc1__py3-none-any.whl → 2024.1.0rc3__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.
- doc/doc_config.py +32 -0
- nabu/__init__.py +1 -1
- nabu/app/bootstrap_stitching.py +1 -1
- nabu/app/cli_configs.py +5 -0
- nabu/app/composite_cor.py +23 -10
- nabu/app/multicor.py +0 -1
- nabu/app/reduce_dark_flat.py +5 -2
- nabu/app/stitching.py +16 -2
- nabu/estimation/cor.py +169 -73
- nabu/estimation/tests/test_cor.py +27 -18
- nabu/io/writer.py +5 -3
- nabu/pipeline/estimators.py +72 -30
- nabu/pipeline/fullfield/chunked.py +5 -1
- nabu/pipeline/fullfield/computations.py +2 -2
- nabu/pipeline/fullfield/nabu_config.py +4 -4
- nabu/pipeline/helical/gridded_accumulator.py +22 -4
- nabu/pipeline/helical/helical_chunked_regridded.py +9 -4
- nabu/pipeline/helical/tests/test_accumulator.py +1 -0
- nabu/pipeline/params.py +3 -0
- nabu/pipeline/tests/test_estimators.py +9 -11
- nabu/pipeline/writer.py +1 -0
- nabu/stitching/config.py +2 -2
- nabu/stitching/z_stitching.py +61 -38
- {nabu-2024.1.0rc1.dist-info → nabu-2024.1.0rc3.dist-info}/METADATA +2 -1
- {nabu-2024.1.0rc1.dist-info → nabu-2024.1.0rc3.dist-info}/RECORD +29 -29
- nabu/app/tests/__init__.py +0 -0
- {nabu-2024.1.0rc1.dist-info → nabu-2024.1.0rc3.dist-info}/LICENSE +0 -0
- {nabu-2024.1.0rc1.dist-info → nabu-2024.1.0rc3.dist-info}/WHEEL +0 -0
- {nabu-2024.1.0rc1.dist-info → nabu-2024.1.0rc3.dist-info}/entry_points.txt +0 -0
- {nabu-2024.1.0rc1.dist-info → nabu-2024.1.0rc3.dist-info}/top_level.txt +0 -0
@@ -439,25 +439,34 @@ class TestCorOctaveAccurate:
|
|
439
439
|
assert np.isclose(self.cor_pos_abs_blc12781, cor_position, atol=self.abs_tol), message
|
440
440
|
|
441
441
|
|
442
|
-
@pytest.mark.usefixtures("bootstrap_cor_fourier")
|
442
|
+
@pytest.mark.usefixtures("bootstrap_cor_fourier", "bootstrap_cor_win")
|
443
443
|
class TestCorFourierAngle:
|
444
|
-
def
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
if img_2.shape[0] != img_1.shape[0]: # handle odd number of projections.
|
450
|
-
img_2 = img_2[:-1, :]
|
444
|
+
def test_sino_right_axis_with_near_pos(self):
|
445
|
+
sino1 = self.data_ha_sino[0, :, :]
|
446
|
+
sino2 = np.fliplr(self.data_ha_sino[1, :, :])
|
447
|
+
start_angle = np.pi / 4
|
448
|
+
angles = np.linspace(start_angle, start_angle + 2 * np.pi, 2 * sino1.shape[0])
|
451
449
|
|
452
|
-
cor_options = {"
|
450
|
+
cor_options = {"side": 740, "refine": True}
|
453
451
|
|
454
452
|
CoR_calc = CenterOfRotationFourierAngles(cor_options=cor_options)
|
455
|
-
cor_position = CoR_calc.find_shift(
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
)
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
453
|
+
cor_position = CoR_calc.find_shift(sino1, sino2, angles, side="right")
|
454
|
+
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
455
|
+
|
456
|
+
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_sn_pix
|
457
|
+
assert np.isclose(self.cor_ha_sn_pix, cor_position, atol=self.abs_tol * 3), message
|
458
|
+
|
459
|
+
def test_sino_right_axis_with_ignore(self):
|
460
|
+
sino1 = self.data_ha_sino[0, :, :]
|
461
|
+
sino2 = np.fliplr(self.data_ha_sino[1, :, :])
|
462
|
+
start_angle = np.pi / 4
|
463
|
+
angles = np.linspace(start_angle, start_angle + 2 * np.pi, 2 * sino1.shape[0])
|
464
|
+
|
465
|
+
cor_options = {"side": "ignore", "refine": True}
|
466
|
+
|
467
|
+
CoR_calc = CenterOfRotationFourierAngles(cor_options=cor_options)
|
468
|
+
cor_position = CoR_calc.find_shift(sino1, sino2, angles, side="right")
|
469
|
+
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
470
|
+
|
471
|
+
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_sn_pix
|
472
|
+
assert np.isclose(self.cor_ha_sn_pix, cor_position, atol=self.abs_tol * 3), message
|
nabu/io/writer.py
CHANGED
@@ -795,15 +795,15 @@ class HSTVolWriter(Writer):
|
|
795
795
|
super().__init__(fname)
|
796
796
|
self.append = append
|
797
797
|
self._vol_writer = RawVolume(fname, overwrite=True, append=append)
|
798
|
+
self._hst_metadata = kwargs.get("hst_metadata", {})
|
798
799
|
|
799
|
-
|
800
|
-
def generate_metadata(data, **kwargs):
|
800
|
+
def generate_metadata(self, data, **kwargs):
|
801
801
|
n_z, n_y, n_x = data.shape
|
802
802
|
metadata = {
|
803
803
|
"NUM_X": n_x,
|
804
804
|
"NUM_Y": n_y,
|
805
805
|
"NUM_Z": n_z,
|
806
|
-
"voxelSize":
|
806
|
+
"voxelSize": 40.0,
|
807
807
|
"BYTEORDER": "LOWBYTEFIRST",
|
808
808
|
"ValMin": kwargs.get("ValMin", 0.0),
|
809
809
|
"ValMax": kwargs.get("ValMin", 1.0),
|
@@ -812,6 +812,8 @@ class HSTVolWriter(Writer):
|
|
812
812
|
"S1": 0.0,
|
813
813
|
"S2": 0.0,
|
814
814
|
}
|
815
|
+
for key, default_val in metadata.items():
|
816
|
+
metadata[key] = kwargs.get(key, None) or self._hst_metadata.get(key, None) or default_val
|
815
817
|
return metadata
|
816
818
|
|
817
819
|
@staticmethod
|
nabu/pipeline/estimators.py
CHANGED
@@ -124,42 +124,72 @@ class CORFinderBase:
|
|
124
124
|
if isinstance(cor_options, dict):
|
125
125
|
self.cor_options.update(cor_options)
|
126
126
|
|
127
|
-
#
|
128
|
-
#
|
129
|
-
|
127
|
+
# tomotools internal meeting 07 feb 2024: Merge of options 'near_pos' and 'side'.
|
128
|
+
# See [minutes](https://gitlab.esrf.fr/tomotools/minutes/-/blob/master/minutes-20240207.md?ref_type=heads)
|
129
|
+
|
130
130
|
detector_width = self.dataset_info.radio_dims[0]
|
131
|
-
|
132
|
-
|
131
|
+
default_lookup_side = "right" if self.dataset_info.is_halftomo else "center"
|
132
|
+
near_init = self.cor_options.get("side", None)
|
133
|
+
|
134
|
+
if near_init is None:
|
135
|
+
near_init = default_lookup_side
|
133
136
|
|
134
|
-
if
|
137
|
+
if near_init == "from_file":
|
135
138
|
try:
|
136
139
|
near_pos = self.dataset_info.dataset_scanner.estimated_cor_frm_motor # relative pos in pixels
|
137
140
|
if isinstance(near_pos, Real):
|
141
|
+
# near_pos += detector_width // 2 # Field in NX is relative.
|
138
142
|
self.cor_options.update({"near_pos": int(near_pos)})
|
139
143
|
else:
|
140
|
-
|
144
|
+
near_init = default_lookup_side
|
141
145
|
except:
|
142
|
-
self.cor_options.update({"near_pos": "ignore"})
|
143
146
|
self.logger.warning(
|
144
147
|
"COR estimation from motor position absent from NX file. Global search is performed."
|
145
148
|
)
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
149
|
+
near_init = default_lookup_side
|
150
|
+
elif isinstance(near_init, Real):
|
151
|
+
self.cor_options.update({"near_pos": near_init})
|
152
|
+
near_init = "near" # ???
|
153
|
+
elif near_init == "near": # Legacy
|
154
|
+
if not isinstance(self.cor_options["near_pos"], Real):
|
155
|
+
self.logger.warning("Side option set to 'near' but no 'near_pos' option set.")
|
156
|
+
self.logger.warning("Set side to right if HA, center otherwise.")
|
157
|
+
near_init = default_lookup_side
|
158
|
+
elif near_init in ("left", "right", "center", "all"):
|
159
|
+
pass
|
160
|
+
else:
|
161
|
+
self.logger.warning(
|
162
|
+
f"COR option 'side' received {near_init} and should be either 'from_file' (default), 'left', 'right', 'center', 'near' or a number."
|
163
|
+
)
|
164
|
+
|
165
|
+
if isinstance(self.cor_options.get("near_pos", None), Real):
|
166
|
+
# Check validity of near_pos
|
167
|
+
if np.abs(self.cor_options["near_pos"]) > detector_width / 2:
|
151
168
|
self.logger.warning(
|
152
|
-
f"
|
169
|
+
f"Relative COR passed is greater than half the size of the detector. Did you enter a absolute COR position?"
|
153
170
|
)
|
154
171
|
self.logger.warning("Instead, the center of the detector is used.")
|
172
|
+
self.cor_options["near_pos"] = 0
|
155
173
|
|
156
|
-
# Set side
|
157
|
-
if self.cor_options["near_pos"] <
|
174
|
+
# Set side from near_pos if passed.
|
175
|
+
if self.cor_options["near_pos"] < 0.0:
|
158
176
|
self.cor_options.update({"side": "left"})
|
177
|
+
near_init = "left"
|
159
178
|
else:
|
160
179
|
self.cor_options.update({"side": "right"})
|
161
|
-
|
162
|
-
|
180
|
+
near_init = "right"
|
181
|
+
|
182
|
+
self.cor_options.update({"side": near_init})
|
183
|
+
|
184
|
+
# At this stage : side is set to one of left, right, center near.
|
185
|
+
# and near_pos to a numeric value.
|
186
|
+
|
187
|
+
# if isinstance(self.cor_options["near_pos"], Real):
|
188
|
+
# # estimated_cor_frm_motor value is supposed to be relative. Since the config documentation expects the "near_pos" options
|
189
|
+
# # to be given as an absolute COR estimate, a conversion is needed.
|
190
|
+
# self.cor_options["near_pos"] += detector_width // 2 # converted in absolute nb of pixels.
|
191
|
+
# if not (isinstance(self.cor_options["near_pos"], Real) or self.cor_options["near_pos"] == "ignore"):
|
192
|
+
# self.cor_options.update({"near_pos": "ignore"})
|
163
193
|
|
164
194
|
# At this stage, cor_options["near_pos"] is either
|
165
195
|
# - 'ignore':
|
@@ -168,11 +198,11 @@ class CORFinderBase:
|
|
168
198
|
cor_class = self.search_methods[method]["class"]
|
169
199
|
self.cor_finder = cor_class(logger=self.logger, cor_options=self.cor_options)
|
170
200
|
|
171
|
-
default_lookup_side = "right" if self.dataset_info.is_halftomo else "center"
|
172
201
|
lookup_side = self.cor_options.get("side", default_lookup_side)
|
202
|
+
|
203
|
+
# OctaveAccurate
|
173
204
|
if cor_class == CenterOfRotationOctaveAccurate:
|
174
205
|
lookup_side = "center"
|
175
|
-
|
176
206
|
angles = self.dataset_info.rotation_angles
|
177
207
|
|
178
208
|
self.cor_exec_args = []
|
@@ -185,6 +215,7 @@ class CORFinderBase:
|
|
185
215
|
self.cor_exec_args[0] = lookup_side
|
186
216
|
elif cor_class in (CenterOfRotationFourierAngles,):
|
187
217
|
self.cor_exec_args[0] = angles
|
218
|
+
self.cor_exec_args[1] = lookup_side
|
188
219
|
#
|
189
220
|
self.cor_exec_kwargs = update_func_kwargs(self.cor_finder.find_shift, self.cor_options)
|
190
221
|
|
@@ -297,7 +328,7 @@ class SinoCORFinder(CORFinderBase):
|
|
297
328
|
"sino-growing-window": {
|
298
329
|
"class": CenterOfRotationGrowingWindow,
|
299
330
|
},
|
300
|
-
"fourier-angles": {"class": CenterOfRotationFourierAngles, "default_args": [None]},
|
331
|
+
"fourier-angles": {"class": CenterOfRotationFourierAngles, "default_args": [None, "center"]},
|
301
332
|
}
|
302
333
|
|
303
334
|
def __init__(
|
@@ -413,7 +444,7 @@ class SinoCORFinder(CORFinderBase):
|
|
413
444
|
SinoCOREstimator = SinoCORFinder
|
414
445
|
|
415
446
|
|
416
|
-
class CompositeCORFinder:
|
447
|
+
class CompositeCORFinder(CORFinderBase):
|
417
448
|
"""
|
418
449
|
Class and method to prepare sinogram and calculate COR
|
419
450
|
The pseudo sinogram is built with shrinked radios taken every theta_interval degres
|
@@ -430,6 +461,11 @@ class CompositeCORFinder:
|
|
430
461
|
by several order of magnitude without modifing the final result
|
431
462
|
"""
|
432
463
|
|
464
|
+
search_methods = {
|
465
|
+
"composite-coarse-to-fine": {
|
466
|
+
"class": CenterOfRotation, # Hack. Not used. Everything is done in the find_cor() func.
|
467
|
+
}
|
468
|
+
}
|
433
469
|
_default_cor_options = {"low_pass": 0.4, "high_pass": 10, "side": "center", "near_pos": 0, "near_width": 20}
|
434
470
|
|
435
471
|
def __init__(
|
@@ -444,6 +480,9 @@ class CompositeCORFinder:
|
|
444
480
|
logger=None,
|
445
481
|
norm_order=1,
|
446
482
|
):
|
483
|
+
super().__init__(
|
484
|
+
"composite-coarse-to-fine", dataset_info, do_flatfield=True, cor_options=cor_options, logger=logger
|
485
|
+
)
|
447
486
|
if norm_order not in [1, 2]:
|
448
487
|
raise ValueError(
|
449
488
|
f""" the norm order (nom_order parameter) must be either 1 or 2. You passed {norm_order}
|
@@ -455,6 +494,12 @@ class CompositeCORFinder:
|
|
455
494
|
self.dataset_info = dataset_info
|
456
495
|
self.logger = LoggerOrPrint(logger)
|
457
496
|
|
497
|
+
self.sx, self.sy = self.dataset_info.radio_dims
|
498
|
+
|
499
|
+
default_cor_options = self._default_cor_options.copy()
|
500
|
+
default_cor_options.update(self.cor_options)
|
501
|
+
self.cor_options = default_cor_options
|
502
|
+
|
458
503
|
# the algorithm can work for angular ranges larger than 1.2*pi
|
459
504
|
# up to an arbitrarily number of turns as it is the case in helical scans
|
460
505
|
self.spike_threshold = spike_threshold
|
@@ -465,8 +510,6 @@ class CompositeCORFinder:
|
|
465
510
|
self.angle_min = self.unwrapped_rotation_angles.min()
|
466
511
|
self.angle_max = self.unwrapped_rotation_angles.max()
|
467
512
|
|
468
|
-
self.sx, self.sy = self.dataset_info.radio_dims
|
469
|
-
|
470
513
|
if (self.angle_max - self.angle_min) < 1.2 * np.pi:
|
471
514
|
useful_span = None
|
472
515
|
raise ValueError(
|
@@ -481,7 +524,7 @@ class CompositeCORFinder:
|
|
481
524
|
if useful_span < np.pi:
|
482
525
|
theta_interval = theta_interval * useful_span / np.pi
|
483
526
|
|
484
|
-
self._get_cor_options(cor_options)
|
527
|
+
# self._get_cor_options(cor_options)
|
485
528
|
|
486
529
|
self.take_log = take_log
|
487
530
|
self.ovs = oversampling
|
@@ -541,7 +584,6 @@ class CompositeCORFinder:
|
|
541
584
|
# initialize sinograms and radios arrays
|
542
585
|
self.sino = np.zeros([2 * self.nprobed * n_subsampling_y, (self.sx - 1) * self.ovs + 1], "f")
|
543
586
|
self._loaded = False
|
544
|
-
|
545
587
|
self.high_pass = self.cor_options["high_pass"]
|
546
588
|
img_filter = fourier_filters.get_bandpass_filter(
|
547
589
|
(self.sino.shape[0] // 2, self.sino.shape[1]),
|
@@ -742,10 +784,10 @@ class CompositeCORFinder:
|
|
742
784
|
if min_error == error:
|
743
785
|
best_overlap = z
|
744
786
|
best_error = min_error
|
745
|
-
self.logger.debug(
|
746
|
-
|
747
|
-
|
748
|
-
)
|
787
|
+
# self.logger.debug(
|
788
|
+
# "testing an overlap of %.2f pixels, actual best overlap is %.2f pixels over %d\r"
|
789
|
+
# % (z / self.ovs, best_overlap / self.ovs, ovsd_sx / self.ovs),
|
790
|
+
# )
|
749
791
|
|
750
792
|
offset = (ovsd_sx - best_overlap) / self.ovs / 2
|
751
793
|
cor_abs = (self.sx - 1) / 2 + offset
|
@@ -611,6 +611,7 @@ class ChunkedPipeline:
|
|
611
611
|
"float_clip_values": options["float_clip_values"],
|
612
612
|
"tiff_single_file": options.get("tiff_single_file", False),
|
613
613
|
"single_output_file_initialized": getattr(self.process_config, "single_output_file_initialized", False),
|
614
|
+
"raw_vol_metadata": {"voxelSize": self.dataset_info.pixel_size}, # legacy...
|
614
615
|
}
|
615
616
|
writer_extra_options.update(extra_options)
|
616
617
|
self.writer = WriterManager(
|
@@ -751,8 +752,11 @@ class ChunkedPipeline:
|
|
751
752
|
|
752
753
|
@pipeline_step("writer", "Saving data")
|
753
754
|
def _write_data(self, data=None):
|
754
|
-
if data is None:
|
755
|
+
if data is None and self.reconstruction is not None:
|
755
756
|
data = self.recs
|
757
|
+
if data is None:
|
758
|
+
self.logger.info("No data to write")
|
759
|
+
return
|
756
760
|
self.writer.write_data(data)
|
757
761
|
self.logger.info("Wrote %s" % self.writer.fname)
|
758
762
|
self._write_histogram()
|
@@ -230,7 +230,7 @@ def estimate_max_chunk_size(
|
|
230
230
|
break
|
231
231
|
if delta_a is not None and delta_a > process_config.n_angles():
|
232
232
|
break
|
233
|
-
if delta_z is not None and delta_z > process_config.radio_shape()[
|
233
|
+
if delta_z is not None and delta_z > process_config.radio_shape()[0]:
|
234
234
|
break
|
235
235
|
last_valid_delta_a, last_valid_delta_z = delta_a, delta_z
|
236
236
|
if pipeline_part == "radios":
|
@@ -242,7 +242,7 @@ def estimate_max_chunk_size(
|
|
242
242
|
|
243
243
|
if pipeline_part != "radios":
|
244
244
|
if mem / 1e9 < available_memory_GB:
|
245
|
-
res = min(delta_z, process_config.radio_shape()[
|
245
|
+
res = min(delta_z, process_config.radio_shape()[0])
|
246
246
|
else:
|
247
247
|
res = last_valid_delta_z
|
248
248
|
else:
|
@@ -77,7 +77,7 @@ nabu_config = {
|
|
77
77
|
"type": "advanced",
|
78
78
|
},
|
79
79
|
"normalize_srcurrent": {
|
80
|
-
"default": "
|
80
|
+
"default": "1",
|
81
81
|
"help": "Whether to normalize frames with Synchrotron Current. This can correct the effect of a beam refill not taken into account by flats.",
|
82
82
|
"validator": boolean_validator,
|
83
83
|
"type": "advanced",
|
@@ -259,13 +259,13 @@ nabu_config = {
|
|
259
259
|
},
|
260
260
|
"rotation_axis_position": {
|
261
261
|
"default": "sliding-window",
|
262
|
-
"help": "Rotation axis position. It can be a number or the name of an estimation method (empty value means the middle of the detector).\nThe following methods are available to find automatically the Center of Rotation (CoR):\n - centered : a fast and simple auto-CoR method. It only works when the CoR is not far from the middle of the detector. It does not work for half-tomography.\n - global : a slow but robust auto-CoR.\n - sliding-window : semi-automatically find the CoR with a sliding window. You have to specify on which side the CoR is (left, center, right). Please see the 'cor_options' parameter.\n - growing-window : automatically find the CoR with a sliding-and-growing window. You can tune the option with the parameter 'cor_options'.\n - sino-coarse-to-fine: Estimate CoR from sinogram. Only works for 360 degrees scans.\n - composite-coarse-to-fine: Estimate CoR from composite multi-angle images. Only works for 360 degrees scans.",
|
262
|
+
"help": "Rotation axis position. It can be a number or the name of an estimation method (empty value means the middle of the detector).\nThe following methods are available to find automatically the Center of Rotation (CoR):\n - centered : a fast and simple auto-CoR method. It only works when the CoR is not far from the middle of the detector. It does not work for half-tomography.\n - global : a slow but robust auto-CoR.\n - sliding-window : semi-automatically find the CoR with a sliding window. You have to specify on which side the CoR is (left, center, right). Please see the 'cor_options' parameter.\n - growing-window : automatically find the CoR with a sliding-and-growing window. You can tune the option with the parameter 'cor_options'.\n - sino-coarse-to-fine: Estimate CoR from sinogram. Only works for 360 degrees scans.\n - composite-coarse-to-fine: Estimate CoR from composite multi-angle images. Only works for 360 degrees scans.\n - fourier-angles: Estimate CoR from sino based on an angular correlation analysis. You can tune the option with the parameter 'cor_options'.\n - octave-accurate: Legacy from octave accurate COR estimation algorithm. It first estimates the COR with global fourier-based correlation, then refines this estimation with local correlation based on the variance of the difference patches. You can tune the option with the parameter 'cor_options'.",
|
263
263
|
"validator": cor_validator,
|
264
264
|
"type": "required",
|
265
265
|
},
|
266
266
|
"cor_options": {
|
267
|
-
"default": "",
|
268
|
-
"help": "Options for methods finding automatically the rotation axis position. The parameters are separated by commas and passed as 'name=value'.\nFor example: low_pass=1; high_pass=20
|
267
|
+
"default": "side='from_file'",
|
268
|
+
"help": "Options for methods finding automatically the rotation axis position. The parameters are separated by commas and passed as 'name=value'.\nFor example: low_pass=1; high_pass=20. Mind the semicolon separator (;) and the '' for string values that are strings.\nIf 'side' is set, it is expected to be either:\n - 'from_file' (to pick the value in the NX file.)\n - or an relative CoR position in pixels (if so, it overrides the value in the NX file), \n or any of 'left', 'center', 'right', 'all', 'near'.\n The default value for 'side' is 'from_file'.",
|
269
269
|
"validator": generic_options_validator,
|
270
270
|
"type": "advanced",
|
271
271
|
},
|
@@ -105,7 +105,14 @@ class GriddedAccumulator:
|
|
105
105
|
self.double_flat = double_flat
|
106
106
|
|
107
107
|
def extract_preprocess_with_flats(
|
108
|
-
self,
|
108
|
+
self,
|
109
|
+
subchunk_slice,
|
110
|
+
subchunk_file_indexes,
|
111
|
+
chunk_info,
|
112
|
+
subr_start_end,
|
113
|
+
dtasrc_start_end,
|
114
|
+
data_raw,
|
115
|
+
radios_angular_range_slicing,
|
109
116
|
):
|
110
117
|
"""
|
111
118
|
This functions is meant to be called providing, each time, a subset of the data
|
@@ -149,6 +156,13 @@ class GriddedAccumulator:
|
|
149
156
|
They indicated, vertically, the detector portion the data_raw data correspond to
|
150
157
|
data_raw: np.array 3D
|
151
158
|
the data which correspond to a limited detector stripe and a limited angular subset
|
159
|
+
radios_angular_range_slicing:
|
160
|
+
my_subsampled_indexes is important in order to compare the
|
161
|
+
radios positions with respect to the flat position, and these position
|
162
|
+
are given as the sequential acquisition number which counts everything ( flats, darks, radios )
|
163
|
+
Insteqd, in order to access array which spans only the radios, we need to have an idea of where we are.
|
164
|
+
this is provided by radios_angular_range_slicing which addresses the radios domain
|
165
|
+
|
152
166
|
"""
|
153
167
|
|
154
168
|
# the object below is going to containing some auxiliary variable that are use to reframe the data.
|
@@ -170,7 +184,9 @@ class GriddedAccumulator:
|
|
170
184
|
)
|
171
185
|
|
172
186
|
# extraction of the data
|
173
|
-
self._extract_preprocess_with_flats(
|
187
|
+
self._extract_preprocess_with_flats(
|
188
|
+
data_raw, reframing_infos, chunk_info, radios_subset, radios_angular_range_slicing
|
189
|
+
)
|
174
190
|
|
175
191
|
if self.weights is not None:
|
176
192
|
# ... and, if required, extraction of the associated weights
|
@@ -310,7 +326,9 @@ class GriddedAccumulator:
|
|
310
326
|
|
311
327
|
self.floating_subregion = None, None, floating_start_z, floating_end_z
|
312
328
|
|
313
|
-
def _extract_preprocess_with_flats(
|
329
|
+
def _extract_preprocess_with_flats(
|
330
|
+
self, data_raw, reframing_infos, chunk_info, output, radios_angular_range_slicing=None, it_is_weight=False
|
331
|
+
):
|
314
332
|
if not it_is_weight:
|
315
333
|
if self.dark is not None:
|
316
334
|
data_raw = data_raw - self.dark[reframing_infos.dtasrc_start_z : reframing_infos.dtasrc_end_z]
|
@@ -322,7 +340,7 @@ class GriddedAccumulator:
|
|
322
340
|
flat = flat - self.dark[reframing_infos.dtasrc_start_z : reframing_infos.dtasrc_end_z]
|
323
341
|
|
324
342
|
if self.radios_srcurrent is not None:
|
325
|
-
factor = self.nominal_current / self.radios_srcurrent[
|
343
|
+
factor = self.nominal_current / self.radios_srcurrent[radios_angular_range_slicing.start + i]
|
326
344
|
else:
|
327
345
|
factor = 1
|
328
346
|
|
@@ -995,19 +995,24 @@ class HelicalChunkedRegriddedPipeline:
|
|
995
995
|
|
996
996
|
self.chunk_reader.load_data(overwrite=True, sub_total_prange_slice=sub_total_prange_slice)
|
997
997
|
if self.chunk_reader.dataset_subsampling > 1:
|
998
|
-
|
998
|
+
radios_angular_range_slicing = self._expand_slice(sub_total_prange_slice)
|
999
999
|
else:
|
1000
|
-
|
1001
|
-
my_subsampled_indexes = self.chunk_reader._sorted_files_indices[
|
1000
|
+
radios_angular_range_slicing = sub_total_prange_slice
|
1001
|
+
my_subsampled_indexes = self.chunk_reader._sorted_files_indices[radios_angular_range_slicing]
|
1002
1002
|
data_raw = self.chunk_reader.data[: len(my_subsampled_indexes)]
|
1003
1003
|
|
1004
1004
|
self.regular_accumulator.extract_preprocess_with_flats(
|
1005
1005
|
subchunk_slice,
|
1006
|
-
my_subsampled_indexes,
|
1006
|
+
my_subsampled_indexes, # these are indexes pointing within the global domain sequence which is composed of darks flats radios
|
1007
1007
|
chunk_info,
|
1008
1008
|
np.array((subr_start_z, subr_end_z), "i"),
|
1009
1009
|
np.array((dtasrc_start_z, dtasrc_end_z), "i"),
|
1010
1010
|
data_raw,
|
1011
|
+
radios_angular_range_slicing # my_subsampled_indexes is important in order to compare the
|
1012
|
+
# radios positions with respect to the flat position, and these position
|
1013
|
+
# are given as the sequential acquisition number which counts everything ( flats, darks, radios )
|
1014
|
+
# Insteqd, in order to access array which spans only the radios, we need to have an idea of where we are.
|
1015
|
+
# this is provided by radios_angular_range_slicing which addresses the radios domain
|
1011
1016
|
)
|
1012
1017
|
|
1013
1018
|
def binning_expanded(self, region):
|
nabu/pipeline/params.py
CHANGED
@@ -119,6 +119,9 @@ cor_methods = {
|
|
119
119
|
"composite-coarse-to-fine": "composite-coarse-to-fine",
|
120
120
|
"near": "composite-coarse-to-fine",
|
121
121
|
"fourier-angles": "fourier-angles",
|
122
|
+
"fourier angles": "fourier-angles",
|
123
|
+
"fourier-angle": "fourier-angles",
|
124
|
+
"fourier angle": "fourier-angles",
|
122
125
|
"octave-accurate": "octave-accurate",
|
123
126
|
}
|
124
127
|
|
@@ -26,7 +26,7 @@ def bootstrap(request):
|
|
26
26
|
cls.cor_pix = 1321.625
|
27
27
|
cls.abs_tol = 0.0001
|
28
28
|
cls.dataset_info = HDF5DatasetAnalyzer(dataset_downloaded_path)
|
29
|
-
cls.cor_options = """side="near"; near_pos = 300.0; near_width = 20.0 """
|
29
|
+
cls.cor_options = extract_parameters("""side="near"; near_pos = 300.0; near_width = 20.0 """, sep=";")
|
30
30
|
|
31
31
|
|
32
32
|
@pytest.mark.skipif(not (__do_long_tests__), reason="Need NABU_LONG_TESTS=1 for this test")
|
@@ -75,13 +75,13 @@ class TestCorNearPos:
|
|
75
75
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
76
76
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
77
77
|
|
78
|
-
cor_options
|
78
|
+
cor_options.update({"side": "from_file"})
|
79
79
|
finder = CORFinder("sliding-window", self.ds_std, do_flatfield=True, cor_options=cor_options)
|
80
80
|
cor = finder.find_cor()
|
81
81
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
82
82
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
83
83
|
|
84
|
-
cor_options
|
84
|
+
cor_options.update({"side": "center"})
|
85
85
|
finder = CORFinder("sliding-window", self.ds_std, do_flatfield=True, cor_options=cor_options)
|
86
86
|
cor = finder.find_cor()
|
87
87
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
@@ -95,14 +95,13 @@ class TestCorNearPos:
|
|
95
95
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
96
96
|
|
97
97
|
# Checks that it works though no data in NX
|
98
|
-
cor_options
|
98
|
+
cor_options.update({"side": "from_file"})
|
99
99
|
finder = SinoCORFinder("fourier-angles", self.ds_std, do_flatfield=True, cor_options=cor_options)
|
100
100
|
cor = finder.find_cor()
|
101
101
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
102
102
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
103
103
|
|
104
|
-
cor_options
|
105
|
-
cor_options["side"] = "center"
|
104
|
+
cor_options.update({"side": "center"})
|
106
105
|
finder = SinoCORFinder("fourier-angles", self.ds_std, do_flatfield=True, cor_options=cor_options)
|
107
106
|
cor = finder.find_cor()
|
108
107
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
@@ -116,15 +115,14 @@ class TestCorNearPos:
|
|
116
115
|
print(message)
|
117
116
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
118
117
|
|
119
|
-
cor_options
|
118
|
+
cor_options.update({"side": "from_file"})
|
120
119
|
finder = CORFinder("sliding-window", self.ds_bliss, do_flatfield=True, cor_options=cor_options)
|
121
120
|
cor = finder.find_cor()
|
122
121
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
123
122
|
print(message)
|
124
123
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
125
124
|
|
126
|
-
cor_options
|
127
|
-
cor_options["side"] = "center"
|
125
|
+
cor_options.update({"side": "center"})
|
128
126
|
finder = CORFinder("sliding-window", self.ds_bliss, do_flatfield=True, cor_options=cor_options)
|
129
127
|
cor = finder.find_cor()
|
130
128
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
@@ -138,13 +136,13 @@ class TestCorNearPos:
|
|
138
136
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
139
137
|
|
140
138
|
# Checks that it works though no data in NX
|
141
|
-
cor_options
|
139
|
+
cor_options.update({"side": "from_file"})
|
142
140
|
finder = SinoCORFinder("fourier-angles", self.ds_bliss, do_flatfield=True, cor_options=cor_options)
|
143
141
|
cor = finder.find_cor()
|
144
142
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
145
143
|
assert np.isclose(self.true_cor, cor, atol=self.abs_tol), message
|
146
144
|
|
147
|
-
cor_options
|
145
|
+
cor_options.update({"side": "center"})
|
148
146
|
finder = SinoCORFinder("fourier-angles", self.ds_bliss, do_flatfield=True, cor_options=cor_options)
|
149
147
|
cor = finder.find_cor()
|
150
148
|
message = f"Computed CoR {cor} and expected CoR {self.true_cor} do not coincide. Near_pos options was set to {cor_options.get('near_pos',None)}."
|
nabu/pipeline/writer.py
CHANGED
@@ -136,6 +136,7 @@ class WriterManager:
|
|
136
136
|
),
|
137
137
|
"overwrite": self.overwrite,
|
138
138
|
"append": self.extra_options.get("single_output_file_initialized", False),
|
139
|
+
"hst_metadata": self.extra_options.get("raw_vol_metadata", {}),
|
139
140
|
}
|
140
141
|
else:
|
141
142
|
raise ValueError("Unsupported file format: %s" % self.file_format)
|
nabu/stitching/config.py
CHANGED
@@ -152,7 +152,7 @@ SLURM_OTHER_OPTIONS = "other_options"
|
|
152
152
|
|
153
153
|
SLURM_PREPROCESSING_COMMAND = "python_venv"
|
154
154
|
|
155
|
-
SLURM_MODULES_TO_LOADS = "
|
155
|
+
SLURM_MODULES_TO_LOADS = "modules"
|
156
156
|
|
157
157
|
SLURM_CLEAN_SCRIPTS = "clean_scripts"
|
158
158
|
|
@@ -450,7 +450,7 @@ class SlurmConfig:
|
|
450
450
|
n_cpu_per_task: int = 4
|
451
451
|
|
452
452
|
def __post_init__(self) -> None:
|
453
|
-
# make sure either '
|
453
|
+
# make sure either 'modules' or 'preprocessing_command' is provided
|
454
454
|
if len(self.modules_to_load) > 0 and self.preprocessing_command not in (None, ""):
|
455
455
|
raise ValueError(
|
456
456
|
f"Either modules {SLURM_MODULES_TO_LOADS} or preprocessing_command {SLURM_PREPROCESSING_COMMAND} can be provided. Not both."
|