tomwer 1.4.0__py3-none-any.whl → 1.4.0rc0__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 (53) hide show
  1. orangecontrib/tomwer/tutorials/test_cor.ows +3 -3
  2. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +14 -6
  3. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +2 -4
  4. tomwer/app/axis.py +3 -0
  5. tomwer/app/multipag.py +3 -11
  6. tomwer/core/process/reconstruction/axis/axis.py +736 -27
  7. tomwer/core/process/reconstruction/axis/mode.py +24 -86
  8. tomwer/core/process/reconstruction/axis/params.py +138 -127
  9. tomwer/core/process/reconstruction/nabu/nabuscores.py +22 -19
  10. tomwer/core/process/reconstruction/nabu/nabuslices.py +1 -5
  11. tomwer/core/process/reconstruction/saaxis/saaxis.py +4 -4
  12. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +4 -4
  13. tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
  14. tomwer/core/process/reconstruction/tests/test_utils.py +4 -4
  15. tomwer/core/process/reconstruction/utils/cor.py +4 -8
  16. tomwer/core/process/tests/test_axis.py +231 -0
  17. tomwer/core/process/tests/test_nabu.py +3 -1
  18. tomwer/core/scan/nxtomoscan.py +0 -2
  19. tomwer/core/scan/scanbase.py +4 -4
  20. tomwer/core/scan/tests/test_process_registration.py +18 -0
  21. tomwer/gui/reconstruction/axis/AxisMainWindow.py +9 -20
  22. tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +79 -239
  23. tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +17 -38
  24. tomwer/gui/reconstruction/axis/AxisWidget.py +8 -16
  25. tomwer/gui/reconstruction/axis/CalculationWidget.py +200 -44
  26. tomwer/gui/reconstruction/axis/ControlWidget.py +2 -10
  27. tomwer/gui/reconstruction/axis/InputWidget.py +155 -11
  28. tomwer/gui/reconstruction/saaxis/corrangeselector.py +10 -19
  29. tomwer/gui/reconstruction/scores/scoreplot.py +2 -5
  30. tomwer/gui/reconstruction/tests/test_nabu.py +0 -8
  31. tomwer/gui/stitching/config/axisparams.py +0 -2
  32. tomwer/gui/stitching/z_stitching/fineestimation.py +1 -1
  33. tomwer/gui/tests/test_axis_gui.py +15 -31
  34. tomwer/synctools/stacks/reconstruction/axis.py +23 -5
  35. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
  36. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  37. tomwer/synctools/stacks/reconstruction/normalization.py +1 -1
  38. tomwer/synctools/stacks/reconstruction/saaxis.py +1 -1
  39. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +1 -1
  40. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +16 -0
  41. tomwer/tests/test_ewoks/test_single_node_execution.py +1 -1
  42. tomwer/tests/test_ewoks/test_workflows.py +1 -1
  43. tomwer/version.py +1 -1
  44. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/METADATA +3 -3
  45. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/RECORD +49 -52
  46. tomwer/core/process/reconstruction/axis/side.py +0 -8
  47. tomwer/gui/fonts.py +0 -5
  48. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +0 -394
  49. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +0 -118
  50. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/LICENSE +0 -0
  51. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/WHEEL +0 -0
  52. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/entry_points.txt +0 -0
  53. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,23 @@
