tomwer 1.4.0rc0__py3-none-any.whl → 1.4.0rc2__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.
Files changed (52) hide show
  1. orangecontrib/tomwer/tutorials/test_cor.ows +3 -3
  2. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +6 -14
  3. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +4 -2
  4. tomwer/app/axis.py +0 -3
  5. tomwer/app/multipag.py +11 -3
  6. tomwer/core/process/reconstruction/axis/axis.py +27 -736
  7. tomwer/core/process/reconstruction/axis/mode.py +86 -24
  8. tomwer/core/process/reconstruction/axis/params.py +127 -138
  9. tomwer/core/process/reconstruction/axis/side.py +8 -0
  10. tomwer/core/process/reconstruction/nabu/nabuscores.py +17 -20
  11. tomwer/core/process/reconstruction/nabu/nabuslices.py +5 -1
  12. tomwer/core/process/reconstruction/saaxis/saaxis.py +4 -4
  13. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +4 -4
  14. tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
  15. tomwer/core/process/reconstruction/tests/test_utils.py +4 -4
  16. tomwer/core/process/reconstruction/utils/cor.py +8 -4
  17. tomwer/core/process/tests/test_nabu.py +1 -3
  18. tomwer/core/scan/nxtomoscan.py +2 -0
  19. tomwer/core/scan/scanbase.py +4 -4
  20. tomwer/core/scan/tests/test_process_registration.py +0 -18
  21. tomwer/gui/fonts.py +5 -0
  22. tomwer/gui/reconstruction/axis/AxisMainWindow.py +20 -9
  23. tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +239 -79
  24. tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +38 -17
  25. tomwer/gui/reconstruction/axis/AxisWidget.py +16 -8
  26. tomwer/gui/reconstruction/axis/CalculationWidget.py +40 -200
  27. tomwer/gui/reconstruction/axis/ControlWidget.py +10 -2
  28. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +394 -0
  29. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +118 -0
  30. tomwer/gui/reconstruction/axis/InputWidget.py +11 -155
  31. tomwer/gui/reconstruction/saaxis/corrangeselector.py +19 -10
  32. tomwer/gui/reconstruction/scores/scoreplot.py +5 -2
  33. tomwer/gui/reconstruction/tests/test_nabu.py +8 -0
  34. tomwer/gui/stitching/z_stitching/fineestimation.py +1 -1
  35. tomwer/gui/tests/test_axis_gui.py +31 -15
  36. tomwer/synctools/stacks/reconstruction/axis.py +5 -23
  37. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
  38. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  39. tomwer/synctools/stacks/reconstruction/normalization.py +1 -1
  40. tomwer/synctools/stacks/reconstruction/saaxis.py +1 -1
  41. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +1 -1
  42. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +0 -16
  43. tomwer/tests/test_ewoks/test_single_node_execution.py +1 -1
  44. tomwer/tests/test_ewoks/test_workflows.py +1 -1
  45. tomwer/version.py +1 -1
  46. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc2.dist-info}/METADATA +2 -2
  47. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc2.dist-info}/RECORD +51 -48
  48. tomwer/core/process/tests/test_axis.py +0 -231
  49. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc2.dist-info}/LICENSE +0 -0
  50. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc2.dist-info}/WHEEL +0 -0
  51. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc2.dist-info}/entry_points.txt +0 -0
  52. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc2.dist-info}/top_level.txt +0 -0
@@ -5,10 +5,11 @@ from collections import namedtuple
5
5
 
6
6
  import numpy
7
7
  from silx.io.url import DataUrl
8
- from silx.utils.deprecation import deprecated
9
8
  from silx.utils.enum import Enum as _Enum
10
9
  from tomoscan.esrf.scan.utils import get_data
11
10
 
11
+ from tomwer.core.process.reconstruction.utils.cor import relative_pos_to_absolute
12
+ from tomwer.core.process.reconstruction.axis.side import Side
12
13
  from tomwer.core.scan.scanbase import TomwerScanBase
13
14
 
14
15
  from .anglemode import CorAngleMode
@@ -77,13 +78,14 @@ class AxisResource(object):
77
78
  """Paganin configuration for axis calculation. To simplify we have only one
78
79
  static configuration for now. Otherwise complicate stuff"""
79
80
 
80
- def __init__(self, url):
81
+ def __init__(self, url: None | DataUrl, angle: float | None = None):
81
82
  assert url is None or isinstance(url, DataUrl)
82
83
  assert url is None or url.is_valid()
83
84
  self.__url = url
84
85
  self.__raw_data = None
