nabu 2023.2.1__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.
Files changed (183) hide show
  1. doc/conf.py +1 -1
  2. doc/doc_config.py +32 -0
  3. nabu/__init__.py +2 -1
  4. nabu/app/bootstrap_stitching.py +1 -1
  5. nabu/app/cli_configs.py +122 -2
  6. nabu/app/composite_cor.py +27 -2
  7. nabu/app/correct_rot.py +70 -0
  8. nabu/app/create_distortion_map_from_poly.py +42 -18
  9. nabu/app/diag_to_pix.py +358 -0
  10. nabu/app/diag_to_rot.py +449 -0
  11. nabu/app/generate_header.py +4 -3
  12. nabu/app/histogram.py +2 -2
  13. nabu/app/multicor.py +6 -1
  14. nabu/app/parse_reconstruction_log.py +151 -0
  15. nabu/app/prepare_weights_double.py +83 -22
  16. nabu/app/reconstruct.py +5 -1
  17. nabu/app/reconstruct_helical.py +7 -0
  18. nabu/app/reduce_dark_flat.py +6 -3
  19. nabu/app/rotate.py +4 -4
  20. nabu/app/stitching.py +16 -2
  21. nabu/app/tests/test_reduce_dark_flat.py +18 -2
  22. nabu/app/validator.py +4 -4
  23. nabu/cuda/convolution.py +8 -376
  24. nabu/cuda/fft.py +4 -0
  25. nabu/cuda/kernel.py +4 -4
  26. nabu/cuda/medfilt.py +5 -158
  27. nabu/cuda/padding.py +5 -71
  28. nabu/cuda/processing.py +23 -2
  29. nabu/cuda/src/ElementOp.cu +78 -0
  30. nabu/cuda/src/backproj.cu +28 -2
  31. nabu/cuda/src/fourier_wavelets.cu +2 -2
  32. nabu/cuda/src/normalization.cu +23 -0
  33. nabu/cuda/src/padding.cu +2 -2
  34. nabu/cuda/src/transpose.cu +16 -0
  35. nabu/cuda/utils.py +39 -0
  36. nabu/estimation/alignment.py +10 -1
  37. nabu/estimation/cor.py +808 -38
  38. nabu/estimation/cor_sino.py +7 -9
  39. nabu/estimation/tests/test_cor.py +85 -3
  40. nabu/io/reader.py +26 -18
  41. nabu/io/tests/test_cast_volume.py +3 -3
  42. nabu/io/tests/test_detector_distortion.py +3 -3
  43. nabu/io/tiffwriter_zmm.py +2 -2
  44. nabu/io/utils.py +14 -4
  45. nabu/io/writer.py +5 -3
  46. nabu/misc/fftshift.py +6 -0
  47. nabu/misc/histogram.py +5 -285
  48. nabu/misc/histogram_cuda.py +8 -104
  49. nabu/misc/kernel_base.py +3 -121
  50. nabu/misc/padding_base.py +5 -69
  51. nabu/misc/processing_base.py +3 -107
  52. nabu/misc/rotation.py +5 -62
  53. nabu/misc/rotation_cuda.py +5 -65
  54. nabu/misc/transpose.py +6 -0
  55. nabu/misc/unsharp.py +3 -78
  56. nabu/misc/unsharp_cuda.py +5 -52
  57. nabu/misc/unsharp_opencl.py +8 -85
  58. nabu/opencl/fft.py +6 -0
  59. nabu/opencl/kernel.py +21 -6
  60. nabu/opencl/padding.py +5 -72
  61. nabu/opencl/processing.py +27 -5
  62. nabu/opencl/src/backproj.cl +3 -3
  63. nabu/opencl/src/fftshift.cl +65 -12
  64. nabu/opencl/src/padding.cl +2 -2
  65. nabu/opencl/src/roll.cl +96 -0
  66. nabu/opencl/src/transpose.cl +16 -0
  67. nabu/pipeline/config_validators.py +63 -3
  68. nabu/pipeline/dataset_validator.py +2 -2
  69. nabu/pipeline/estimators.py +193 -35
  70. nabu/pipeline/fullfield/chunked.py +34 -17
  71. nabu/pipeline/fullfield/chunked_cuda.py +7 -5
  72. nabu/pipeline/fullfield/computations.py +48 -13
  73. nabu/pipeline/fullfield/nabu_config.py +13 -13
  74. nabu/pipeline/fullfield/processconfig.py +10 -5
  75. nabu/pipeline/fullfield/reconstruction.py +1 -2
  76. nabu/pipeline/helical/fbp.py +5 -0
  77. nabu/pipeline/helical/filtering.py +12 -9
  78. nabu/pipeline/helical/gridded_accumulator.py +179 -33
  79. nabu/pipeline/helical/helical_chunked_regridded.py +262 -151
  80. nabu/pipeline/helical/helical_chunked_regridded_cuda.py +4 -11
  81. nabu/pipeline/helical/helical_reconstruction.py +56 -18
  82. nabu/pipeline/helical/span_strategy.py +1 -1
  83. nabu/pipeline/helical/tests/test_accumulator.py +4 -0
  84. nabu/pipeline/params.py +23 -2
  85. nabu/pipeline/processconfig.py +3 -8
  86. nabu/pipeline/tests/test_chunk_reader.py +78 -0
  87. nabu/pipeline/tests/test_estimators.py +120 -2
  88. nabu/pipeline/utils.py +25 -0
  89. nabu/pipeline/writer.py +2 -0
  90. nabu/preproc/ccd_cuda.py +9 -7
  91. nabu/preproc/ctf.py +21 -26
  92. nabu/preproc/ctf_cuda.py +25 -25
  93. nabu/preproc/double_flatfield.py +14 -2
  94. nabu/preproc/double_flatfield_cuda.py +7 -11
  95. nabu/preproc/flatfield_cuda.py +23 -27
  96. nabu/preproc/phase.py +19 -24
  97. nabu/preproc/phase_cuda.py +21 -21
  98. nabu/preproc/shift_cuda.py +58 -28
  99. nabu/preproc/tests/test_ctf.py +5 -5
  100. nabu/preproc/tests/test_double_flatfield.py +2 -2
  101. nabu/preproc/tests/test_vshift.py +13 -2
  102. nabu/processing/__init__.py +0 -0
  103. nabu/processing/convolution_cuda.py +375 -0
  104. nabu/processing/fft_base.py +163 -0
  105. nabu/processing/fft_cuda.py +256 -0
  106. nabu/processing/fft_opencl.py +54 -0
  107. nabu/processing/fftshift.py +134 -0
  108. nabu/processing/histogram.py +286 -0
  109. nabu/processing/histogram_cuda.py +103 -0
  110. nabu/processing/kernel_base.py +126 -0
  111. nabu/processing/medfilt_cuda.py +159 -0
  112. nabu/processing/muladd.py +29 -0
  113. nabu/processing/muladd_cuda.py +68 -0
  114. nabu/processing/padding_base.py +71 -0
  115. nabu/processing/padding_cuda.py +75 -0
  116. nabu/processing/padding_opencl.py +77 -0
  117. nabu/processing/processing_base.py +123 -0
  118. nabu/processing/roll_opencl.py +64 -0
  119. nabu/processing/rotation.py +63 -0
  120. nabu/processing/rotation_cuda.py +66 -0
  121. nabu/processing/tests/__init__.py +0 -0
  122. nabu/processing/tests/test_fft.py +268 -0
  123. nabu/processing/tests/test_fftshift.py +71 -0
  124. nabu/{misc → processing}/tests/test_histogram.py +2 -4
  125. nabu/{cuda → processing}/tests/test_medfilt.py +1 -1
  126. nabu/processing/tests/test_muladd.py +54 -0
  127. nabu/{cuda → processing}/tests/test_padding.py +119 -75
  128. nabu/processing/tests/test_roll.py +63 -0
  129. nabu/{misc → processing}/tests/test_rotation.py +3 -2
  130. nabu/processing/tests/test_transpose.py +72 -0
  131. nabu/{misc → processing}/tests/test_unsharp.py +41 -8
  132. nabu/processing/transpose.py +126 -0
  133. nabu/processing/unsharp.py +79 -0
  134. nabu/processing/unsharp_cuda.py +53 -0
  135. nabu/processing/unsharp_opencl.py +75 -0
  136. nabu/reconstruction/fbp.py +34 -10
  137. nabu/reconstruction/fbp_base.py +35 -16
  138. nabu/reconstruction/fbp_opencl.py +7 -12
  139. nabu/reconstruction/filtering.py +2 -2
  140. nabu/reconstruction/filtering_cuda.py +13 -14
  141. nabu/reconstruction/filtering_opencl.py +3 -4
  142. nabu/reconstruction/projection.py +2 -0
  143. nabu/reconstruction/rings.py +158 -1
  144. nabu/reconstruction/rings_cuda.py +218 -58
  145. nabu/reconstruction/sinogram_cuda.py +16 -12
  146. nabu/reconstruction/tests/test_deringer.py +116 -14
  147. nabu/reconstruction/tests/test_fbp.py +22 -31
  148. nabu/reconstruction/tests/test_filtering.py +11 -2
  149. nabu/resources/dataset_analyzer.py +89 -26
  150. nabu/resources/nxflatfield.py +2 -2
  151. nabu/resources/tests/test_nxflatfield.py +1 -1
  152. nabu/resources/utils.py +9 -2
  153. nabu/stitching/alignment.py +184 -0
  154. nabu/stitching/config.py +241 -39
  155. nabu/stitching/definitions.py +6 -0
  156. nabu/stitching/frame_composition.py +4 -2
  157. nabu/stitching/overlap.py +99 -3
  158. nabu/stitching/sample_normalization.py +60 -0
  159. nabu/stitching/slurm_utils.py +10 -10
  160. nabu/stitching/tests/test_alignment.py +99 -0
  161. nabu/stitching/tests/test_config.py +16 -1
  162. nabu/stitching/tests/test_overlap.py +68 -2
  163. nabu/stitching/tests/test_sample_normalization.py +49 -0
  164. nabu/stitching/tests/test_slurm_utils.py +5 -5
  165. nabu/stitching/tests/test_utils.py +3 -33
  166. nabu/stitching/tests/test_z_stitching.py +391 -22
  167. nabu/stitching/utils.py +144 -202
  168. nabu/stitching/z_stitching.py +309 -126
  169. nabu/testutils.py +18 -0
  170. nabu/thirdparty/tomocupy_remove_stripe.py +586 -0
  171. nabu/utils.py +32 -6
  172. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/LICENSE +1 -1
  173. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/METADATA +5 -5
  174. nabu-2024.1.0rc3.dist-info/RECORD +296 -0
  175. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/WHEEL +1 -1
  176. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/entry_points.txt +5 -1
  177. nabu/conftest.py +0 -14
  178. nabu/opencl/fftshift.py +0 -92
  179. nabu/opencl/tests/test_fftshift.py +0 -55
  180. nabu/opencl/tests/test_padding.py +0 -84
  181. nabu-2023.2.1.dist-info/RECORD +0 -252
  182. /nabu/cuda/src/{fftshift.cu → dfi_fftshift.cu} +0 -0
  183. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,6 @@