1
- """contain the AxisTask class
1
+ """contain the AxisProcess
2
2
  """
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
6
  import logging
7
7
 
8
- from nabu.pipeline.estimators import estimate_cor
8
+ import numpy
9
+ from nabu.estimation.cor import (
10
+ CenterOfRotation,
11
+ CenterOfRotationAdaptiveSearch,
12
+ CenterOfRotationGrowingWindow,
13
+ CenterOfRotationSlidingWindow,
14
+ CenterOfRotationOctaveAccurate,
15
+ )
16
+ from nabu.pipeline.estimators import SinoCORFinder, CORFinder
9
17
  from nabu.resources.nxflatfield import update_dataset_info_flats_darks
10
18
  from processview.core.manager import DatasetState, ProcessManager
11
19
  from processview.core.superviseprocess import SuperviseProcess
20
+ from tomwer.core.utils.deprecation import deprecated_warning
12
21
 
13
22
  import tomwer.version
14
23
  from tomwer.core.process.reconstruction.utils.cor import absolute_pos_to_relative
@@ -33,8 +42,19 @@ from .params import (
33
42
  )
34
43
  from .projectiontype import ProjectionType
35
44
 
36
- _logger = logging.getLogger(__name__)
45
+ try:
46
+ from nabu.pipeline.estimators import CompositeCOREstimator
47
+ except ImportError:
48
+ has_composite_cor_finder = False
49
+ else:
50
+ has_composite_cor_finder = True
51
+ from silx.io.url import DataUrl
52
+ from silx.io.utils import h5py_read_dataset
53
+ from silx.io.utils import open as open_hdf5
37
54
 
55
+ _logger = logging.getLogger(__name__)
56
+ if not has_composite_cor_finder:
57
+ _logger.warning("No composite cor finder found at nabu level")
38
58
  # vertically, work on a window having only a percentage of the frame.
39
59
  pc_height = 10.0 / 100.0
40
60
  # horizontally. Global method supposes the COR is more or less in center
@@ -85,6 +105,191 @@ def adapt_tomwer_scan_to_nabu(scan: TomwerScanBase):
85
105
  return dataset_infos
86
106
 
87
107
 
108
+ def compute_cor_nabu_growing_window(
109
+ radio_1: numpy.ndarray,
110
+ radio_2: numpy.ndarray,
111
+ side: str,
112
+ padding_mode,
113
+ flip_frame_2_lr=True,
114
+ horz_fft_width=False,
115
+ ):
116
+ """
117
+ Call nabu.preproc.alignement.CenterOfRotationGrowingWindow.find_shift
118
+
119
+ :param radio_1:
120
+ :param radio_2:
121
+ :param padding_mode: padding mode
122
+ :param side: side of the cor
123
+ :param flip_frame_2_lr: if True will left-right flip the second frame
124
+ :param horz_fft_width:
125
+
126
+ :return:
127
+ """
128
+ nabu_class = CenterOfRotationGrowingWindow(horz_fft_width=horz_fft_width)
129
+ # value return is relative
130
+ res = nabu_class.find_shift(
131
+ img_1=radio_1,
132
+ img_2=numpy.fliplr(radio_2) if flip_frame_2_lr else radio_2,
133
+ side=side,
134
+ roi_yxhw=None,
135
+ padding_mode=padding_mode,
136
+ median_filt_shape=None,
137
+ )
138
+ if isinstance(res, numpy.ndarray):
139
+ if len(res) == 1:
140
+ return res[0]
141
+ else:
142
+ raise ValueError(
143
+ "nabu rsult is expected to be a scalar, numpy array found. Please upgrade nabu this issue is expected to be solved"
144
+ )
145
+ else:
146
+ return res
147
+
148
+
149
+ def compute_cor_nabu_growing_window_radios(
150
+ scan: TomwerScanBase,
151
+ ) -> float | None:
152
+ """
153
+ Call nabu.preproc.alignement.CenterOfRotationGrowingWindow.find_shift
154
+
155
+ :param scan: scan for which we want to get the reconstruction parameters
156
+ """
157
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
158
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
159
+
160
+ projection_angles = scan.get_proj_angle_url()
161
+ projection_angles_i = {
162
+ value.path(): key for key, value in projection_angles.items()
163
+ }
164
+ url_radio_1, url_radio_2 = AxisTask.get_inputs_urls(scan=scan)
165
+ angle_radio_1 = float(projection_angles_i[url_radio_1.url.path()])
166
+ angle_radio_2 = float(projection_angles_i[url_radio_2.url.path()])
167
+ radio_angles = tuple(numpy.radians((angle_radio_1, angle_radio_2)))
168
+
169
+ corfinder = CORFinder(
170
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
171
+ method="growing-window",
172
+ do_flatfield=has_darks and has_flats,
173
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
174
+ radio_angles=radio_angles,
175
+ logger=_logger,
176
+ )
177
+ res = corfinder.find_cor() # Returns absolute cor
178
+ if isinstance(res, numpy.ndarray):
179
+ if len(res) == 1:
180
+ res = res[0]
181
+ else:
182
+ raise ValueError(
183
+ "nabu rsult is expected to be a scalar, numpy array found. Please upgrade nabu this issue is expected to be solved"
184
+ )
185
+
186
+ return _absolute_pos_to_relative_with_warning(
187
+ absolute_pos=res, det_width=scan.dim_1
188
+ )
189
+
190
+
191
+ def compute_cor_nabu_growing_window_sinogram(
192
+ scan: TomwerScanBase,
193
+ ) -> float | None:
194
+ """
195
+ Call nabu.preproc.alignement.CenterOfRotationGrowingWindow.find_shift
196
+
197
+ :param scan:
198
+ """
199
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
200
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
201
+
202
+ corfinder = SinoCORFinder(
203
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
204
+ method="sino-growing-window",
205
+ slice_idx=scan.axis_params.sinogram_line or "middle",
206
+ subsampling=scan.axis_params.sinogram_subsampling,
207
+ do_flatfield=has_darks and has_flats,
208
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
209
+ logger=_logger,
210
+ )
211
+ res = corfinder.find_cor()
212
+ if isinstance(res, numpy.ndarray):
213
+ if len(res) == 1:
214
+ res = res[0]
215
+ else:
216
+ raise ValueError(
217
+ "nabu rsult is expected to be a scalar, numpy array found. Please upgrade nabu this issue is expected to be solved"
218
+ )
219
+
220
+ return _absolute_pos_to_relative_with_warning(
221
+ absolute_pos=res, det_width=scan.dim_1
222
+ )
223
+
224
+
225
+ def compute_scan_sino_coarse_to_fine(scan):
226
+ """
227
+ Compute center of rotation from `sino-coarse-to-fine` algorithm for given
228
+ scan
229
+ :param scan:
230
+ :return:
231
+ """
232
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
233
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
234
+
235
+ corfinder = SinoCORFinder(
236
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
237
+ method=AxisMode.sino_coarse_to_fine.value,
238
+ slice_idx=scan.axis_params.sinogram_line or "middle",
239
+ subsampling=scan.axis_params.sinogram_subsampling,
240
+ do_flatfield=has_darks and has_flats,
241
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
242
+ logger=_logger,
243
+ )
244
+ res = corfinder.find_cor()
245
+ return _absolute_pos_to_relative_with_warning(
246
+ absolute_pos=res, det_width=scan.dim_1
247
+ )
248
+
249
+
250
+ def compute_scan_composite_coarse_to_fine(scan: TomwerScanBase):
251
+ """
252
+ Compute center of rotation from `sino-coarse-to-fine` algorithm for given
253
+ scan
254
+ :param scan:
255
+ :return:
256
+ """
257
+ if not has_composite_cor_finder:
258
+ _logger.error("unable to find nabu CompositeCOREstimator")
259
+ return None
260
+
261
+ (
262
+ theta,
263
+ n_subsampling_y,
264
+ oversampling,
265
+ take_log,
266
+ near_pos,
267
+ near_width,
268
+ ) = get_composite_options(scan)
269
+
270
+ # as the new corfinder is not yet merged in the main branch
271
+ # allow some tolerance for the "side" argument that is there only
272
+ # in the new one
273
+
274
+ cor_options = scan.axis_params.get_nabu_cor_options_as_dict()
275
+ for key in "low_pass", "high_pass":
276
+ if key in cor_options:
277
+ cor_options[key] = int(cor_options[key])
278
+ corfinder = CompositeCOREstimator(
279
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
280
+ theta_interval=theta,
281
+ n_subsampling_y=n_subsampling_y,
282
+ oversampling=oversampling,
283
+ cor_options=cor_options,
284
+ logger=_logger,
285
+ take_log=take_log,
286
+ )
287
+ res = corfinder.find_cor()
288
+ return _absolute_pos_to_relative_with_warning(
289
+ absolute_pos=res, det_width=scan.dim_1
290
+ )
291
+
292
+
88
293
  def get_composite_options(scan) -> tuple:
89
294
  theta = scan.axis_params.composite_options.get("theta", DEFAULT_CMP_THETA)
90
295
  n_subsampling_y = scan.axis_params.composite_options.get(
@@ -102,6 +307,216 @@ def get_composite_options(scan) -> tuple:
102
307
  return theta, n_subsampling_y, oversampling, take_log, near_pos, near_width
103
308
 
104
309
 
310
+ def compute_scan_cor_nabu_growing_window(scan) -> float | None:
311
+ """
312
+ Call to nabu.preproc.alignment.CenterOfRotation from the scan axis_params
313
+ value.
314
+
315
+ :param scan: scan to process
316
+ """
317
+ if not scan.axis_params.use_sinogram:
318
+ radio_1, radio_2 = AxisTask.get_inputs(scan=scan)
319
+ if radio_1 is None or radio_2 is None:
320
+ raise NoAxisUrl("Unable to find projections for nabu axis calculation")
321
+ else:
322
+ radio_1, radio_2 = None, None
323
+
324
+ _logger.info(
325
+ "compute scan axis from nabu CenterOfRotationGrowingWindow with padding "
326
+ "mode {} and side {}. Use sinogram: {}".format(
327
+ scan.axis_params.padding_mode,
328
+ scan.axis_params.side,
329
+ scan.axis_params.use_sinogram,
330
+ )
331
+ )
332
+
333
+ if scan.axis_params.use_sinogram:
334
+ return compute_cor_nabu_growing_window_sinogram(scan=scan)
335
+ else:
336
+ return compute_cor_nabu_growing_window_radios(scan=scan)
337
+
338
+
339
+ def compute_cor_nabu_sliding_window(
340
+ radio_1: numpy.ndarray,
341
+ radio_2: numpy.ndarray,
342
+ side: str,
343
+ padding_mode,
344
+ flip_frame_2_lr=True,
345
+ horz_fft_width=False,
346
+ ) -> float | None:
347
+ """
348
+ Call nabu.preproc.alignement.CenterOfRotationSlidingWindow.find_shift
349
+
350
+ :param radio_1:
351
+ :param radio_2:
352
+ :param padding_mode:
353
+ :param side: side of the cor
354
+ :param horz_fft_width:
355
+ :param flip_frame_2_lr: if True will left-right flip the second frame
356
+ :param half_acq_cor_guess: The approximate position of the rotation axis
357
+ from the image center. Optional. When given a
358
+ special algorithm is used which can work also
359
+ in half-tomo conditions.
360
+ """
361
+ nabu_class = CenterOfRotationSlidingWindow(horz_fft_width=horz_fft_width)
362
+ res = nabu_class.find_shift(
363
+ img_1=radio_1,
364
+ img_2=numpy.fliplr(radio_2) if flip_frame_2_lr else radio_2,
365
+ side=side,
366
+ roi_yxhw=None,
367
+ median_filt_shape=None,
368
+ )
369
+ return res
370
+
371
+
372
+ def compute_cor_nabu_sliding_window_radios(
373
+ scan: TomwerScanBase,
374
+ ) -> float | None:
375
+ """
376
+ Call nabu.preproc.alignment.CenterOfRotationGrowingWindow.find_shift
377
+
378
+ :param TomwerScanBase scan:
379
+ """
380
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
381
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
382
+
383
+ projection_angles = scan.get_proj_angle_url()
384
+ projection_angles_i = {
385
+ value.path(): key for key, value in projection_angles.items()
386
+ }
387
+ url_radio_1, url_radio_2 = AxisTask.get_inputs_urls(scan=scan)
388
+ angle_radio_1 = float(projection_angles_i[url_radio_1.url.path()])
389
+ angle_radio_2 = float(projection_angles_i[url_radio_2.url.path()])
390
+ radio_angles = tuple(numpy.radians((angle_radio_1, angle_radio_2)))
391
+
392
+ corfinder = CORFinder(
393
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
394
+ method="sliding-window",
395
+ do_flatfield=has_darks and has_flats,
396
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
397
+ radio_angles=radio_angles,
398
+ logger=_logger,
399
+ )
400
+ res = corfinder.find_cor() # Returns absolute cor.
401
+ return _absolute_pos_to_relative_with_warning(
402
+ absolute_pos=res, det_width=scan.dim_1
403
+ )
404
+
405
+
406
+ def compute_cor_nabu_sliding_window_sinogram(
407
+ scan: TomwerScanBase,
408
+ ) -> float | None:
409
+ """
410
+ Call nabu.preproc.alignment.CenterOfRotationGrowingWindow.find_shift
411
+
412
+ :param TomwerScanBase scan:
413
+ """
414
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
415
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
416
+
417
+ corfinder = SinoCORFinder(
418
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
419
+ method="sino-sliding-window",
420
+ slice_idx=scan.axis_params.sinogram_line or "middle",
421
+ subsampling=scan.axis_params.sinogram_subsampling,
422
+ do_flatfield=has_darks and has_flats,
423
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
424
+ logger=_logger,
425
+ )
426
+ res = corfinder.find_cor()
427
+ return _absolute_pos_to_relative_with_warning(
428
+ absolute_pos=res, det_width=scan.dim_1
429
+ )
430
+
431
+
432
+ def compute_scan_cor_nabu_sliding_window(scan: TomwerScanBase) -> float | None:
433
+ """
434
+ Call to nabu.preproc.alignment.CenterOfRotation from the scan axis_params
435
+ value.
436
+
437
+ :param scan: scan to process
438
+ """
439
+ if not scan.axis_params.use_sinogram:
440
+ radio_1, radio_2 = AxisTask.get_inputs(scan=scan)
441
+ if radio_1 is None or radio_2 is None:
442
+ raise NoAxisUrl("Unable to find projections for nabu axis calculation")
443
+ else:
444
+ radio_1 = radio_2 = None
445
+
446
+ _logger.info(
447
+ "compute scan axis from nabu CenterOfRotationSlidingWindow with padding "
448
+ "mode {} and side {}. Use sinogram: {}".format(
449
+ scan.axis_params.padding_mode,
450
+ scan.axis_params.side,
451
+ scan.axis_params.use_sinogram,
452
+ )
453
+ )
454
+
455
+ if scan.axis_params.use_sinogram:
456
+ return compute_cor_nabu_sliding_window_sinogram(scan=scan)
457
+ else:
458
+ return compute_cor_nabu_sliding_window_radios(scan=scan)
459
+
460
+
461
+ def compute_scan_fourier_angles(scan: TomwerScanBase) -> float | None:
462
+ """
463
+ run 'scan_fourier_angles' algorithm for the requested scan
464
+ """
465
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
466
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
467
+
468
+ corfinder = SinoCORFinder(
469
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
470
+ method="fourier-angles",
471
+ slice_idx=scan.axis_params.sinogram_line or "middle",
472
+ subsampling=scan.axis_params.sinogram_subsampling,
473
+ do_flatfield=has_darks and has_flats,
474
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
475
+ logger=_logger,
476
+ )
477
+ res = corfinder.find_cor()
478
+ return _absolute_pos_to_relative_with_warning(
479
+ absolute_pos=res, det_width=scan.dim_1
480
+ )
481
+
482
+
483
+ def compute_scan_octave_accurate_radios(
484
+ scan: TomwerScanBase,
485
+ ) -> float | None:
486
+ """
487
+ Call nabu.preproc.alignment.CenterOfRotationGrowingWindow.find_shift
488
+
489
+ :return:
490
+ """
491
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
492
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
493
+
494
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
495
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
496
+
497
+ projection_angles = scan.get_proj_angle_url()
498
+ projection_angles_i = {
499
+ value.path(): key for key, value in projection_angles.items()
500
+ }
501
+ url_radio_1, url_radio_2 = AxisTask.get_inputs_urls(scan=scan)
502
+ angle_radio_1 = float(projection_angles_i[url_radio_1.url.path()])
503
+ angle_radio_2 = float(projection_angles_i[url_radio_2.url.path()])
504
+ radio_angles = tuple(numpy.radians((angle_radio_1, angle_radio_2)))
505
+
506
+ corfinder = CORFinder(
507
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
508
+ method="octave-accurate",
509
+ do_flatfield=has_darks and has_flats,
510
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
511
+ radio_angles=radio_angles,
512
+ logger=_logger,
513
+ )
514
+ res = corfinder.find_cor()
515
+ return _absolute_pos_to_relative_with_warning(
516
+ absolute_pos=res, det_width=scan.dim_1
517
+ )
518
+
519
+
105
520
  def read_scan_x_rotation_axis_pixel_position(scan: TomwerScanBase):
106
521
  """read center of rotation estimation from metadata"""
107
522
  if isinstance(scan, EDFTomoScan):
@@ -115,6 +530,214 @@ def read_scan_x_rotation_axis_pixel_position(scan: TomwerScanBase):
115
530
  return res
116
531
 
117
532
 
533
+ def compute_scan_octave_accurate(scan: TomwerScanBase) -> float | None:
534
+ """
535
+ Compute center of rotation from `octave-accurate` algorithm
536
+ scan
537
+ """
538
+ cor_options = scan.axis_params.get_nabu_cor_options_as_dict()
539
+ radio_1, radio_2 = AxisTask.get_inputs(scan=scan)
540
+ extra_options = {}
541
+ for key in "low_pass", "high_pass":
542
+ if key in cor_options:
543
+ extra_options[key] = float(cor_options[key])
544
+
545
+ corfinder = CenterOfRotationOctaveAccurate()
546
+ res = corfinder.find_shift(
547
+ img_1=radio_1,
548
+ img_2=numpy.fliplr(radio_2) if scan.axis_params.flip_lr else radio_2,
549
+ side=scan.axis_params.side,
550
+ padding_mode=scan.axis_params.padding_mode,
551
+ **extra_options,
552
+ )
553
+ return res
554
+
555
+
556
+ def compute_cor_nabu_centered_radios(
557
+ scan: TomwerScanBase,
558
+ ) -> float | None:
559
+ """
560
+ Call nabu.preproc.alignment.CenterOfRotationGrowingWindow.find_shift
561
+ """
562
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
563
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
564
+
565
+ projection_angles = scan.get_proj_angle_url()
566
+ projection_angles_i = {
567
+ value.path(): key for key, value in projection_angles.items()
568
+ }
569
+ url_radio_1, url_radio_2 = AxisTask.get_inputs_urls(scan=scan)
570
+ angle_radio_1 = float(projection_angles_i[url_radio_1.url.path()])
571
+ angle_radio_2 = float(projection_angles_i[url_radio_2.url.path()])
572
+ radio_angles = tuple(numpy.radians((angle_radio_1, angle_radio_2)))
573
+
574
+ corfinder = CORFinder(
575
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
576
+ method="centered",
577
+ do_flatfield=has_darks and has_flats,
578
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
579
+ radio_angles=radio_angles,
580
+ logger=_logger,
581
+ )
582
+ res = corfinder.find_cor()
583
+ return _absolute_pos_to_relative_with_warning(
584
+ absolute_pos=res, det_width=scan.dim_1
585
+ )
586
+
587
+
588
+ def compute_cor_nabu_centered(
589
+ radio_1: numpy.ndarray,
590
+ radio_2: numpy.ndarray,
591
+ padding_mode,
592
+ flip_frame_2_lr=True,
593
+ horz_fft_width=False,
594
+ vert_fft_width=False,
595
+ ):
596
+ """
597
+ Call nabu.preproc.alignement.CenterOfRotation.find_shift
598
+
599
+ :param radio_1:
600
+ :param radio_2:
601
+ :param padding_mode:
602
+ :param horz_fft_width:
603
+ :param flip_frame_2_lr: if True will left-right flip the second frame
604
+ :param half_acq_cor_guess: The approximate position of the rotation axis
605
+ from the image center. Optional. When given a
606
+ special algorithm is used which can work also
607
+ in half-tomo conditions.
608
+
609
+ :return:
610
+ """
611
+ nabu_class = CenterOfRotation(
612
+ horz_fft_width=horz_fft_width, vert_fft_width=vert_fft_width
613
+ )
614
+ return nabu_class.find_shift(
615
+ img_1=radio_1,
616
+ img_2=numpy.fliplr(radio_2) if flip_frame_2_lr else radio_2,
617
+ roi_yxhw=None,
618
+ padding_mode=padding_mode,
619
+ median_filt_shape=None,
620
+ )
621
+
622
+
623
+ def compute_scan_cor_nabu_centered(scan: TomwerScanBase) -> float | None:
624
+ """
625
+ Call to nabu.preproc.alignment.CenterOfRotation from the scan axis_params
626
+ value.
627
+
628
+ :param scan: scan to process
629
+ """
630
+ assert scan.axis_params is not None
631
+ radio_1, radio_2 = AxisTask.get_inputs(scan=scan)
632
+ if radio_1 is None or radio_2 is None:
633
+ raise NoAxisUrl("Unable to find projections for nabu axis calculation")
634
+
635
+ _logger.info(
636
+ "compute scan axis from nabu CenterOfRotation with padding "
637
+ "mode %s" % scan.axis_params.padding_mode
638
+ )
639
+
640
+ return compute_cor_nabu_centered_radios(scan)
641
+
642
+
643
+ def compute_cor_nabu_global(
644
+ radio_1: numpy.ndarray,
645
+ radio_2: numpy.ndarray,
646
+ padding_mode,
647
+ flip_frame_2_lr=True,
648
+ horz_fft_width=False,
649
+ ):
650
+ """
651
+ Call nabu.preproc.alignement.CenterOfRotation.find_shift
652
+
653
+ :param radio_1:
654
+ :param radio_2:
655
+ :param padding_mode:
656
+ :param horz_fft_width:
657
+ :param flip_frame_2_lr: if True will left-right flip the second frame
658
+ :return:
659
+ """
660
+ nabu_class = CenterOfRotationAdaptiveSearch(horz_fft_width=horz_fft_width)
661
+ return nabu_class.find_shift(
662
+ img_1=radio_1,
663
+ img_2=numpy.fliplr(radio_2) if flip_frame_2_lr else radio_2,
664
+ roi_yxhw=None,
665
+ padding_mode=padding_mode,
666
+ median_filt_shape=None,
667
+ )
668
+
669
+
670
+ def compute_cor_nabu_global_radios(
671
+ scan: TomwerScanBase,
672
+ ) -> float | None:
673
+ """
674
+ Call nabu.preproc.alignement.CenterOfRotationGrowingWindow.find_shift
675
+
676
+ :param scan:
677
+
678
+ :return:
679
+ """
680
+ has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
681
+ has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
682
+
683
+ projection_angles = scan.get_proj_angle_url()
684
+ projection_angles_i = {
685
+ value.path(): key for key, value in projection_angles.items()
686
+ }
687
+ url_radio_1, url_radio_2 = AxisTask.get_inputs_urls(scan=scan)
688
+ angle_radio_1 = float(projection_angles_i[url_radio_1.url.path()])
689
+ angle_radio_2 = float(projection_angles_i[url_radio_2.url.path()])
690
+ radio_angles = tuple(numpy.radians((angle_radio_1, angle_radio_2)))
691
+
692
+ corfinder = CORFinder(
693
+ dataset_info=adapt_tomwer_scan_to_nabu(scan),
694
+ method="global",
695
+ do_flatfield=has_darks and has_flats,
696
+ cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
697
+ radio_angles=radio_angles,
698
+ logger=_logger,
699
+ )
700
+ res = corfinder.find_cor()
701
+ return _absolute_pos_to_relative_with_warning(
702
+ absolute_pos=res, det_width=scan.dim_1
703
+ )
704
+
705
+
706
+ def compute_scan_cor_nabu_global(scan: TomwerScanBase) -> float | None:
707
+ """
708
+ Call to nabu.preproc.alignment.CenterOfRotation from the scan axis_params
709
+ value.
710
+
711
+ :param scan: scan to process
712
+ """
713
+ assert scan.axis_params is not None
714
+ radio_1, radio_2 = AxisTask.get_inputs(scan=scan)
715
+ if radio_1 is None or radio_2 is None:
716
+ raise NoAxisUrl("Unable to find projections for nabu axis calculation")
717
+
718
+ _logger.info(
719
+ "compute scan axis from nabu CenterOfRotation with padding "
720
+ "mode %s" % scan.axis_params.padding_mode
721
+ )
722
+ return compute_cor_nabu_global_radios(scan)
723
+
724
+
725
+ def get_stdmax_column(x: numpy.ndarray) -> float:
726
+ """
727
+
728
+ :return: column index of the maximal standard deviation
729
+ """
730
+ kernel_size = 5
731
+ length = len(x)
732
+ r = range(length - kernel_size)
733
+ y = numpy.empty(length - kernel_size)
734
+ for i in r:
735
+ s = numpy.std(x[i : i + kernel_size])
736
+ y[i] = s
737
+
738
+ return y.argmax()
739
+
740
+
118
741
  class NoAxisUrl(Exception):
119
742
  pass
120
743
 
@@ -130,6 +753,21 @@ class AxisTask(
130
753
  Process used to compute the center of rotation of a scan
131
754
  """