85
86
  self.__norme_data = None
86
87
  self.__norm_paganin = None
88
+ self.__angle = angle
87
89
 
88
90
  def __str__(self):
89
91
  return f"{type(self)}, url: {self.__url.path() if self.__url else None}"
@@ -116,6 +118,10 @@ class AxisResource(object):
116
118
  def normalized_data(self, data):
117
119
  self.__norme_data = data
118
120
 
121
+ @property
122
+ def angle(self) -> float | None:
123
+ return self.__angle
124
+
119
125
  def normalize_data(self, scan, log_):
120
126
  """Normalize data for axis calculation"""
121
127
  if not isinstance(scan, TomwerScanBase):
@@ -182,7 +188,7 @@ class AxisResource(object):
182
188
 
183
189
  class AxisRP:
184
190
  """
185
- Configuration class for a tomwer :class:`AxisProcess`
191
+ Configuration class for a tomwer :class:`AxisTask`
186
192
 
187
193
  note: every modification on the parameters will process a call fo changed
188
194
  except `axis_url_1` and `axis_url_2` which will produce a call to the
@@ -206,11 +212,12 @@ class AxisRP:
206
212
  "FINE_STEP_X",
207
213
  "SCALE_IMG2_TO_IMG1",
208
214
  "NEAR_POSITION",
215
+ "MOTOR_OFFSET",
216
+ "X_ROTATION_AXIS_PIXEL_POSITION",
209
217
  "SINOGRAM_SUBSAMPLING",
210
218
  "PADDING_MODE",
211
219
  "FLIP_LR",
212
220
  "COMPOSITE_OPTS",
213
- "SIDE",
214
221
  "COR_OPTIONS",
215
222
  )
216
223
 
@@ -225,17 +232,18 @@ class AxisRP:
225
232
  None is not processing"""
226
233
  self.__angle_mode = CorAngleMode.use_0_180
227
234
  """Angle to use for radios"""
235
+ self.__x_rotation_axis_pixel_position = None
236
+ """rotation axis position obtained from motor"""
237
+ self.__x_rotation_axis_pos_px_offset = 0.0
238
+ """motor offset to be used to compute the 'estimated_cor' from 'x_rotation_axis_pixel_position'"""
228
239
  self.__estimated_cor = 0.0
229
- """Position to use for near calculation"""
240
+ """Estimated position of the center of rotation. Given by the user or computed from 'x_rotation_axis_pixel_position' and 'pixel_offset'"""
230
241
  self.__axis_url_1 = AxisResource(url=None)
231
242
  """first data url to use for axis cor calculation"""
232
243
  self.__axis_url_2 = AxisResource(url=None)
233
244
  """second data url to use for axis cor calculation"""
234
245
  self.__calculation_input_type = AxisCalculationInput.transmission
235
246
  """Type of input (emission, absorption, with or without paganin)"""
236
- self.__use_sinogram = False
237
- """Do we want to use the sinogram of the radios for computing center
238
- of rotation"""
239
247
  self.__sinogram_line = "middle"
240
248
  """Line of the radios to use for getting the sinogram"""
241
249
  self.__sinogram_subsampling = 10
@@ -244,17 +252,14 @@ class AxisRP:
244
252
  self.__look_at_stdmax = False
245
253
  """do the near search at X position which as the max Y column standard
246
254
  deviation"""
247
- self.__near_wx = 5
255
+ self.__composite_window_size = 5
248
256
  """do the near search in an X window of size +-near_wx"""
249
- self.__fine_stepx = 0.1
257
+ self.__composite_fine_step = 0.1
250
258
  """shift step x for fine shifting image"""
251
259
  self.__scale_img2_to_img1 = False
252
260
  """do image scaling"""
253
261
  self.__padding_mode = None
254
262
  self.__frame_width = None
255
- self.__side = "right"
256
- """side of the cor. Requested by nabu cor algorithms growing-window
257
- and sliding-window"""
258
263
  self.__flip_lr = True