1
1
  # pylint: skip-file
2
2
 
3
3
  from os import path
4
- from time import time
5
4
  import numpy as np
6
5
  import math
7
6
  from silx.image.tomography import get_next_power
@@ -11,19 +10,18 @@ import silx.io
11
10
  import copy
12
11
  from silx.io.url import DataUrl
13
12
  from ...resources.logger import LoggerOrPrint
14
- from ...resources.utils import is_hdf5_extension, extract_parameters
13
+ from ...resources.utils import is_hdf5_extension
15
14
  from ...io.reader_helical import ChunkReaderHelical, get_hdf5_dataset_shape
16
15
  from ...preproc.flatfield_variable_region import FlatFieldDataVariableRegionUrls as FlatFieldDataHelicalUrls
17
16
  from ...preproc.distortion import DistortionCorrection
18
17
  from ...preproc.shift import VerticalShift
19
18
  from ...preproc.double_flatfield_variable_region import DoubleFlatFieldVariableRegion as DoubleFlatFieldHelical
20
19
  from ...preproc.phase import PaganinPhaseRetrieval
21
- from ...reconstruction.sinogram import SinoBuilder, SinoNormalization
22
- from ...misc.unsharp import UnsharpMask
23
- from ...misc.histogram import PartialHistogram, hist_as_2Darray
20
+ from ...reconstruction.sinogram import SinoBuilder
21
+ from ...processing.unsharp import UnsharpMask
22
+ from ...processing.histogram import PartialHistogram, hist_as_2Darray
24
23
  from ..utils import use_options, pipeline_step