132
755
 
756
+ _CALCULATIONS_METHODS = {
757
+ AxisMode.centered: compute_scan_cor_nabu_centered,
758
+ AxisMode.global_: compute_scan_cor_nabu_global,
759
+ AxisMode.sliding_window_sinogram: compute_scan_cor_nabu_sliding_window,
760
+ AxisMode.sliding_window_radios: compute_scan_cor_nabu_sliding_window,
761
+ AxisMode.growing_window_sinogram: compute_scan_cor_nabu_growing_window,
762
+ AxisMode.growing_window_radios: compute_scan_cor_nabu_growing_window,
763
+ AxisMode.sino_coarse_to_fine: compute_scan_sino_coarse_to_fine,
764
+ AxisMode.composite_coarse_to_fine: compute_scan_composite_coarse_to_fine,
765
+ AxisMode.near: compute_scan_composite_coarse_to_fine,
766
+ AxisMode.sino_fourier_angles: compute_scan_fourier_angles,
767
+ AxisMode.octave_accurate_radios: compute_scan_octave_accurate,
768
+ AxisMode.read: read_scan_x_rotation_axis_pixel_position,
769
+ }
770
+
133
771
  def __init__(
134
772
  self,
135
773
  process_id=None,
@@ -304,12 +942,12 @@ class AxisTask(
304
942
  _logger.error(e)
305
943
 
306
944
  @staticmethod
307
- def get_input_radios(scan) -> tuple:
945
+ def get_inputs_urls(scan):
308
946
  """Make sure we have valid projections to be used for axis calculation
309
947
 
310
948
  :param scan: scan to check
311
949
  :raise: NoAxisUrl if fails to found
312
- :return: tuple of AxisResource
950
+ :return: the two projections to be used for axis calculation
313
951
  """
314
952
  if (
315
953
  scan.axis_params
@@ -317,13 +955,16 @@ class AxisTask(
317
955
  and scan.axis_params.axis_url_1.url
318
956
  ):
319
957
  return scan.axis_params.axis_url_1, scan.axis_params.axis_url_2
320
-
321
- return scan.get_opposite_projections(mode=scan.axis_params.angle_mode)
958
+ else:
959
+ _radio_1, _radio_2 = scan.get_opposite_projections(
960
+ mode=scan.axis_params.angle_mode
961
+ )
962
+ return _radio_1, _radio_2
322
963
 
323
964
  @staticmethod
324
965
  def get_inputs(scan):
325
966
  assert isinstance(scan, TomwerScanBase)
326
- radio_1, radio_2 = AxisTask.get_input_radios(scan=scan)
967
+ radio_1, radio_2 = AxisTask.get_inputs_urls(scan=scan)
327
968
  if radio_1 and radio_2:
328
969
  mess = " ".join(
329
970
  ("input radios are", radio_1.url.path(), "and", radio_2.url.path())
@@ -406,8 +1047,14 @@ class AxisTask(
406
1047
  self._axis_params.frame_width = scan.dim_1
407
1048
  self._axis_params.set_relative_value(position)
408
1049
  scan_name = scan.path or "undef scan"
1050
+ if scan.axis_params.use_sinogram:
1051
+ method = "sinogram"
1052
+ else:
1053
+ method = scan.axis_params.mode.value
409
1054
  r_cor_value = scan.axis_params.relative_cor_value
410
- mess = f"Compute axis position ({r_cor_value}) with {scan.axis_params.mode.value}"
1055
+ mess = (
1056
+ f"Compute axis position ({r_cor_value}) with {method} for {scan_name}"
1057
+ )
411
1058
  _logger.info(mess)
412
1059
  return scan
413
1060
 
@@ -430,30 +1077,18 @@ class AxisTask(
430
1077
 
431
1078
  :param scan: scan for which we compute the center of rotation
432
1079
  :return: position of the rotation axis. Use the `.AxisMode` defined
433
- by the `.ReconsParams` of the `.AxisTask`
1080
+ by the `.ReconsParams` of the `.AxisProcess`
434
1081
  """
435
1082
  mode = self._axis_params.mode
436
- if mode is AxisMode.manual:
1083
+ if mode in (AxisMode.manual,):
437
1084
  # If mode is read or manual the position_value is not computed and
438
1085
  # we will keep the actual one (should have been defined previously)
439
1086
  res = self._axis_params.relative_cor_value
440
- elif mode is AxisMode.read:
441
- res = read_scan_x_rotation_axis_pixel_position(scan=scan)
1087
+ elif mode in self._CALCULATIONS_METHODS:
1088
+ _logger.info("use radios, mode is %s" % mode.value)
1089
+ res = self._CALCULATIONS_METHODS[mode](scan)
442
1090
  else:
443
- has_darks = scan.reduced_darks is not None and len(scan.reduced_darks) > 0
444
- has_flats = scan.reduced_flats is not None and len(scan.reduced_flats) > 0
445
-
446
- res = estimate_cor(
447
- method=mode.value,
448
- dataset_info=adapt_tomwer_scan_to_nabu(scan=scan),
449
- do_flatfield=has_darks and has_flats,
450
- cor_options=scan.axis_params.get_nabu_cor_options_as_dict(),
451
- )
452
- # convert back to relative
453
- res = _absolute_pos_to_relative_with_warning(
454
- absolute_pos=res, det_width=scan.dim_1
455
- )
456
-
1091
+ raise NotImplementedError("Method for", mode, "is not defined")
457
1092
  return res
458
1093
 
459
1094
  @docstring(Task.program_name)
@@ -470,3 +1105,77 @@ class AxisTask(
470
1105
  @staticmethod
471
1106
  def definition():
472
1107
  return "Compute center of rotation"
1108
+
1109
+ @staticmethod
1110
+ def get_cor_frm_process_file(process_file, entry, as_url=False) -> float | None:
1111
+ """
1112
+ Read cor position from a tomwer_process file
1113
+
1114
+ :param process_file:
1115
+ :param entry:
1116
+ :return:
1117
+ """
1118
+ if entry is None:
1119
+ with open_hdf5(process_file) as h5f:
1120
+ entries = AxisTask._get_process_nodes(root_node=h5f, process=AxisTask)
1121
+ if len(entries) == 0:
1122
+ _logger.info("unable to find a Axis process in %s" % process_file)
1123
+ return None
1124
+ elif len(entries) > 1:
1125
+ raise ValueError("several entry found, entry should be " "specify")
1126
+ else:
1127
+ entry = list(entries.keys())[0]
1128
+ _logger.info("take %s as default entry" % entry)
1129
+
1130
+ with open_hdf5(process_file) as h5f:
1131
+ axis_nodes = AxisTask._get_process_nodes(
1132
+ root_node=h5f[entry], process=AxisTask
1133
+ )
1134
+ index_to_path = {}
1135
+ for key, index in axis_nodes.items():
1136
+ index_to_path[index] = key
1137
+
1138
+ if len(axis_nodes) == 0:
1139
+ return None
1140
+ # take the last processed dark ref
1141
+ last_process_index = sorted(list(axis_nodes.values()))[-1]
1142
+ last_process_dark = index_to_path[last_process_index]
1143
+ if (len(index_to_path)) > 1:
1144
+ _logger.debug(
1145
+ "several processing found for dark-ref,"
1146
+ "take the last one: %s" % last_process_dark
1147
+ )
1148
+
1149
+ res = None
1150
+ if "results" in h5f[last_process_dark].keys():
1151
+ results_node = h5f[last_process_dark]["results"]
1152
+ if "center_of_rotation" in results_node.keys():
1153
+ if as_url:
1154
+ res = DataUrl(
1155
+ file_path=process_file,
1156
+ data_path="/".join((results_node, "center_of_rotation")),
1157
+ scheme="h5py",
1158
+ )
1159
+ else:
1160
+ res = h5py_read_dataset(results_node["center_of_rotation"])
1161
+ return res
1162
+
1163
+
1164
+ class AxisProcess(AxisTask):
1165
+ def __init__(
1166
+ self,
1167
+ process_id=None,
1168
+ varinfo=None,
1169
+ inputs=None,
1170
+ node_id=None,
1171
+ node_attrs=None,
1172
+ execinfo=None,
1173
+ ):
1174
+ deprecated_warning(
1175
+ name="tomwer.core.process.reconstruction.axis.axis.AxisProcess",
1176
+ type_="class",
1177
+ reason="improve readibility",
1178
+ since_version="1.2",
1179
+ replacement="AxisTask",
1180
+ )
1181
+ super().__init__(process_id, varinfo, inputs, node_id, node_attrs, execinfo)