259
264
  self.__composite_options = {
260
265
  "theta": DEFAULT_CMP_THETA,
@@ -274,37 +279,7 @@ class AxisRP:
274
279
 
275
280
  @mode.setter
276
281
  def mode(self, mode):
277
- if isinstance(mode, str):
278
- try:
279
- name = mode
280
- if name in ("global_", "global"):
281
- name = AxisMode.global_.name
282
- elif name == "accurate":
283
- _logger.info(
284
- f"convert axis mode {name} to {AxisMode.centered.name} (renamed)"
285
- )
286
- name = AxisMode.centered.name
287
- elif name == "growing-window":
288
- _logger.warning(
289
- f"growing-window mode has been removed. Replace it by {AxisMode.growing_window_radios.value}"
290
- )
291
- name = AxisMode.growing_window_radios.name
292
- elif name == "sliding-window":
293
- _logger.warning(
294
- f"sliding-window mode has been removed. Replace it by {AxisMode.sliding_window_radios.value}"
295
- )
296
- name = AxisMode.sliding_window_radios.name
297
- try:
298
- _mode = getattr(AxisMode, name)
299
- except Exception:
300
- _mode = AxisMode.from_value(name)
301
- except Exception:
302
- raise ValueError(f"Fail to create axis mode from {mode}")
303
- else:
304
- if not isinstance(mode, AxisMode):
305
- raise TypeError(f"mode is expected to be an instance of {AxisMode}")
306
- _mode = mode
307
- self.__mode = _mode
282
+ self.__mode = AxisMode.from_value(mode)
308
283
  self.changed()
309
284
 
310
285
  @property
@@ -346,7 +321,7 @@ class AxisRP:
346
321
  def set_relative_value(self, value):
347
322
  if not isinstance(value, (int, float, str, type(None))):
348
323
  raise TypeError(
349
- f"value is expected to be an instance of {int} {float}, {str} or {None}. {type(value)} provided"
324
+ f"value is expected to be an instance of {int}, {float}, {str}, or {None}. {type(value)} provided"
350
325
  )
351
326
  if value is None:
352
327
  changed = self.__relative_value is not None
@@ -361,32 +336,46 @@ class AxisRP:
361
336
  else:
362
337
  self.__relative_value = float(value)
363
338
  if self.frame_width is not None:
364
- self.__absolute_value = (
365
- self.__relative_value + (self.frame_width - 1) / 2.0
339
+ self.__absolute_value = relative_pos_to_absolute(
340
+ relative_pos=self.__relative_value, det_width=self.frame_width
366
341
  )
367
342
  self.changed()
368
343
 
369
344
  @property
370
- def estimated_cor(self):
345
+ def estimated_cor(self) -> Side | float | None:
371
346
  return self.__estimated_cor
372
347
 
373
348
  @estimated_cor.setter
374
- def estimated_cor(self, value):
349
+ def estimated_cor(self, value: Side | float | None):
350
+ try:
351
+ value = Side.from_value(value)
352
+ except ValueError:
353
+ pass
375
354
  if self.__estimated_cor != value:
376
355
  self.__estimated_cor = value
377
356
  self.changed()
378
357
 
379
358
  @property
380
- def side(self):
381
- return self.__side
382
-
383
- @side.setter
384
- def side(self, side):
385
- if side not in ("all", "left", "right", "center", "near"):
386
- raise ValueError(f"side '{side}' is not managed")
387
- if self.__side != side:
388
- self.__side = side
389
- self.changed()
359
+ def x_rotation_axis_pos_px_offset(self) -> float:
360
+ return self.__x_rotation_axis_pos_px_offset
361
+
362
+ @x_rotation_axis_pos_px_offset.setter
363
+ def x_rotation_axis_pos_px_offset(self, value: float):
364
+ assert isinstance(
365
+ value, float
366
+ ), f"x_rotation_axis_pos_px_offset should be a float. Got {type(value)}"
367
+ self.__x_rotation_axis_pos_px_offset = value
368
+
369
+ @property
370
+ def x_rotation_axis_pixel_position(self) -> float | None:
371
+ return self.__x_rotation_axis_pixel_position
372
+
373
+ @x_rotation_axis_pixel_position.setter
374
+ def x_rotation_axis_pixel_position(self, value: float | None):
375
+ assert isinstance(
376
+ value, (float, type(None))
377
+ ), f"x_rotation_axis_pixel_position should be None or a float. Got{type(value)}"
378
+ self.__x_rotation_axis_pixel_position = value
390
379
 
391
380
  @property
392
381
  def axis_url_1(self):
@@ -470,16 +459,6 @@ class AxisRP:
470
459
  self.__calculation_input_type = type_
471
460
  self.changed()
472
461
 
473
- @property
474
- def use_sinogram(self):
475
- return self.__use_sinogram
476
-
477
- @use_sinogram.setter
478
- def use_sinogram(self, sinogram):
479
- if self.__use_sinogram != sinogram:
480
- self.__use_sinogram = sinogram
481
- self.changed()
482
-
483
462
  @property
484
463
  def sinogram_line(self):
485
464
  return self.__sinogram_line
@@ -520,23 +499,24 @@ class AxisRP:
520
499
  self.__look_at_stdmax = stdmax
521
500
 
522
501
  @property
523
- def near_wx(self):
524
- return self.__near_wx
502
+ def composite_window_size(self):
503
+ return self.__composite_window_size
525
504
 
526
- @near_wx.setter
527
- def near_wx(self, width):
528
- if self.__near_wx != width:
529
- self.__near_wx = width
505
+ @composite_window_size.setter
506
+ def composite_window_size(self, width):
507
+ if self.__composite_window_size != width:
508
+ self.__composite_window_size = width
530
509
  self.changed()
531
510
 
532
511
  @property
533
- def fine_step_x(self):
534
- return self.__fine_stepx
535
-
536
- @fine_step_x.setter
537
- def fine_step_x(self, step_size):
538
- if self.__fine_stepx != step_size:
539
- self.__fine_stepx = step_size
512
+ def composite_fine_step(self):
513
+ """Fine step along x axis"""
514
+ return self.__composite_fine_step
515
+
516
+ @composite_fine_step.setter
517
+ def composite_fine_step(self, step_size):
518
+ if self.__composite_fine_step != step_size:
519
+ self.__composite_fine_step = step_size
540
520
  self.changed()
541
521
 
542
522
  @property
@@ -593,7 +573,7 @@ class AxisRP:
593
573
  "near_pos",
594
574
  "near_width",
595
575
  ):
596
- raise KeyError(f"{key} is not recogized")
576
+ raise KeyError(f"{key} is not recognized")
597
577
  self.__composite_options = opts
598
578
 
599
579
  @property
@@ -607,7 +587,7 @@ class AxisRP:
607
587
  self.changed()
608
588
 
609
589
  def changed(self):
610
- """callback to overwrite when the paramter value changed"""
590
+ """callback to overwrite when the parameter value changed"""
611
591
  pass
612
592
 
613
593
  def n_url(self):
@@ -640,21 +620,27 @@ class AxisRP:
640
620
  "POSITION_VALUE": self.relative_cor_value,
641
621
  "CALC_INPUT_TYPE": self.calculation_input_type.to_dict(),
642
622
  "ANGLE_MODE": self.angle_mode.value,
643
- "USE_SINOGRAM": self.use_sinogram,
644
- "SINOGRAM_LINE": self.sinogram_line if self.use_sinogram else "",
623
+ "SINOGRAM_LINE": (
624
+ self.sinogram_line if self.mode.requires_sinogram_index() else ""
625
+ ),
645
626
  "SINOGRAM_SUBSAMPLING": self.sinogram_subsampling,
646
627
  "AXIS_URL_1": axis_urls_1,
647
628
  "AXIS_URL_2": axis_urls_2,
648
629
  "LOOK_AT_STDMAX": self.look_at_stdmax,
649
- "NEAR_WX": self.near_wx,
650
- "FINE_STEP_X": self.fine_step_x,
630
+ "NEAR_WX": self.composite_window_size,
631
+ "FINE_STEP_X": self.composite_fine_step,
651
632
  "SCALE_IMG2_TO_IMG1": self.scale_img2_to_img1,
652
- "NEAR_POSITION": self.estimated_cor,
633
+ "NEAR_POSITION": (
634
+ self.estimated_cor.value
635
+ if isinstance(self.estimated_cor, Side)
636
+ else self.estimated_cor
637
+ ),
653
638
  "PADDING_MODE": self.padding_mode,
654
639
  "FLIP_LR": self.flip_lr,
655
640
  "COMPOSITE_OPTS": self.composite_options,
656
- "SIDE": self.side,
657
641
  "COR_OPTIONS": self.extra_cor_options,
642
+ "MOTOR_OFFSET": self.x_rotation_axis_pos_px_offset,
643
+ "X_ROTATION_AXIS_PIXEL_POSITION": self.x_rotation_axis_pixel_position,
658
644
  }