25
24
 
26
- from ...resources.utils import extract_parameters
27
25
  from ..detector_distortion_provider import DetectorDistortionProvider
28
26
 
29
27
  from .utils import (
@@ -36,9 +34,26 @@ from numpy.lib.stride_tricks import sliding_window_view
36
34
  from ...misc.binning import get_binning_function
37
35
  from .helical_utils import find_mirror_indexes
38
36
 
39
- from ...preproc.ccd import Log, CCDFilter
40
37
 
41
- from . import gridded_accumulator
38
+ try:
39
+ import nabuxx
40
+
41
+ GriddedAccumulator = nabuxx.gridded_accumulator.GriddedAccumulator
42
+ CCDFilter = nabuxx.ccd.CCDFilter
43
+ Log = nabuxx.ccd.LogFilter
44
+ cxx_paganin = nabuxx.paganin
45
+ except:
46
+ logger_tmp = LoggerOrPrint(None)
47
+ logger_tmp.info(
48
+ "Nabuxx not available. Loading python implementation for gridded_accumulator, Log, CCDFilter, paganin"
49
+ )
50
+ from . import gridded_accumulator
51
+
52
+ GriddedAccumulator = gridded_accumulator.GriddedAccumulator
53
+ from ...preproc.ccd import Log, CCDFilter
54
+
55
+ cxx_paganin = None
56
+
42
57
 
43
58
  # For now we don't have a plain python/numpy backend for reconstruction
44
59
  Backprojector = None
@@ -81,6 +96,7 @@ class HelicalChunkedRegriddedPipeline:
81
96
  phase_margin=None,
82
97
  reading_granularity=10,
83
98
  span_info=None,
99
+ diag_zpro_run=0,
84
100
  ):
