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.
@@ -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 test_proj_center_axis_cen(self):
445
- detector_width = self.sinos.shape[1]
446
- nb_angles = len(self.angles)
447
- img_1 = self.sinos[: nb_angles // 2, :]
448
- img_2 = self.sinos[nb_angles // 2 :]
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 = {"near_pos": self.estimated_cor_from_motor, "refine": True}
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
- img_1,
457
- np.fliplr(img_2),
458
- self.angles,
459
- )
460
- cor_position = cor_position + detector_width / 2
461
- assert np.isscalar(cor_position), f"cor_position expected to be a scalar, {type(cor_position)} returned"
462
- message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.true_cor
463
- assert np.isclose(self.true_cor, cor_position, atol=self.abs_tol), message
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
- @staticmethod
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": kwargs.get("voxelSize", 40.0),
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
@@ -124,42 +124,72 @@ class CORFinderBase:
124
124
  if isinstance(cor_options, dict):
125
125
  self.cor_options.update(cor_options)
126
126
 
127
- # Priority is given to the cor estimate given by user in conf file rather than motor (in NX).
128
- # NB : this applies only when "rotation_axis_position" is set to a string i.e. an algo to estimate the cor,
129
- # Then, the "near_pos" value set in the cor_options is used as a first guess.
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
- if "near_pos" not in self.cor_options.keys():
132
- self.cor_options.update({"near_pos": "ignore"})
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 self.cor_options["near_pos"] == "from_file":
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
- self.cor_options.update({"near_pos": "ignore"})
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
- if isinstance(self.cor_options["near_pos"], Real):
147
- # estimated_cor_frm_motor value is supposed to be relative. Since the config documentation expects the "near_pos" options
148
- # to be given as an absolute COR estimate, a conversion is needed.
149
- self.cor_options["near_pos"] += detector_width // 2 # converted in absolute nb of pixels.
150
- if self.cor_options["near_pos"] > detector_width:
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"Absolute COR passed is greater than the size of the detector. Did you enter a relative COR position?"
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"] < detector_width // 2:
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
- if not (isinstance(self.cor_options["near_pos"], Real) or self.cor_options["near_pos"] == "ignore"):
162
- self.cor_options.update({"near_pos": "ignore"})
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
- "testing an overlap of %.2f pixels, actual best overlap is %.2f pixels over %d\r"
747
- % (z / self.ovs, best_overlap / self.ovs, ovsd_sx / self.ovs),
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()[1]:
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()[1])
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": "0",
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; side='near'. Mind the semicolon separator (;) and the '' for string values that are strings.\nIf 'near_pos' is set, it is expected to be an absolute CoR position in pixels.\nIt will override the CoR position taken from motor position.",
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, subchunk_slice, subchunk_file_indexes, chunk_info, subr_start_end, dtasrc_start_end, data_raw
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(data_raw, reframing_infos, chunk_info, radios_subset)
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(self, data_raw, reframing_infos, chunk_info, output, it_is_weight=False):
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[idx]
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
- subsampling_file_slice = self._expand_slice(sub_total_prange_slice)
998
+ radios_angular_range_slicing = self._expand_slice(sub_total_prange_slice)
999
999
  else:
1000
- subsampling_file_slice = sub_total_prange_slice
1001
- my_subsampled_indexes = self.chunk_reader._sorted_files_indices[subsampling_file_slice]
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):
@@ -153,4 +153,5 @@ class TestGriddedAccumulator:
153
153
  np.array((subr_start_z, subr_end_z), "i"),
154
154
  np.array((dtasrc_start_z, dtasrc_end_z), "i"),
155
155
  data_raw,
156
+ sub_total_prange_slice,
156
157
  )
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["near_pos"] = "from_file"
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["near_pos"] = "ignore"
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["near_pos"] = "from_file"
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["near_pos"] = "ignore"
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["near_pos"] = "from_file"
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["near_pos"] = "ignore"
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["near_pos"] = "from_file"
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["near_pos"] = "ignore"
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 = "modules_to_load"
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 'modules_to_load' or 'preprocessing_command' is provided
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."