659
645
  return _dict
660
646
 
@@ -679,8 +665,6 @@ class AxisRP:
679
665
  self.calculation_input_type = AxisCalculationInput.from_value(
680
666
  _dict["CALC_INPUT_TYPE"]
681
667
  )
682
- if "USE_SINOGRAM" in _dict:
683
- self.use_sinogram = _dict["USE_SINOGRAM"]
684
668
  if "ANGLE_MODE" in _dict:
685
669
  self.angle_mode = CorAngleMode.from_value(_dict["ANGLE_MODE"])
686
670
  if "SINOGRAM_LINE" in _dict:
@@ -692,13 +676,13 @@ class AxisRP:
692
676
  if "LOOK_AT_STDMAX" in _dict:
693
677
  self.look_at_stdmax = _dict["LOOK_AT_STDMAX"]
694
678
  if "NEAR_WX" in _dict:
695
- self.near_wx = _dict["NEAR_WX"]
679
+ self.composite_window_size = _dict["NEAR_WX"]
696
680
  if "FINE_STEP_X" in _dict:
697
- self.fine_step_x = _dict["FINE_STEP_X"]
681
+ self.composite_fine_step = _dict["FINE_STEP_X"]
698
682
  if "SCALE_IMG2_TO_IMG1" in _dict:
699
683
  self.scale_img2_to_img1 = _dict["SCALE_IMG2_TO_IMG1"]
700
684
  if "NEAR_POSITION" in _dict:
701
- self.estimated_cor = float(_dict["NEAR_POSITION"])
685
+ self.estimated_cor = _dict["NEAR_POSITION"]
702
686
  if "SINOGRAM_SUBSAMPLING" in _dict:
703
687
  self.sinogram_subsampling = _dict["SINOGRAM_SUBSAMPLING"]
704
688
  if "PADDING_MODE" in _dict:
@@ -707,8 +691,6 @@ class AxisRP:
707
691
  self.flip_lr = bool(_dict["FLIP_LR"])
708
692
  if "COMPOSITE_OPTS" in _dict:
709
693
  self.composite_options = _dict["COMPOSITE_OPTS"]
710
- if "SIDE" in _dict:
711
- self.side = _dict["SIDE"]
712
694
  self.extra_cor_options = _dict.get("COR_OPTIONS", "")
713
695
 
714
696
  def copy(self, axis_params, copy_axis_url=True, copy_flip_lr=True):
@@ -717,17 +699,17 @@ class AxisRP:
717
699
  self.frame_width = axis_params.frame_width
718
700
  self.set_relative_value(axis_params.relative_cor_value)
719
701
  self.calculation_input_type = axis_params.calculation_input_type
720
- self.use_sinogram = axis_params.use_sinogram
721
702
  self.angle_mode = axis_params.angle_mode
722
703
  self.sinogram_line = axis_params.sinogram_line
723
704
  self.sinogram_subsampling = axis_params.sinogram_subsampling
724
705
  self.look_at_stdmax = axis_params.look_at_stdmax
725
- self.near_wx = axis_params.near_wx
726
- self.fine_step_x = axis_params.fine_step_x
706
+ self.composite_window_size = axis_params.composite_window_size
707
+ self.composite_fine_step = axis_params.composite_fine_step
727
708
  self.scale_img2_to_img1 = axis_params.scale_img2_to_img1
728
709
  self.estimated_cor = axis_params.estimated_cor
710
+ self.x_rotation_axis_pixel_position = axis_params.x_rotation_axis_pixel_position
711
+ self.x_rotation_axis_pos_px_offset = axis_params.x_rotation_axis_pos_px_offset
729
712
  self.padding_mode = axis_params.padding_mode
730
- self.side = axis_params.side
731
713
  self.composite_options = axis_params.composite_options
732
714
  self.extra_cor_options = axis_params.extra_cor_options
733
715
  if copy_axis_url:
@@ -754,56 +736,63 @@ class AxisRP:
754
736
  AxisMode.sliding_window_radios,
755
737
  AxisMode.sliding_window_sinogram,
756
738
  ):
757
- extra_info = f"side: {self.side}, use sinogram: {self.use_sinogram}"
739
+ extra_info = f"side: {self.estimated_cor}"
758
740
  results = ", ".join((results, extra_info))
759
741
  return results
760
742
 
761
743
  def get_nabu_cor_options_as_dict(self) -> str:
762
744
  options = {}