85
101
  """
86
102
  Initialize a "HelicalChunked" pipeline.
@@ -119,11 +135,12 @@ class HelicalChunkedRegriddedPipeline:
119
135
 
120
136
  self.logger = LoggerOrPrint(logger)
121
137
 
122
- self._set_params(process_config, sub_region, extra_options, phase_margin)
138
+ self._set_params(process_config, sub_region, extra_options, phase_margin, diag_zpro_run)
123
139
 
124
140
  self._init_pipeline()
125
141
 
126
- def _set_params(self, process_config, sub_region, extra_options, phase_margin):
142
+ def _set_params(self, process_config, sub_region, extra_options, phase_margin, diag_zpro_run):
143
+ self.diag_zpro_run = diag_zpro_run
127
144
  self.process_config = process_config
128
145
  self.dataset_info = self.process_config.dataset_info
129
146
 
@@ -443,23 +460,54 @@ class HelicalChunkedRegriddedPipeline:
443
460
 
444
461
  ### these radios are for diagnostic of the translations ( they will be optionally written, for being further used
445
462
  ## by correlation techniques ). Two radios for the first two pass over the first gridded angles
446
- self.diagnostic_radios = np.zeros((2,) + subradio_shape, np.float32)
447
- self.diagnostic_weights = np.zeros((2,) + subradio_shape, np.float32)
448
- self.diagnostic_proj_angle = np.zeros([2], "f")
463
+ if self.diag_zpro_run:
464
+ # 2 for the redundancy, 2 for +180 mirror
465
+ ndiag = 2 * 2 * self.diag_zpro_run
466
+ else:
467
+ ndiag = 2 * 2
468
+
469
+ self.diagnostic_searched_angles_rad_clipped = (
470
+ (0.5 + np.arange(ndiag // 2)) * (2 * np.pi / (ndiag // 2))
471
+ ).astype("f")
472
+
473
+ self.diagnostic_radios = np.zeros((ndiag,) + subradio_shape, np.float32)
474
+ self.diagnostic_weights = np.zeros((ndiag,) + subradio_shape, np.float32)
475
+ self.diagnostic_proj_angle = np.zeros([ndiag], "f")
476
+ self.diagnostic_zpix_transl = np.zeros([ndiag], "f")
477
+ self.diagnostic_zmm_transl = np.zeros([ndiag], "f")
478
+
449
479
  self.diagnostic = {
450
480
  "radios": self.diagnostic_radios,
451
481
  "weights": self.diagnostic_weights,
452
482
  "angles": self.diagnostic_proj_angle,
483
+ "zpix_transl": self.diagnostic_zpix_transl,
484
+ "zmm_trans": self.diagnostic_zmm_transl,
485
+ "pixel_size_mm": self.span_info.pix_size_mm,
486
+ "searched_rad": self.diagnostic_searched_angles_rad_clipped,
453
487
  }
454
488
  ## -------
455
- self.gridded_radios = np.zeros((self.n_gridded_angles,) + subradio_shape, np.float32)
456
- self.gridded_cumulated_weights = np.zeros((self.n_gridded_angles,) + subradio_shape, np.float32)
489
+ if self.diag_zpro_run == 0:
490
+ self.gridded_radios = np.zeros((self.n_gridded_angles,) + subradio_shape, np.float32)
491
+ self.gridded_cumulated_weights = np.zeros((self.n_gridded_angles,) + subradio_shape, np.float32)
492
+ else:
493
+ # only diagnostic will be cumulated. No need to keep the full size for diagnostic runs.
494
+ # The gridder is initialised passing also the two buffer below,
495
+ # and the two first dimensions are used to allocate auxiliaries,
496
+ # so we shorten only the last dimension, but this is already a good cut
497
+ self.gridded_radios = np.zeros((self.n_gridded_angles,) + (subradio_shape[0], 2), np.float32)
498
+ self.gridded_cumulated_weights = np.zeros((self.n_gridded_angles,) + (subradio_shape[0], 2), np.float32)
499
+
457
500
  self.radios_subset = np.zeros((self.reading_granularity,) + subradio_shape, np.float32)
458
501
  self.radios_weights_subset = np.zeros((self.reading_granularity,) + subradio_shape, np.float32)
459
502
 
460
- self.radios = np.zeros(
461
- (self.n_gridded_angles,) + ((end_z - down_margin) - (start_z + up_margin), shp_h), np.float32
462
- )
503
+ if not self.diag_zpro_run:
504
+ self.radios = np.zeros(
505
+ (self.n_gridded_angles,) + ((end_z - down_margin) - (start_z + up_margin), shp_h), np.float32
506
+ )
507
+ else:
508
+ # place holder
509
+ self.radios = np.zeros((self.n_gridded_angles,) + (1, 1), np.float32)
510
+
463
511
  self.radios_weights = np.zeros_like(self.radios)
464
512
 
465
513
  self.radios_slim = self._allocate_array(self._get_shape("one_sino_slim"), "f", name="radios_slim")
@@ -479,6 +527,13 @@ class HelicalChunkedRegriddedPipeline:
479
527
  # Pipeline initialization
480
528
  #
481
529
 
530
+ def _reset_diagnostics(self):
531
+ self.diagnostic_radios[:] = 0
532
+ self.diagnostic_weights[:] = 0
533
+ self.diagnostic_zpix_transl[:] = 0
534
+ self.diagnostic_zmm_transl[:] = 0
535
+ self.diagnostic_proj_angle[:] = np.nan
536
+
482
537
  def _init_pipeline(self):
483
538
  self._get_size_of_a_raw_radio()
484
539
 
@@ -514,19 +569,35 @@ class HelicalChunkedRegriddedPipeline:
514
569
  )
515
570
 
516
571
  def _configure_regular_accumulator(self):
517
- accumulator_cls = gridded_accumulator.GriddedAccumulator
572
+ ##
573
+ # keeping these freshly numpyed objects referenced by self
574
+ # ensures that their buffer info, conserved by c++ implementation of GriddedAccumulator
575
+ # will always point to existing data, which could otherwise be garbage collected by python
576
+ #
577
+
578
+ if self.process_config.nabu_config["preproc"]["normalize_srcurrent"]:
579
+ self.radios_srcurrent = np.array(self.dataset_info.projections_srcurrent, "f")
580
+ self.flats_srcurrent = np.array(self.dataset_info.flats_srcurrent, "f")
581
+ else:
582
+ self.radios_srcurrent = None
583
+ self.flats_srcurrent = None
518
584
 
519
- self.regular_accumulator = accumulator_cls(
585
+ self.regular_accumulator = GriddedAccumulator(
520
586
  gridded_radios=self.gridded_radios,
521
587
  gridded_weights=self.gridded_cumulated_weights,
522
588
  diagnostic_radios=self.diagnostic_radios,
523
589
  diagnostic_weights=self.diagnostic_weights,
524
590
  diagnostic_angles=self.diagnostic_proj_angle,
591
+ diagnostic_zpix_transl=self.diagnostic_zpix_transl,
592
+ diagnostic_searched_angles_rad_clipped=self.diagnostic_searched_angles_rad_clipped,
525
593
  dark=self.flatfield.get_dark(),
526
594
  flat_indexes=self.flatfield._sorted_flat_indices,
527
595
  flats=self.flatfield.flats_stack,
528
596
  weights=self.weights_field.data,
529
597
  double_flat=self.double_flatfield.data,
598
+ diag_zpro_run=self.diag_zpro_run,
599
+ radios_srcurrent=self.radios_srcurrent,
600
+ flats_srcurrent=self.flats_srcurrent,
530
601
  )
531
602
 
532
603
  return
@@ -689,7 +760,7 @@ class HelicalChunkedRegriddedPipeline:
689
760
  pixel_size=options["pixel_size_m"],
690
761
  padding=options["padding_type"],
691
762
  margin=margin,
692
- fftw_num_threads=True, # TODO tune in advanced params of nabu config file
763
+ fft_num_threads=True, # TODO tune in advanced params of nabu config file
693
764
  )
694
765
  if self.phase_retrieval.use_fftw:
695
766
  self.logger.debug(
@@ -752,8 +823,13 @@ class HelicalChunkedRegriddedPipeline:
752
823
  x_s, x_e = options["start_x"], options["end_x"]
753
824
  y_s, y_e = options["start_y"], options["end_y"]
754
825
 
755
- self._rec_roi = (x_s, x_e + 1, y_s, y_e + 1)
756
- self._allocate_recs(y_e - y_s + 1, x_e - x_s + 1)
826
+ if not self.diag_zpro_run:
827
+ self._rec_roi = (x_s, x_e + 1, y_s, y_e + 1)
828
+ self._allocate_recs(y_e - y_s + 1, x_e - x_s + 1)
829
+ else:
830
+ ## Dummy 1x1 place holder
831
+ self._rec_roi = (x_s, x_s + 1, y_s, y_s + 1)
832
+ self._allocate_recs(y_s - y_s + 1, x_s - x_s + 1)
757
833
 
758
834
  @use_options("reconstruction", "reconstruction")
759
835
  def _init_reconstruction(self):
@@ -769,7 +845,7 @@ class HelicalChunkedRegriddedPipeline:
769
845
 
770
846
  start_y, end_y, start_x, end_x = self._rec_roi
771
847
 
772
- if self.HBPClass is not None:
848
+ if self.HBPClass is not None and self.process_config.nabu_config["reconstruction"]["use_hbp"]:
773
849
  fan_source_distance_meters = self.process_config.nabu_config["reconstruction"]["fan_source_distance_meters"]
774
850
 
775
851
  self.reconstruction_hbp = self.HBPClass(
@@ -780,7 +856,8 @@ class HelicalChunkedRegriddedPipeline:
780
856
  extra_options={"axis_correction": np.zeros(self.radios.shape[0], "f")},
781
857
  axis_source_meters=fan_source_distance_meters,
782
858
  voxel_size_microns=options["voxel_size_cm"][0] * 1.0e4,
783
- scale_factor=1.0 / options["voxel_size_cm"][0],
859
+ scale_factor=2.0 / options["voxel_size_cm"][0],
860
+ clip_outer_circle=options["clip_outer_circle"],
784
861
  )
785
862
 
786
863
  else:
@@ -793,10 +870,10 @@ class HelicalChunkedRegriddedPipeline:
793
870
  filter_name=options["fbp_filter_type"],
794
871
  slice_roi=self._rec_roi,
795
872
  # slice_shape = ( end_y-start_y, end_x- start_x ),
796
- scale_factor=1.0 / options["voxel_size_cm"][0],
873
+ scale_factor=2.0 / options["voxel_size_cm"][0],
797
874
  padding_mode=options["padding_type"],
798
875
  extra_options={
799
- "scale_factor": 1.0 / options["voxel_size_cm"][0],
876
+ "scale_factor": 2.0 / options["voxel_size_cm"][0],
800
877
  "axis_correction": np.zeros(self.radios.shape[0], "f"),
801
878
  "clip_outer_circle": options["clip_outer_circle"],
802
879
  }, # "padding_mode": options["padding_type"], },
@@ -850,9 +927,10 @@ class HelicalChunkedRegriddedPipeline:
850
927
  "entry": entry,
851
928
  }
852
929
  self._histogram_processing_index = nx_info["processing_index"] + 1
853
- elif options["file_format"] in ["tif", "tiff"]:
930
+ elif options["file_format"] in ["tif", "tiff", "edf"]:
854
931
  fname_start_index = self._get_slice_start_index()
855
932
  self._histogram_processing_index = 1
933
+
856
934
  self._writer_configurator = WriterConfigurator(
857
935
  output_dir,
858
936
  file_prefix,
@@ -884,94 +962,57 @@ class HelicalChunkedRegriddedPipeline:
884
962
  result_slice = slice(start, stop, step)
885
963
  return result_slice
886
964
 
887
- def _extract_preprocess_with_flats(self, sub_total_prange_slice, subchunk_slice, chunk_info, output):
888
- """Read, and apply dark+ff to, a small angular domain corresponding to the slice argument sub_total_prange_slice
889
- without refilling the holes.
890
-
891
- """
892
-
893
- if self.chunk_reader.dataset_subsampling > 1:
894
- subsampling_file_slice = self._expand_slice(sub_total_prange_slice)
895
- else:
896
- subsampling_file_slice = sub_total_prange_slice
897
-
965
+ def _read_data_and_apply_flats(self, sub_total_prange_slice, subchunk_slice, chunk_info):
898
966
  my_integer_shifts_v = chunk_info.integer_shift_v[subchunk_slice]
899
967
  fract_complement_shifts_v = chunk_info.fract_complement_to_integer_shift_v[subchunk_slice]
900
-
901
968
  x_shifts_list = chunk_info.x_pix_per_proj[subchunk_slice]
902
-
903
969
  (subr_start_x, subr_end_x, subr_start_z, subr_end_z) = self.sub_region
904
970
  subr_start_z_list = subr_start_z - my_integer_shifts_v
905
971
  subr_end_z_list = subr_end_z - my_integer_shifts_v + 1
906
- floating_start_z = subr_start_z_list.min()
907
- floating_end_z = subr_end_z_list.max()
908
-
909
- floating_subregion = None, None, floating_start_z, floating_end_z
910
-
911
- self._reset_reader_subregion(floating_subregion)
912
-
913
- self.chunk_reader.load_data(overwrite=True, sub_total_prange_slice=sub_total_prange_slice)
914
- my_indexes = self.chunk_reader._sorted_files_indices[subsampling_file_slice]
915
- data_raw = self.chunk_reader.data[: len(my_indexes)]
916
972
 
917
- if (self.flatfield is not None) or (self.double_flatfield is not None):
918
- sub_regions_per_radio = [self.trimmed_floating_subregion] * len(my_indexes)
919
-
920
- if self.flatfield is not None:
921
- self.flatfield.normalize_radios(data_raw, my_indexes, sub_regions_per_radio)
973
+ self._reset_reader_subregion((None, None, subr_start_z_list.min(), subr_end_z_list.max()))
922
974
 
923
- if self.double_flatfield is not None:
924
- self.double_flatfield.apply_double_flatfield_for_sub_regions(data_raw, sub_regions_per_radio)
975
+ dtasrc_start_x, dtasrc_end_x, dtasrc_start_z, dtasrc_end_z = self.trimmed_floating_subregion
925
976
 
926
- source_start_x, source_end_x, source_start_z, sources_end_z = self.trimmed_floating_subregion
977
+ if self.diag_zpro_run:
978
+ searched_angles = self.diagnostic_searched_angles_rad_clipped
927
979
 
928
- if self.weights_field is not None:
929
- data_weight = self.weights_field.data[source_start_z:sources_end_z]
930
- else:
931
- data_weight = None
932
-
933
- for data_read, list_subr_start_z, list_subr_end_z, fract_shit, x_shift, data_target in zip(
934
- data_raw, subr_start_z_list, subr_end_z_list, fract_complement_shifts_v, x_shifts_list, output
935
- ):
936
- _fill_in_chunk_by_shift_crop_data(
937
- data_target,
938
- data_read,
939
- fract_shit,
940
- list_subr_start_z,
941
- list_subr_end_z,
942
- source_start_z,
943
- sources_end_z,
944
- x_shift=x_shift,
945
- )
946
-
947
- def _read_data_and_apply_flats(self, sub_total_prange_slice, subchunk_slice, chunk_info):
948
- my_integer_shifts_v = chunk_info.integer_shift_v[subchunk_slice]
949
- fract_complement_shifts_v = chunk_info.fract_complement_to_integer_shift_v[subchunk_slice]
950
- x_shifts_list = chunk_info.x_pix_per_proj[subchunk_slice]
951
- (subr_start_x, subr_end_x, subr_start_z, subr_end_z) = self.sub_region
952
- subr_start_z_list = subr_start_z - my_integer_shifts_v
953
- subr_end_z_list = subr_end_z - my_integer_shifts_v + 1
980
+ these_angles = chunk_info.angles_rad[subchunk_slice]
981
+ if len(these_angles) > 1:
982
+ # these_angles are the projection angles
983
+ # if no diagnostic angle falls close to them we skip to the next angular subchunk
984
+ # (here slice refers to angular slicing)
985
+ # We like hdf5 but we that is not a reason to read them all the time, so we spare time
954
986
 
955
- self._reset_reader_subregion((None, None, subr_start_z_list.min(), subr_end_z_list.max()))
987
+ a_step = abs(these_angles[1:] - these_angles[:-1]).mean()
988
+ distance = abs(np.mod(these_angles, np.pi * 2) - searched_angles[:, None]).min()
989
+ distance_l = abs(np.mod(these_angles, np.pi * 2) - searched_angles[:, None] - a_step).min()
990
+ distance_h = abs(np.mod(these_angles, np.pi * 2) - searched_angles[:, None] + a_step).min()
991
+ distance = np.array([distance, distance_h, distance_l]).min()
956
992
 
957
- dtasrc_start_x, dtasrc_end_x, dtasrc_start_z, dtasrc_end_z = self.trimmed_floating_subregion
993
+ if distance > 2 * a_step:
994
+ return
958
995
 
959
996
  self.chunk_reader.load_data(overwrite=True, sub_total_prange_slice=sub_total_prange_slice)
960
-
961
997
  if self.chunk_reader.dataset_subsampling > 1:
962
- subsampling_file_slice = self._expand_slice(sub_total_prange_slice)
998
+ radios_angular_range_slicing = self._expand_slice(sub_total_prange_slice)
963
999
  else:
964
- subsampling_file_slice = sub_total_prange_slice
965
- 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]
966
1002
  data_raw = self.chunk_reader.data[: len(my_subsampled_indexes)]
967
1003
 
968
1004
  self.regular_accumulator.extract_preprocess_with_flats(
969
1005
  subchunk_slice,
970
- my_subsampled_indexes,
1006
+ my_subsampled_indexes, # these are indexes pointing within the global domain sequence which is composed of darks flats radios
971
1007
  chunk_info,
972
1008
  np.array((subr_start_z, subr_end_z), "i"),
973
1009
  np.array((dtasrc_start_z, dtasrc_end_z), "i"),
974
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
975
1016
  )
976
1017
 
977
1018
  def binning_expanded(self, region):
@@ -1116,14 +1157,24 @@ class HelicalChunkedRegriddedPipeline:
1116
1157
  self.recs_histogram = self.histogram.merge_histograms(self.histo_stack)
1117
1158
  self.histo_stack.clear()
1118
1159
 
1119
- def _write_data(self, data=None, counter=[0]):
1160
+ def _write_data(self, data=None):
1120
1161
  if data is None:
1121
1162
  data = self.recs_stack
1122
1163
  my_kw_args = copy.copy(self._writer_exec_kwargs)
1123
1164
  if "config" in my_kw_args:
1124
- self.logger.info("omitting config in writer because of too slow nexus writer ")
1125
- my_kw_args["config"] = {"test": counter[0]}
1126
- counter[0] += 1
1165
+ self.logger.info(
1166
+ "omitting config in writer because of too slow nexus writer. Just writing the diagnostics, if any "
1167
+ )
1168
+
1169
+ # diagnostic are saved here, with the Nabu mechanism for config
1170
+ self.diagnostic_zpix_transl[:] = np.interp(
1171
+ self.diagnostic_proj_angle,
1172
+ np.deg2rad(self.span_info.projection_angles_deg_internally),
1173
+ self.span_info.z_pix_per_proj,
1174
+ )
1175
+ self.diagnostic_zmm_transl[:] = self.diagnostic_zpix_transl * self.span_info.pix_size_mm
1176
+
1177
+ my_kw_args["config"] = self.diagnostic
1127
1178
 
1128
1179
  self.writer.write(data, *self._writer_exec_args, **my_kw_args)
1129
1180
  self.logger.info("Wrote %s" % self.writer.get_filename())
@@ -1181,12 +1232,6 @@ class HelicalChunkedRegriddedPipeline:
1181
1232
  """This will be used in the derived class to transfer data to gpu"""
1182
1233
  self.radios_slim[:] = self.radios[:, i_slice, :]
1183
1234
 
1184
- def reset_translation_diagnostics_accumulators(self):
1185
- self.diagnostic_radios[:] = 0
1186
- self.diagnostic_weights[:] = 0
1187
- self.diagnostic_proj_angle[1] = (2**30) * 3.14
1188
- self.diagnostic_proj_angle[0] = (2**30 - 1) * 3.14
1189
-
1190
1235
  def process_chunk(self, sub_region=None):
1191
1236
  self._private_process_chunk(sub_region=sub_region)
1192
1237
  self._process_finalize()
@@ -1194,11 +1239,11 @@ class HelicalChunkedRegriddedPipeline:
1194
1239
  def _private_process_chunk(self, sub_region=None):
1195
1240
  assert sub_region is not None, "sub_region argument is mandatory in helical pipeline"
1196
1241
 
1197
- self.set_subregion(sub_region)
1198
- self.reset_translation_diagnostics_accumulators()
1199
- # self._allocate_reduced_radios()
1242
+ # Every chunk has its diagnostic, that is good to follow the trends in helical scans
1243
+ # or zstages
1244
+ self._reset_diagnostics()
1200
1245
 
1201
- # self._allocate_reduced_gridded_and_subset_radios()
1246
+ self.set_subregion(sub_region)
1202
1247
 
1203
1248
  (subr_start_x, subr_end_x, subr_start_z, subr_end_z) = self.sub_region
1204
1249
 
@@ -1218,8 +1263,13 @@ class HelicalChunkedRegriddedPipeline:
1218
1263
 
1219
1264
  my_first_pnum = proj_num_start
1220
1265
 
1221
- self.gridded_cumulated_weights[:] = 0
1222
- self.gridded_radios[:] = 0
1266
+ if self.diag_zpro_run == 0:
1267
+ # It may seem anodine, but setting a huge vector to zero
1268
+ # takes time.
1269
+ # In diagnostic collection mode we can spare it. On the other hand nothing has would be allocated for the data
1270
+ # in such case
1271
+ self.gridded_cumulated_weights[:] = 0
1272
+ self.gridded_radios[:] = 0
1223
1273
 
1224
1274
  for pnum_start, pnum_end in zip(pnum_start_list, pnum_end_list):
1225
1275
  start_in_chunk = pnum_start - my_first_pnum
@@ -1229,60 +1279,102 @@ class HelicalChunkedRegriddedPipeline:
1229
1279
  slice(pnum_start, pnum_end), slice(start_in_chunk, end_in_chunk), chunk_info
1230
1280
  )
1231
1281
 
1232
- self.gridded_radios[:] /= self.gridded_cumulated_weights
1282
+ if not self.diag_zpro_run:
1283
+ # when we collect diagnostics we dont collect all the data
1284
+ # so there would be nothing to process here
1233
1285
 
1234
- if "flatfield" in self._data_dump:
1235
- paganin_margin = self._phase_margin_up
1236
- if paganin_margin:
1237
- data_to_dump = self.gridded_radios[:, paganin_margin:-paganin_margin, :]
1238
- else:
1239
- data_to_dump = self.gridded_radios
1240
- self._dump_data_to_file("flatfield", data_to_dump)
1241
- if self.process_config.nabu_config["pipeline"]["skip_after_flatfield_dump"]:
1242
- return
1286
+ self.gridded_radios[:] /= self.gridded_cumulated_weights
1243
1287
 
1244
- if "ccd_correction" in self.processing_steps:
1245
- self._ccd_corrections()
1288
+ self.correct_for_missing_angles()
1246
1289
 
1247
- if ("phase" in self.processing_steps) or ("unsharp_mask" in self.processing_steps):
1248
- self._retrieve_phase()
1249
- if "unsharp_mask" in self.processing_steps:
1250
- self._apply_unsharp()
1251
- else:
1252
- self._nophase_put_to_radios(self.radios, self.gridded_radios)
1290
+ linea = self.gridded_cumulated_weights.sum(axis=(1, 2))
1291
+ i_zero_list = np.where(linea == 0)[0]
1292
+ for i_zero in i_zero_list:
1293
+ if i_zero > linea.shape[0] // 2:
1294
+ direction = -1
1295
+ else:
1296
+ direction = 1
1297
+ i = i_zero
1298
+ while ((i >= 0 and direction == -1) or ((i < linea.shape[0] - 1) and direction == 1)) and linea[i] == 0:
1299
+ i += direction
1300
+ if linea[i]:
1301
+ self.gridded_radios[i_zero] = self.gridded_radios[i]
1302
+ self.gridded_cumulated_weights[i_zero] = self.gridded_cumulated_weights[i]
1303
+
1304
+ if "flatfield" in self._data_dump:
1305
+ paganin_margin = self._phase_margin_up
1306
+ if paganin_margin:
1307
+ data_to_dump = self.gridded_radios[:, paganin_margin:-paganin_margin, :]
1308
+ else:
1309
+ data_to_dump = self.gridded_radios
1310
+ self._dump_data_to_file("flatfield", data_to_dump)
1311
+ if self.process_config.nabu_config["pipeline"]["skip_after_flatfield_dump"]:
1312
+ return
1313
+
1314
+ if "ccd_correction" in self.processing_steps:
1315
+ self._ccd_corrections()
1316
+
1317
+ if cxx_paganin is None:
1318
+ if ("phase" in self.processing_steps) or ("unsharp_mask" in self.processing_steps):
1319
+ self._retrieve_phase()
1320
+ if "unsharp_mask" in self.processing_steps:
1321
+ self._apply_unsharp()
1322
+ else:
1323
+ self._nophase_put_to_radios(self.radios, self.gridded_radios)
1324
+ else:
1325
+ if "phase" in self.processing_steps:
1326
+ pr = self.phase_retrieval
1327
+ paganin_l_micron = math.sqrt(pr.wavelength_micron * pr.distance_micron * pr.delta_beta * math.pi)
1328
+ cxx_paganin.paganin_pyhst(
1329
+ data_raw=self.gridded_radios,
1330
+ output=self.radios,
1331
+ num_of_threads=-1,
1332
+ paganin_marge=self._phase_margin_up,
1333
+ paganin_l_micron=paganin_l_micron / pr.pixel_size_micron,
1334
+ image_pixel_size_y=1.0,
1335
+ image_pixel_size_x=1.0,
1336
+ unsharp_sigma=self.unsharp_sigma,
1337
+ unsharp_coeff=self.unsharp_coeff,
1338
+ unsharp_LoG=int((self.unsharp_method == "log")),
1339
+ )
1340
+ else:
1341
+ self._nophase_put_to_radios(self.radios, self.gridded_radios)
1253
1342
 
1254
- self.logger.info(" LOG ")
1255
- self._nophase_put_to_radios(self.radios_weights, self.gridded_cumulated_weights)
1343
+ self.logger.info(" LOG ")
1344
+ self._nophase_put_to_radios(self.radios_weights, self.gridded_cumulated_weights)
1256
1345
 
1257
- # print( " processing steps ", self.processing_steps )
1258
- # ['read_chunk', 'flatfield', 'double_flatfield', 'take_log', 'reconstruction', 'save']
1346
+ # print( " processing steps ", self.processing_steps )
1347
+ # ['read_chunk', 'flatfield', 'double_flatfield', 'take_log', 'reconstruction', 'save']
1259
1348
 
1260
- if "take_log" in self.processing_steps:
1261
- self._take_log()
1349
+ if "take_log" in self.processing_steps:
1350
+ self._take_log()
1262
1351
 
1263
- self.logger.info(" BALANCE ")
1352
+ self.logger.info(" BALANCE ")
1264
1353
 
1265
- self.balance_weights()
1354
+ self.balance_weights()
1266
1355
 
1267
- num_slices = self.radios.shape[1]
1356
+ num_slices = self.radios.shape[1]
1268
1357
 
1269
- self.logger.info(" NORMALIZE")
1270
- self._normalize_sinos()
1271
- self._dump_sinogram()
1358
+ self.logger.info(" NORMALIZE")
1359
+ self._normalize_sinos()
1360
+ self._dump_sinogram()
1272
1361
 
1273
1362
  if "reconstruction" in self.processing_steps:
1274
- for i_slice in range(num_slices):
1275
- self._post_primary_data_reduction(i_slice) # charge on self.radios_slim
1363
+ if not self.diag_zpro_run:
1364
+ # otherwise, when collecting diagnostic, we are not interested in the remaining steps
1365
+ # on the other hand there would be nothing to process because only diagnostics have been collected
1366
+ for i_slice in range(num_slices):
1367
+ self._post_primary_data_reduction(i_slice) # charge on self.radios_slim
1276
1368
 
1277
- self._filter()
1369
+ self._filter()
1278
1370
 
1279
- self.apply_weights(i_slice)
1371
+ self.apply_weights(i_slice)
1280
1372
 
1281
- self._build_sino()
1373
+ self._build_sino()
1282
1374
 
1283
- self._reconstruct(chunk_info=chunk_info, i_slice=i_slice)
1375
+ self._reconstruct(chunk_info=chunk_info, i_slice=i_slice)
1284
1376
 
1285
- self._compute_histogram(i_slice=i_slice, num_slices=num_slices)
1377
+ self._compute_histogram(i_slice=i_slice, num_slices=num_slices)
1286
1378
 
1287
1379
  self._write_data()
1288
1380
 
@@ -1299,9 +1391,27 @@ class HelicalChunkedRegriddedPipeline:
1299
1391
  : end_angle_index - first_angle_index
1300
1392
  ]
1301
1393
 
1394
+ def correct_for_missing_angles(self):
1395
+ """For non helical scan, the rotation is often incomplete ( < 360)
1396
+ here we complement the missing angles
1397
+ """
1398
+ linea = self.gridded_cumulated_weights.sum(axis=(1, 2))
1399
+ i_zero_list = np.where(linea == 0)[0]
1400
+ for i_zero in i_zero_list:
1401
+ if i_zero > linea.shape[0] // 2:
1402
+ direction = -1
1403
+ else:
1404
+ direction = 1
1405
+ i = i_zero
1406
+ while ((i >= 0 and direction == -1) or ((i < linea.shape[0] - 1) and direction == 1)) and linea[i] == 0:
1407
+ i += direction
1408
+ if linea[i]:
1409
+ self.gridded_radios[i_zero] = self.gridded_radios[i]
1410
+ self.gridded_cumulated_weights[i_zero] = self.gridded_cumulated_weights[i]
1411
+
1302
1412
  @classmethod
1303
1413
  def estimate_required_memory(
1304
- cls, process_config, reading_granularity=None, chunk_size=None, margin_v=0, span_info=None
1414
+ cls, process_config, reading_granularity=None, chunk_size=None, margin_v=0, span_info=None, diag_zpro_run=0
1305
1415
  ):
1306
1416
  """
