nabu 2024.1.0rc1__py3-none-any.whl → 2024.1.0rc3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
doc/doc_config.py ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python
2
+
3
+ from nabu.resources.nabu_config import nabu_config
4
+
5
+
6
+ def generate(file_):
7
+ def write(content):
8
+ print(content, file=file_)
9
+ for section, values in nabu_config.items():
10
+ if section == "about":
11
+ continue
12
+ write("## %s\n" % section)
13
+ for key, val in values.items():
14
+ if val["type"] == "unsupported":
15
+ continue
16
+ write(val["help"] + "\n")
17
+ write(
18
+ "```ini\n%s = %s\n```"
19
+ % (key, val["default"])
20
+ )
21
+
22
+
23
+
24
+ if __name__ == "__main__":
25
+
26
+ import sys, os
27
+ print(os.path.abspath(__file__))
28
+ exit(0)
29
+
30
+ fname = "/tmp/test.md"
31
+ with open(fname, "w") as f:
32
+ generate(f)
nabu/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "2024.1.0-rc1"
1
+ __version__ = "2024.1.0-rc3"
2
2
  __nabu_modules__ = [
3
3
  "app",
4
4
  "cuda",
@@ -43,7 +43,7 @@ def guess_tomo_objects(my_str: str) -> tuple:
43
43
  def bootstrap_stitching():
44
44
  args = parse_params_values(
45
45
  BootstrapStitchingConfig,
46
- parser_description="Initialize a nabu configuration file",
46
+ parser_description="Initialize a 'nabu-stitching' configuration file",
47
47
  )
48
48
 
49
49
  prefilled_values = {}
nabu/app/cli_configs.py CHANGED
@@ -329,6 +329,11 @@ StitchingConfig = {
329
329
  "help": "Logging level. Can be 'debug', 'info', 'warning', 'error'. Default is 'info'.",
330
330
  "default": "info",
331
331
  },
332
+ "--only-create-master-file": {
333
+ "help": "Will create the master file with all sub files (volumes or scans). It expects the processing to be finished. It can happen if all slurm job have been submitted but you've been kicked out of the cluster of if you need to relaunch manually some failling job slurm for any reason",
334
+ "default": False,
335
+ "action": "store_true",
336
+ },
332
337
  }
333
338
 
334
339
  # Default configuration for "stitching-bootstrap" command
nabu/app/composite_cor.py CHANGED
@@ -5,8 +5,9 @@ import numpy as np
5
5
  import re
6
6
 
7
7
  from nabu.resources.dataset_analyzer import HDF5DatasetAnalyzer
8
- from nabu.pipeline.estimators import CompositeCOREstimator
8
+ from nabu.pipeline.estimators import CompositeCOREstimator, estimate_cor
9
9
  from nabu.resources.nxflatfield import update_dataset_info_flats_darks
10
+ from nabu.resources.utils import extract_parameters
10
11
  from nxtomo.application.nxtomo import NXtomo
11
12
  from .. import version
12
13
  from .cli_configs import CompositeCorConfig
@@ -84,15 +85,27 @@ def composite_cor_entry_point(args_dict):
84
85
  dataset_info = HDF5DatasetAnalyzer(nexus_name, extra_options={"h5_entry": args.entry_name})
85
86
  update_dataset_info_flats_darks(dataset_info, flatfield_mode=1)
86
87
 
87
- if "near_pos" not in args.cor_options:
88
- scan = NXtomo()
89
- scan.load(file_path=nexus_name, data_path=args.entry_name)
90
- estimated_near = scan.instrument.detector.estimated_cor_from_motor
91
-
92
- cor_options = args.cor_options + f" ; near_pos = {estimated_near} "
93
-
94
- else:
95
- cor_options = args.cor_options
88
+ ### JL start ###
89
+ # Extract CoR parameters from configuration file
90
+ try:
91
+ cor_options = extract_parameters(args.cor_options, sep=";")
92
+ except Exception as exc:
93
+ msg = "Could not extract parameters from cor_options: %s" % (str(exc))
94
+ raise ValueError(msg)
95
+ ### JL end ###
96
+
97
+ # JL start ###
98
+ #: next bit will be done in estimate
99
+ # if "near_pos" not in args.cor_options:
100
+ # scan = NXtomo()
101
+ # scan.load(file_path=nexus_name, data_path=args.entry_name)
102
+ # estimated_near = scan.instrument.detector.estimated_cor_from_motor
103
+ #
104
+ # cor_options = args.cor_options + f" ; near_pos = {estimated_near} "
105
+ #
106
+ # else:
107
+ # cor_options = args.cor_options
108
+ ### JL end ###
96
109
 