763
- if self.mode is AxisMode.near:
764
- self.side = "near"
765
- request_side = len(AXIS_MODE_METADATAS[self.mode].valid_sides) > 0
766
- if request_side:
767
-
768
- if self.side == "near":
769
- options["side"] = self.composite_options.get(
770
- "near_pos", self.estimated_cor
771
- )
772
- else:
773
- options["side"] = self.side
745
+ # provide side. Scalar if a first guess is provided else a value in ('left', 'right', 'center', 'all')
746
+ if (
747
+ isinstance(self.estimated_cor, Side)
748
+ and AXIS_MODE_METADATAS[self.mode].valid_sides
749
+ ):
750
+ options["side"] = self.estimated_cor.value
751
+ elif AXIS_MODE_METADATAS[self.mode].allows_estimated_cor_as_numerical_value:
752
+ options["side"] = self.estimated_cor
753
+
754
+ if self.mode in (AxisMode.composite_coarse_to_fine, AxisMode.near):
755
+ options["near_width"] = self.composite_options.get(
756
+ "near_width", DEFAULT_CMP_NEAR_WIDTH
757
+ )
758
+ options["theta_interval"] = self.composite_options.get(
759
+ "theta_interval", DEFAULT_CMP_THETA
760
+ )
761
+ options["oversampling"] = self.composite_options.get(
762
+ "oversampling", DEFAULT_CMP_OVERSAMPLING
763
+ )
764
+ options["n_subsampling_y"] = self.composite_options.get(
765
+ "n_subsampling_y", DEFAULT_CMP_N_SUBSAMPLING_Y
766
+ )
767
+ options["take_log"] = self.composite_options.get(
768
+ "take_log", DEFAULT_CMP_TAKE_LOG
769
+ )
774
770
 
775
- if self.side == "near":
776
- near_width = self.composite_options.get("near_width", 20.0)
777
- options["near_width"] = near_width
771
+ # provide radio indices or sinogram index
772
+ if self.mode.requires_radio_indices:
773
+ # warning: nabu expect radio angles to be in rad, in nxtomo and tomwer they are in degrees
774
+ options["radio_angles"] = (
775
+ numpy.deg2rad(self.axis_url_1.angle or 0.0),
776
+ numpy.deg2rad(self.axis_url_2.angle or 180.0),
777
+ )
778
+ if self.mode.requires_sinogram_index:
779
+ options["slice_idx"] = self.sinogram_line
778
780
 
779
781
  # append "extra_cor_options" to already handled cor options
782
+ # expected values: low_pass, high_pass
780
783
  extra_cor_options = self.extra_cor_options.replace(" ", "")
781
784
 
782
785
  if extra_cor_options != "":
783
786
  for opt in self.extra_cor_options.replace(" ", "").split(";"):
784
787
  if len(opt.split("=")) == 2:
785
788
  key, value = opt.split("=")
786
- options[key] = value
789
+ if key in ("low_pass", "high_pass"):
790
+ value = float(value)
791
+ options[key] = value
792
+ else:
793
+ _logger.warning(
794
+ f"key {key} is not recognized by nabu. Ignore it."
795
+ )
787
796
  else:
788
- _logger.info(f"ignore option {opt}. Invalid synthax")
797
+ _logger.info(f"ignore option {opt}. Invalid syntax")
789
798
  return options
790
-
791
- @deprecated(replacement="get_nabu_cor_options_as_str", since_version="1.1")
792
- def get_nabu_cor_options(self) -> str:
793
- return self.get_nabu_cor_options_as_str()
794
-
795
- def get_nabu_cor_options_as_str(self) -> str:
796
- """return cor option for nabu"""
797
-
798
- def cast_key_value(key, value):
799
- if key in ("side",):
800
- return f"{key}='{value}'"
801
- else:
802
- return f"{key}={value}"
803
-
804
- return " ; ".join(
805
- [
806
- cast_key_value(key, value)
807
- for key, value in self.get_nabu_cor_options_as_dict().items()
808
- ]
809
- )
@@ -0,0 +1,8 @@
1
+ from silx.utils.enum import Enum as _Enum
2
+
3
+
4
+ class Side(_Enum):
5
+ LEFT = "left"
6
+ RIGHT = "right"
7
+ CENTER = "center"
8
+ ALL = "all"
@@ -42,6 +42,10 @@ from tomwer.core.process.reconstruction.nabu.utils import (
42
42
  slice_index_to_int,
43
43
  get_nabu_multicor_file_prefix,
44
44
  )
45
+ from tomwer.core.process.reconstruction.utils.cor import (
46
+ relative_pos_to_absolute,
47
+ absolute_pos_to_relative,
48
+ )
45
49
  from tomwer.core.process.reconstruction.nabu.target import Target
46
50
  from tomwer.core.scan.scanbase import TomwerScanBase
47
51
  from tomwer.core.utils.slurm import get_slurm_script_name, is_slurm_available
@@ -495,22 +499,6 @@ class _ReconstructorMultiCor(_NabuBaseReconstructor):
495
499
  else:
496
500
  raise ValueError(f"{self.target} is not recognized as a valid target")
497
501
 