1307
1417
  Estimate the memory (RAM) needed for a reconstruction.
@@ -1340,11 +1450,12 @@ class HelicalChunkedRegriddedPipeline:
1340
1450
  binning_z = nabu_config["dataset"]["binning_z"]
1341
1451
  projections_subsampling = nabu_config["dataset"]["projections_subsampling"]
1342
1452
 
1343
- # the gridded target
1344
- total_memory_needed += Nx * (2 * margin_v + chunk_size) * n_gridded_angles * 4
1453
+ if not diag_zpro_run:
1454
+ # the gridded target
1455
+ total_memory_needed += Nx * (2 * margin_v + chunk_size) * n_gridded_angles * 4
1345
1456
 
1346
- # the gridded weights
1347
- total_memory_needed += Nx * (2 * margin_v + chunk_size) * n_gridded_angles * 4
1457
+ # the gridded weights
1458
+ total_memory_needed += Nx * (2 * margin_v + chunk_size) * n_gridded_angles * 4
1348
1459
 
1349
1460
  # the read grain
1350
1461
  total_memory_needed += (
@@ -1383,7 +1494,7 @@ class HelicalChunkedRegriddedPipeline:
1383
1494
  # Reconstruction
1384
1495
  # ---------------
1385
1496
  reconstructed_volume_size = 0
1386
- if "reconstruction" in processing_steps:
1497
+ if "reconstruction" in processing_steps and not diag_zpro_run:
1387
1498
  ## radios_slim is used to process one slice at once, It will be on the gpu
1388
1499
  ## and cannot be reduced further, therefore no need to estimate it.
1389
1500
  ## Either it passes or it does not.