97
110
  cor_finder = CompositeCOREstimator(
98
111
  dataset_info,
nabu/app/multicor.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from os import remove
2
- from typing import overload
3
2
  import numpy as np
4
3
  from .. import version
5
4
  from .reconstruct import get_reconstructor
@@ -10,7 +10,7 @@ from .. import version
10
10
  from tomoscan.framereducer.method import ReduceMethod
11
11
  from tomoscan.scanbase import TomoScanBase
12
12
  from tomoscan.esrf.scan.edfscan import EDFTomoScan
13
- from tomoscan.scanfactory import ScanFactory
13
+ from tomoscan.factory import Factory
14
14
  from silx.io.url import DataUrl
15
15
 
16
16
 
@@ -153,6 +153,9 @@ def reduce_dark_flat(
153
153
 
154
154
 
155
155
  def main(argv=None):
156
+ """
157
+ Compute reduce dark(s) and flat(s) of a dataset
158
+ """
156
159
  if argv is None:
157
160
  argv = sys.argv[1:]
158
161
 
@@ -163,7 +166,7 @@ def main(argv=None):
163
166
  user_args=argv,
164
167
  )
165
168
 
166
- scan = ScanFactory.create_scan_object(args["dataset"], entry=args["entry"])
169
+ scan = Factory.create_scan_object(args["dataset"], entry=args["entry"])
167
170
  exit(
168
171
  reduce_dark_flat(
169
172
  scan=scan,
nabu/app/stitching.py CHANGED
@@ -31,7 +31,21 @@ def main():
31
31
 
32
32
  stitching_config = dict_to_config_obj(conf_dict)
33
33
  stitching_config.settle_inputs()
34
- if stitching_config.slurm_config.partition in (None, ""):
34
+ if args["only_create_master_file"]:
35
+ # option to ease creation of the master in the following cases:
36
+ # * user has submitted all the job but has been quicked out of the cluster
37
+ # * only a few slurm job for some random version (cluster update...) and user want to retriger only those job and process the aggragation only. On those cases no need to redo it all.
38
+ tomo_objs = []
39
+ for _, sub_config in split_stitching_configuration_to_slurm_job(stitching_config, yield_configuration=True):
40
+ tomo_objs.append(sub_config.get_output_object().get_identifier().to_str())
41
+
42
+ post_processing = StitchingPostProcAggregation(
43
+ existing_objs_ids=tomo_objs,
44
+ stitching_config=stitching_config,
45
+ )
46
+ post_processing.process()
47
+
48
+ elif stitching_config.slurm_config.partition in (None, ""):
35
49
  # case 1: run locally
36
50
  _logger.info(f"run stitching locally with {stitching_config}")
37
51
 
@@ -60,7 +74,7 @@ def main():
60
74
  # 2.2 wait for future to be done and concatenate the result
61
75
  post_processing = StitchingPostProcAggregation(
62
76
  futures=futures,
63
- stitching_config=stitching_config.to_dict(),
77
+ stitching_config=stitching_config,
64
78
  )
65
79
  post_processing.process()
66
80
 
nabu/estimation/cor.py CHANGED
@@ -133,7 +133,9 @@ class CenterOfRotation(AlignmentBase):
133
133
 
134
134
  if isinstance(self.cor_options.get("near_pos", None), (int, float)):
135
135
  near_pos = self.cor_options["near_pos"]
136
- if np.abs(near_pos - estimated_cor) / near_pos > 0.2:
136
+ if (
137
+ np.abs(near_pos - estimated_cor) / near_pos > 0.2
138
+ ): # For comparison, near_pos is RELATIVE (as estimated_cor is).
137
139
  validity_check_result = cor_result_validity["questionable"]
138
140
  else:
139
141
  validity_check_result = cor_result_validity["sound"]
@@ -266,8 +268,8 @@ class CenterOfRotationSlidingWindow(CenterOfRotation):
266
268
  )
267
269
  img_shape = img_2.shape
268
270
 
269
- near_pos = self.cor_options.get("near_pos", "ignore")
270
- if near_pos == "ignore":
271
+ near_pos = self.cor_options.get("near_pos", None)
272
+ if near_pos is None:
271
273
  if window_width is None:
272
274
  if side.lower() == "center":
273
275
  window_width = round(img_shape[-1] / 4.0 * 3.0)
@@ -280,7 +282,8 @@ class CenterOfRotationSlidingWindow(CenterOfRotation):
280
282
  # number of pixels where the window will "slide".
281
283
  n = img_shape[-1] - window_width
282
284
  else:
283
- offset = min(img_shape[-1] - near_pos, near_pos) # distance to closest edge.
285
+ abs_pos = near_pos + img_shape[-1] // 2
286
+ offset = min(img_shape[-1] - abs_pos, abs_pos) # distance to closest edge.
284
287
 
285
288
  window_fraction = 0.8 # Hard-coded ?
286
289
  window_shift = int(np.floor(offset * window_fraction))
@@ -288,7 +291,7 @@ class CenterOfRotationSlidingWindow(CenterOfRotation):
288
291
 
289
292
  sliding_shift = int(np.floor(offset * (1 - window_fraction))) - 1
290
293
  n = 2 * sliding_shift + 1
291
- win_1_start_seed = near_pos - window_shift - sliding_shift
294
+ win_1_start_seed = abs_pos - window_shift - sliding_shift
292
295
 
293
296
  if side.lower() == "right":
294
297
  win_2_start = 0
@@ -904,39 +907,19 @@ class CenterOfRotationFourierAngles(CenterOfRotation):
904
907
  """
905
908
 
906
909
  _default_cor_options = {
907
- "crop_around_cor": True,
910
+ "crop_around_cor": False,
911
+ "side": "center",
908
912
  "near_pos": None,
913
+ "near_std": 100,
909
914
  "near_width": 20,
910
- "near_weight": 0.1,
911
915
  "near_shape": "tukey",
912
- "near_std": 100,
916
+ "near_weight": 0.1,
913
917
  "near_alpha": 0.5,
914
918
  "shift_sino": True,
915
- "use_paganin": True,
916
919
  "near_step": 0.5,
917
- "side": "center",
918
920
  "refine": False,
919
921
  }
920
922
 
921
- # def __hash__(self):
922
- # if self.cor_options["near_shape"] == "tukey":
923
- # cor_shape = (
924
- # self.cor_options["near_shape"],
925
- # self.cor_options["near_std"],
926
- # self.cor_options["near_alpha"],
927
- # )
928
- # else:
929
- # cor_shape = (self.cor_options["near_shape"], self.cor_options["near_std"])
930
- # return hash(
931
- # (
932
- # self.cor_options["use_paganin"],
933
- # self.cor_options["crop_around_cor"],
934
- # self.cor_options["near_weight"],
935
- # self.cor_options["near_width"],
936
- # cor_shape,
937
- # )
938
- # )
939
-
940
923
  def _freq_radio(self, sinos, ifrom, ito):
941
924
  size = (sinos.shape[0] + sinos.shape[0] % 2) // 2
942
925
  fs = np.empty((size, sinos.shape[1]))
@@ -972,11 +955,11 @@ class CenterOfRotationFourierAngles(CenterOfRotation):
972
955
  lin = np.maximum(0, std - np.abs(p[0] - x)) * 0.5 * np.pi / std
973
956
  return p[3] + p[2] * np.sin(lin)
974
957
 
975
- def _px(self, detector_width, near_pos, near_std):
958
+ def _px(self, detector_width, abs_pos, near_std):
976
959
  sym_range = None
977
- if near_pos is not None:
960
+ if abs_pos is not None:
978
961
  if self.cor_options["crop_around_cor"]:
979
- sym_range = int(near_pos - near_std * 2), int(near_pos + near_std * 2)
962
+ sym_range = int(abs_pos - near_std * 2), int(abs_pos + near_std * 2)
980
963
 
981
964
  window = self.cor_options["near_width"]
982
965
  if sym_range is not None:
@@ -1012,7 +995,7 @@ class CenterOfRotationFourierAngles(CenterOfRotation):
1012
995
 
1013
996
  for j, x in enumerate(px):
1014
997
  i = int(np.floor(x))
1015
- if x - i > 0.4: # Specific to near_step = 0.5?
998
+ if x - i > 0.4: # TO DO : Specific to near_step = 0.5?
1016
999
  f_left = f_array[:, i - window : i]
1017
1000
  f_right = f_shift_array[:, i + 1 : i + window + 1][:, ::-1]
1018
1001
  else:
@@ -1022,24 +1005,36 @@ class CenterOfRotationFourierAngles(CenterOfRotation):
1022
1005
  f_coef[j] = np.sum(np.abs(f_left - f_right))
1023
1006
  return f_coef
1024
1007
 
1025
- def _cor_correlation(self, px, near_pos, near_std):
1026
- if near_pos is not None:
1008
+ def _cor_correlation(self, px, abs_pos, near_std):
1009
+ if abs_pos is not None:
1027
1010
  signal = self.cor_options["near_shape"]
1028
1011
  weight = self.cor_options["near_weight"]
1029
1012
  alpha = self.cor_options["near_alpha"]
1030
1013
  if signal == "sinlet":
1031
- coef = self.sinlet((near_pos, near_std, -weight, 1), px)
1014
+ coef = self.sinlet((abs_pos, near_std, -weight, 1), px)
1032
1015
  elif signal == "gaussian":
1033
- coef = self.gaussian((near_pos, near_std, -weight, 1), px)
1016
+ coef = self.gaussian((abs_pos, near_std, -weight, 1), px)
1034
1017
  elif signal == "tukey":
1035
- coef = self.tukey((near_pos, near_std * 2, alpha, -weight, 1), px)
1018
+ coef = self.tukey((abs_pos, near_std * 2, alpha, -weight, 1), px)
1036
1019
  else:
1037
1020
  raise ValueError("Shape unsupported")
1038
1021
  else:
1039
1022
  coef = np.ones_like(px)
1040
1023
  return coef
1041
1024
 
1042
- def find_shift(self, img_1, img_2, angles):
1025
+ def find_shift(
1026
+ self,
1027
+ img_1,
1028
+ img_2,
1029
+ angles,
1030
+ side,
1031
+ roi_yxhw=None,
1032
+ median_filt_shape=None,
1033
+ padding_mode=None,
1034
+ peak_fit_radius=1,
1035
+ high_pass=None,
1036
+ low_pass=None,
1037
+ ):
1043
1038
  sinos = np.vstack([img_1, np.fliplr(img_2).copy()])
1044
1039
  detector_width = sinos.shape[1]
1045
1040
 
@@ -1048,28 +1043,38 @@ class CenterOfRotationFourierAngles(CenterOfRotation):
1048
1043
  self.logger.warning("Not enough angles, estimator skipped")
1049
1044
  return None
1050
1045
 
1051
- # self.cor_options = self._default_cor_options.update(self.cor_options)
1052
- # Convert near_pos (which is relative) to absolute.
1053
- near_pos = self.cor_options.get("near_pos")
1046
+ near_pos = self.cor_options.get("near_pos", None) # A RELATIVE estimation of the COR
1054
1047
 
1055
1048
  # Default coarse estimate to center of detector
1056
1049
  # if no one is given either in NX or by user.
1057
- if near_pos == "ignore":
1050
+ if near_pos is None:
1058
1051
  self.logger.warning("No initial guess was found (from metadata or user) for CoR")
1059
1052
  self.logger.warning("Setting initial guess to center of detector.")
1060
- near_pos = detector_width // 2
1053
+ if side == "center":
1054
+ abs_pos = detector_width // 2
1055
+ elif side == "left":
1056
+ abs_pos = detector_width // 4
1057
+ elif side == "right":
1058
+ abs_pos = detector_width * 3 // 4
1059
+ elif side == "near":
1060
+ abs_pos = detector_width // 2
1061
+ else:
1062
+ raise ValueError(f"side '{side}' is not handled")
1063
+ elif isinstance(near_pos, (int, float)): # Convert RELATIVE to ABSOLUTE position
1064
+ abs_pos = near_pos + detector_width / 2
1065
+
1061
1066
  near_std = None
1062
- if near_pos is not None:
1067
+ if abs_pos is not None:
1063
1068
  near_std = self.cor_options["near_std"]
1064
1069
 
1065
- px = self._px(detector_width, near_pos, near_std)
1070
+ px = self._px(detector_width, abs_pos, near_std)
1066
1071
 
1067
1072
  coef_f = self._symmetry_correlation(
1068
1073
  px,
1069
1074
  sinos,
1070
1075
  angles,
1071
1076
  )
1072
- coef_p = self._cor_correlation(px, near_pos, near_std)
1077
+ coef_p = self._cor_correlation(px, abs_pos, near_std)
1073
1078
  coef = coef_f * coef_p
1074
1079
 
1075
1080
  if len(px) > 0:
@@ -1090,12 +1095,21 @@ class CenterOfRotationFourierAngles(CenterOfRotation):
1090
1095
  class CenterOfRotationOctaveAccurate(AlignmentBase):
1091
1096
  """This is a Python implementation of Octave/fastomo3/accurate COR estimator.
1092
1097
  The Octave 'accurate' function is renamed `local_correlation`.
1093
- The Nabu standard `find_shift` has the same API as other COR estimators (sliding, growing...)
1098
+ The Nabu standard `find_shift` has the same API as the other COR estimators (sliding, growing...)
1094
1099
 
1095
1100
  The class inherits directly from AlignmentBase.
1096
1101
  """
1097
1102
 
1098
- def cut(self, im, nrows, ncols, new_center_row=None, new_center_col=None):
1103
+ _default_cor_options = {
1104
+ "maxsize": [5, 5],
1105
+ "refine": None,
1106
+ "pmcc": False,
1107
+ "normalize": True,
1108
+ "low_pass": 0.01,
1109
+ "limz": 0.5,
1110
+ }
1111
+
1112
+ def _cut(self, im, nrows, ncols, new_center_row=None, new_center_col=None):
1099
1113
  """Cuts a sub-matrix out of a larger matrix.
1100
1114
  Cuts in the center of the original matrix, except if new center is specified
1101
1115
  NO CHECKING of validity indices sub-matrix!
@@ -1143,7 +1157,7 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1143
1157
 
1144
1158
  return im[rb:re, cb:ce]
1145
1159
 
1146
- def checkifpart(self, rapp, rapp_hist):
1160
+ def _checkifpart(self, rapp, rapp_hist):
1147
1161
  res = 0
1148
1162
  for k in range(rapp_hist.shape[0]):
1149
1163
  if np.allclose(rapp, rapp_hist[k, :]):
@@ -1151,7 +1165,7 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1151
1165
  return res
1152
1166
  return res
1153
1167
 
1154
- def interpolate(self, input, shift, mode="mean", interpolation_method="linear"):
1168
+ def _interpolate(self, input, shift, mode="mean", interpolation_method="linear"):
1155
1169
  """Applies to the input a translation by a vector `shift`. Based on
1156
1170
  `scipy.ndimage.affine_transform` function.
1157
1171
  JL: This Octave function was initially used in the refine clause of the local_correlation (Octave find_shift).
@@ -1218,7 +1232,7 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1218
1232
 
1219
1233
  return affine_transform(input, matrix, mode=mode, order=order)
1220
1234
 
1221
- def local_correlation(
1235
+ def _local_correlation(
1222
1236
  self,
1223
1237
  z1,
1224
1238
  z2,
@@ -1227,7 +1241,6 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1227
1241
  refine=None,
1228
1242
  pmcc=False,
1229
1243
  normalize=True,
1230
- verbose=False,
1231
1244
  ):
1232
1245
  """Returns the 2D shift in pixels between two images.
1233
1246
  It looks for a local optimum around the initial shift cor_estimate
@@ -1261,8 +1274,6 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1261
1274
  normalize: Boolean (default is True)
1262
1275
  Set mean of each image to 1 if True.
1263
1276
 
1264
- verbose: Boolean (default is False)
1265
-
1266
1277
  Returns
1267
1278
  -------
1268
1279
  c = [row,column] (or [NaN,NaN] if unsuccessful.)
@@ -1361,7 +1372,7 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1361
1372
  if pmcc:
1362
1373
  z2p = z2[z2beg[0] - k : z2end[0] - k, z2beg[1] - l : z2end[1] - l].flatten()
1363
1374
  std_z2p = z2p.std()
1364
- cc[k, l] = -np.cov(z1p, z2p) / (std_z1p * std_z2p)
1375
+ cc[k, l] = -np.cov(z1p, z2p, rowvar=True)[1, 0] / (std_z1p * std_z2p)
1365
1376
  else:
1366
1377
  if normalize == 2:
1367
1378
  z2p = z2[z2beg[0] - k : z2end[0] - k, z2beg[1] - l : z2end[1] - l].flatten()
@@ -1388,25 +1399,25 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1388
1399
  if not shiftfound:
1389
1400
  cor_estimate = c
1390
1401
  # Check that new shift estimate was not already done (avoid eternal loop)
1391
- if self.checkifpart(cor_estimate, rapp_hist):
1392
- if verbose:
1402
+ if self._checkifpart(cor_estimate, rapp_hist):
1403
+ if self.verbose:
1393
1404
  self.logger.info(f"Stuck in loop?")
1394
1405
  refine = True
1395
1406
  shiftfound = True
1396
1407
  c = np.array([np.nan, np.nan])
1397
1408
  else:
1398
1409
  rapp_hist.append(cor_estimate)
1399
- if verbose:
1410
+ if self.verbose:
1400
1411
  self.logger.info(f"Changing shift estimate: {cor_estimate}")
1401
1412
  maxsize = np.minimum(maxsize, np.array(z1.shape) - np.abs(cor_estimate) - 1).astype(int)
1402
1413
  if (maxsize == 0).sum():
1403
- if verbose:
1414
+ if self.verbose:
1404
1415
  self.logger.info(f"Edge of image reached")
1405
1416
  refine = False
1406
1417
  shiftfound = True
1407
1418
  c = np.array([np.nan, np.nan])
1408
1419
  elif len(rapp_hist) > 0:
1409
- if verbose:
1420
+ if self.verbose:
1410
1421
  self.logger.info("\n")
1411
1422
 
1412
1423
  ####################################
@@ -1450,16 +1461,86 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1450
1461
  roi_yxhw=None,
1451
1462
  median_filt_shape=None,
1452
1463
  padding_mode=None,
1453
- high_pass=None,
1454
1464
  low_pass=0.01,
1455
- limz=0.5, # maximum vertical drift before warning
1465
+ high_pass=None,
1456
1466
  ):
1467
+ """Automatically finds the Center of Rotation (CoR), given two images
1468
+ (projections/radiographs). Suitable for half-aquisition scan.
1469
+
1470
+ This method finds the half-shift between two opposite images, by
1471
+ minimizing the variance of small ROI around a global COR estimate
1472
+ (obtained by maximizing Fourier-space computed global correlations).
1473
+
1474
+
1475
+ The output of this function, allows to compute motor movements for
1476
+ aligning the sample rotation axis. Given the following values:
1477
+
1478
+ - L1: distance from source to motor
1479
+ - L2: distance from source to detector
1480
+ - ps: physical pixel size
1481
+ - v: output of this function
1482
+
1483
+ displacement of motor = (L1 / L2 * ps) * v
1484
+
1485
+ Parameters
1486
+ ----------
1487
+ img_1: numpy.ndarray
1488
+ First image
1489
+ img_2: numpy.ndarray
1490
+ Second image, it needs to have been flipped already (e.g. using numpy.fliplr).
1491
+ side: string
1492
+ Expected region of the CoR. Must be 'center' in that case.
1493
+ roi_yxhw: (2, ) or (4, ) numpy.ndarray, tuple, or array, optional
1494
+ 4 elements vector containing: vertical and horizontal coordinates
1495
+ of first pixel, plus height and width of the Region of Interest (RoI).
1496
+ Or a 2 elements vector containing: plus height and width of the
1497
+ centered Region of Interest (RoI).
1498
+ Default is None -> deactivated.
1499
+ The ROI will be used for the global estimate.
1500
+ median_filt_shape: (2, ) numpy.ndarray, tuple, or array, optional
1501
+ Shape of the median filter window. Default is None -> deactivated.
1502
+ padding_mode: str in numpy.pad's mode list, optional
1503
+ Padding mode, which determines the type of convolution. If None or
1504
+ 'wrap' are passed, this resorts to the traditional circular convolution.
1505
+ If 'edge' or 'constant' are passed, it results in a linear convolution.
1506
+ Default is the circular convolution.
1507
+ All options are:
1508
+ None | 'constant' | 'edge' | 'linear_ramp' | 'maximum' | 'mean'
1509
+ | 'median' | 'minimum' | 'reflect' | 'symmetric' |'wrap'
1510
+ low_pass: float or sequence of two floats
1511
+ Low-pass filter properties, as described in `nabu.misc.fourier_filters`
1512
+ high_pass: float or sequence of two floats
1513
+ High-pass filter properties, as described in `nabu.misc.fourier_filters`
1514
+
1515
+ Raises
1516
+ ------
1517
+ ValueError
1518
+ In case images are not 2-dimensional or have different sizes.
1519
+
1520
+ Returns
1521
+ -------
1522
+ float
1523
+ Estimated center of rotation position from the center of the RoI in pixels.
1524
+
1525
+ Examples
1526
+ --------
1527
+ The following code computes the center of rotation position for two
1528
+ given images in a tomography scan, where the second image is taken at
1529
+ 180 degrees from the first.
1530
+
1531
+ >>> radio1 = data[0, :, :]
1532
+ ... radio2 = np.fliplr(data[1, :, :])
1533
+ ... CoR_calc = CenterOfRotationOctaveAccurate()
1534
+ ... cor_position = CoR_calc.find_shift(radio1, radio2)
1535
+
1536
+ Or for noisy images:
1537
+
1538
+ >>> cor_position = CoR_calc.find_shift(radio1, radio2, median_filt_shape=(3, 3))
1539
+ """
1540
+
1457
1541
  self.logger.info(
1458
- f"Estimation of the COR with following options: high_pass={high_pass}, low_pass={low_pass}, limz={limz}."
1542
+ f"Estimation of the COR with following options: high_pass={high_pass}, low_pass={low_pass}, limz={self.cor_options['limz']}."
1459
1543
  )
1460
- # Retrieve cor_options
1461
- # self._set_cor_options(cor_options)
1462
- # self.logger.info(f"cor_options are :{self.cor_options}")
1463
1544
 
1464
1545
  self._check_img_pair_sizes(img_1, img_2)
1465
1546
 
@@ -1491,7 +1572,7 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1491
1572
  shift -= np.array(img_shape) // 2
1492
1573
 
1493
1574
  # The real "accurate" starts here (i.e. the octave findshift() func).
1494
- if np.abs(shift[0]) > 10 * limz:
1575
+ if np.abs(shift[0]) > 10 * self.cor_options["limz"]:
1495
1576
  # This is suspiscious. We don't trust results of correlate.
1496
1577
  self.logger.info(f"Pre-correlation yields {shift[0]} pixels vertical motion")
1497
1578
  self.logger.info(f"We do not consider it.")
@@ -1502,20 +1583,35 @@ class CenterOfRotationOctaveAccurate(AlignmentBase):
1502
1583
  cutsize = img_shape[1] // 2
1503
1584
  oldshift = np.round(shift).astype(int)
1504
1585
  if (img_shape[0] > cutsize) or (img_shape[1] > cutsize):
1505
- im0 = self.cut(img_1, min(img_shape[0], cutsize), min(img_shape[1], cutsize))
1506
- im1 = self.cut(
1586
+ im0 = self._cut(img_1, min(img_shape[0], cutsize), min(img_shape[1], cutsize))
1587
+ im1 = self._cut(
1507
1588
  np.roll(img_2, oldshift, axis=(0, 1)), min(img_shape[0], cutsize), min(img_shape[1], cutsize)
1508
1589
  )
1509
- shift = oldshift + self.local_correlation(im0, im1)
1590
+ shift = oldshift + self._local_correlation(
1591
+ im0,
1592
+ im1,
1593
+ maxsize=self.cor_options["maxsize"],
1594
+ refine=self.cor_options["refine"],
1595
+ pmcc=self.cor_options["pmcc"],
1596
+ normalize=self.cor_options["normalize"],
1597
+ )
1510
1598
  else:
1511
- shift = self.local_correlation(img_1, img_2, [], cor_estimate=oldshift)
1599
+ shift = self._local_correlation(
1600
+ img_1,
1601
+ img_2,
1602
+ maxsize=self.cor_options["maxsize"],
1603
+ cor_estimate=oldshift,
1604
+ refine=self.cor_options["refine"],
1605
+ pmcc=self.cor_options["pmcc"],
1606
+ normalize=self.cor_options["normalize"],
1607
+ )
1512
1608
  if ((shift - oldshift) ** 2).sum() > 4:
1513
1609
  self.logger.info(f"Pre-correlation ({oldshift}) and accurate correlation ({shift}) are not consistent.")
1514
1610
  self.logger.info("Please check!!!")
1515
1611
 
1516
1612
  offset = shift[1] / 2
1517
1613
 
1518
- if np.abs(shift[0]) > limz:
1614
+ if np.abs(shift[0]) > self.cor_options["limz"]:
1519
1615
  self.logger.info("Verify alignment or sample motion.")
1520
1616
  self.logger.info(f"Verical motion: {shift[0]} pixels.")
1521
1617
  self.logger.info(f"Offset?: {offset} pixels.")