498
- @staticmethod
499
- def convert_cor_from_rel_to_abs(scan, cor):
500
- if scan.dim_1 is not None:
501
- return cor + (scan.dim_1 - 1) / 2.0
502
- else:
503
- _logger.warning("enable to get image half width. Set it to 1024")
504
- return cor + 1024
505
-
506
- @staticmethod
507
- def convert_cor_from_abs_to_rel(scan, cor):
508
- if scan.dim_1 is not None:
509
- return cor - (scan.dim_1 - 1) / 2.0
510
- else:
511
- _logger.warning("enable to get image half width. Set it to 1024")
512
- return cor - 1024
513
-
514
502
  def _run_nabu_multicor_locally(
515
503
  self,
516
504
  conf_file: str,
@@ -530,7 +518,10 @@ class _ReconstructorMultiCor(_NabuBaseReconstructor):
530
518
  slice_index = slice_index_to_int(self.slice_index, scan=self.scan)
531
519
 
532
520
  cor_in_nabu_ref = tuple(
533
- [self.convert_cor_from_rel_to_abs(self.scan, cor) for cor in self.cors]
521
+ [
522
+ relative_pos_to_absolute(relative_pos=cor, det_width=self.scan.dim_1)
523
+ for cor in self.cors
524
+ ]
534
525
  )
535
526
  cor_in_nabu_ref = ",".join([str(cor) for cor in cor_in_nabu_ref])
536
527
  command = " ".join(
@@ -567,12 +558,15 @@ class _ReconstructorMultiCor(_NabuBaseReconstructor):
567
558
  scan=self.scan,
568
559
  file_format=file_format,
569
560
  cors=[
570
- self.convert_cor_from_rel_to_abs(self.scan, cor) for cor in self.cors
561
+ relative_pos_to_absolute(relative_pos=cor, det_width=self.scan.dim_1)
562
+ for cor in self.cors
571
563
  ],
572
564
  )
573
565
  # convert back from abs ref to rel ref
574
566
  recons_vol_identifiers = {
575
- self.convert_cor_from_abs_to_rel(self.scan, cor): identifiers
567
+ absolute_pos_to_relative(
568
+ absolute_pos=cor, det_width=self.scan.dim_1
569
+ ): identifiers
576
570
  for cor, identifiers in recons_vol_identifiers.items()
577
571
  }
578
572
  return ResultsLocalRun(
@@ -622,7 +616,10 @@ class _ReconstructorMultiCor(_NabuBaseReconstructor):
622
616
 
623
617
  slice_index = slice_index_to_int(self.slice_index, scan=self.scan)
624
618
  cor_in_nabu_ref = tuple(
625
- [self.convert_cor_from_rel_to_abs(self.scan, cor) for cor in self.cors]
619
+ [
620
+ relative_pos_to_absolute(relative_pos=cor, det_width=self.scan.dim_1)
621
+ for cor in self.cors
622
+ ]
626
623
  )
627
624
  cor_in_nabu_ref = ",".join([str(cor) for cor in cor_in_nabu_ref])
628
625
 
@@ -16,6 +16,7 @@ from tomwer.core.utils.scanutils import data_identifier_to_scan
16
16
  from tomwer.core.utils.slurm import is_slurm_available
17
17
  from tomwer.core.process.reconstruction.nabu.plane import NabuPlane
18
18
  from tomwer.core.process.reconstruction.nabu.utils import slice_index_to_int
19
+ from tomwer.core.process.reconstruction.utils.cor import relative_pos_to_absolute
19
20
  from tomwer.core.volume.volumefactory import VolumeFactory
20
21
  from tomwer.core.process.reconstruction.output import (
21
22
  PROCESS_FOLDER_RECONSTRUCTED_SLICES,
@@ -131,7 +132,10 @@ def run_slices_reconstruction(
131
132
  if scan.axis_params is not None and scan.axis_params.relative_cor_value is not None:
132
133
  if "reconstruction" in config:
133
134
  # move the cor value to the nabu reference
134
- cor_nabu_ref = scan.axis_params.relative_cor_value + (scan.dim_1 - 1) / 2.0
135
+ cor_nabu_ref = relative_pos_to_absolute(
136
+ relative_pos=scan.axis_params.relative_cor_value,
137
+ det_width=scan.dim_1,
138
+ )
135
139
  config["reconstruction"]["rotation_axis_position"] = str(cor_nabu_ref)
136
140
  _logger.info(f"set nabu reconstruction parameters to {scan}")
137
141
 
@@ -31,6 +31,7 @@ from tomwer.core.process.reconstruction.scores import (
31
31
  get_disk_mask_radius,
32
32
  )
33
33
  from tomwer.core.process.reconstruction.scores.params import ScoreMethod
34
+ from tomwer.core.process.reconstruction.utils.cor import relative_pos_to_absolute
34
35
  from tomwer.core.process.task import Task
35
36
  from tomwer.core.scan.nxtomoscan import NXtomoScan
36
37
  from tomwer.core.scan.scanbase import TomwerScanBase
@@ -53,7 +54,6 @@ from tomwer.core.process.reconstruction.nabu.utils import (
53
54
  get_multi_cor_recons_volume_identifiers,
54
55
  get_nabu_multicor_file_prefix,
55
56
  )
56
- from tomwer.core.process.reconstruction.nabu.nabuscores import _ReconstructorMultiCor
57
57
 
58
58
  from .params import SAAxisParams
59
59
 
@@ -316,9 +316,9 @@ class SAAxisTask(
316
316
  continue
317
317
 
318
318
  for cor in cors:
319
- cor_nabu_ref = _ReconstructorMultiCor.convert_cor_from_rel_to_abs(
320
- scan=scan,
321
- cor=cor,
319
+ cor_nabu_ref = relative_pos_to_absolute(
320
+ relative_pos=scan.axis_params.relative_cor_value,
321
+ det_width=scan.dim_1,
322
322
  )
323
323
  volume_identifiers = get_multi_cor_recons_volume_identifiers(
324
324
  scan=scan,
@@ -28,6 +28,7 @@ from tomwer.core.process.reconstruction.nabu.nabucommon import (
28
28
  ResultsWithStd,
29
29
  )
30
30
  from tomwer.core.process.reconstruction.nabu.nabuslices import SingleSliceRunner
31
+ from tomwer.core.process.reconstruction.utils.cor import relative_pos_to_absolute
31
32
  from tomwer.core.process.reconstruction.scores import (
32
33
  ComputedScore,
33
34
  ScoreMethod,
@@ -360,15 +361,14 @@ class SADeltaBetaTask(
360
361
 
361
362
  def _config_preprocessing(self, scan, config, delta_beta_s, output_dir) -> dict:
362
363
  config.get("phase", {}).pop("beam_shape", None)
363
- # if scan contains some center of position copy it to nabu
364
364
  if (
365
365
  scan.axis_params is not None
366
366
  and scan.axis_params.relative_cor_value is not None
367
367
  ):
368
368
  if "reconstruction" in config:
369
- # move the cor value to the nabu reference
370
- cor_nabu_ref = (
371
- scan.axis_params.relative_cor_value + (scan.dim_1 - 1) / 2.0
369
+ cor_nabu_ref = relative_pos_to_absolute(
370
+ relative_pos=scan.axis_params.relative_cor_value,
371
+ det_width=scan.dim_1,
372
372
  )
373
373
  config["reconstruction"]["rotation_axis_position"] = str(cor_nabu_ref)
374
374
 
@@ -42,5 +42,5 @@ def test_read_x_rotation_axis_pixel_position(nxtomo_scan_360): # noqa F811
42
42
 
43
43
  nxtomo_scan_360.clear_caches()
44
44
  task.run()
45
- assert nxtomo_scan_360.axis_params.absolute_cor_value == 22.0
45
+ assert nxtomo_scan_360.axis_params.absolute_cor_value == 22.5
46
46
  assert nxtomo_scan_360.axis_params.relative_cor_value == 12.5
@@ -10,11 +10,11 @@ def test_cor_conversion():
10
10
  """
11
11
  test absolute_pos_to_relative and relative_pos_to_absolute functions
12
12
  """
13
- assert relative_pos_to_absolute(relative_pos=0.0, det_width=100) == 49.5
14
- assert relative_pos_to_absolute(relative_pos=0.0, det_width=101) == 50.0
13
+ assert relative_pos_to_absolute(relative_pos=0.0, det_width=100) == 50.0
14
+ assert relative_pos_to_absolute(relative_pos=0.0, det_width=101) == 50.5
15
15
 
16
- assert absolute_pos_to_relative(absolute_pos=20, det_width=500) == -229.5
17
- assert absolute_pos_to_relative(absolute_pos=300, det_width=500) == 50.5
16
+ assert absolute_pos_to_relative(absolute_pos=20, det_width=500) == -230.0
17
+ assert absolute_pos_to_relative(absolute_pos=300, det_width=500) == 50.0
18
18
 
19
19
  for det_width in (10, 20, 30, 50):
20
20
  for relative_cor_pos in (0, -2.3, -4.5):