ChessAnalysisPipeline 0.0.13__py3-none-any.whl → 0.0.14__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.

Potentially problematic release.


This version of ChessAnalysisPipeline might be problematic. Click here for more details.

CHAP/tomo/processor.py CHANGED
@@ -19,14 +19,17 @@ import numpy as np
19
19
  # Local modules
20
20
  from CHAP.utils.general import (
21
21
  is_num,
22
+ is_num_series,
22
23
  is_int_pair,
23
24
  input_int,
24
25
  input_num,
26
+ input_num_list,
25
27
  input_yesno,
26
28
  select_image_indices,
27
29
  select_roi_1d,
28
30
  select_roi_2d,
29
31
  quick_imshow,
32
+ nxcopy,
30
33
  )
31
34
  from CHAP.utils.fit import Fit
32
35
  from CHAP.processor import Processor
@@ -111,6 +114,7 @@ class TomoCHESSMapConverter(Processor):
111
114
  NXdetector,
112
115
  NXentry,
113
116
  NXinstrument,
117
+ NXlink,
114
118
  NXroot,
115
119
  NXsample,
116
120
  NXsource,
@@ -125,12 +129,19 @@ class TomoCHESSMapConverter(Processor):
125
129
  tomofields = get_nxroot(data, 'tomofields')
126
130
  detector_config = self.get_config(data, 'tomo.models.Detector')
127
131
 
128
- if darkfield is not None and not isinstance(darkfield, NXentry):
129
- raise ValueError('Invalid parameter darkfield ({darkfield})')
132
+ if darkfield is not None:
133
+ if isinstance(darkfield, NXroot):
134
+ darkfield = darkfield[darkfield.default]
135
+ if not isinstance(darkfield, NXentry):
136
+ raise ValueError(f'Invalid parameter darkfield ({darkfield})')
137
+ if isinstance(brightfield, NXroot):
138
+ brightfield = brightfield[brightfield.default]
130
139
  if not isinstance(brightfield, NXentry):
131
- raise ValueError('Invalid parameter brightfield ({brightfield})')
140
+ raise ValueError(f'Invalid parameter brightfield ({brightfield})')
141
+ if isinstance(tomofields, NXroot):
142
+ tomofields = tomofields[tomofields.default]
132
143
  if not isinstance(tomofields, NXentry):
133
- raise ValueError('Invalid parameter tomofields {tomofields})')
144
+ raise ValueError(f'Invalid parameter tomofields {tomofields})')
134
145
 
135
146
  # Construct NXroot
136
147
  nxroot = NXroot()
@@ -148,7 +159,7 @@ class TomoCHESSMapConverter(Processor):
148
159
  '(available independent dimensions: '
149
160
  f'{independent_dimensions})')
150
161
  rotation_angles_index = \
151
- tomofields.data.attrs['rotation_angles_indices']
162
+ tomofields.data.axes.index('rotation_angles')
152
163
  rotation_angle_data_type = \
153
164
  tomofields.data.rotation_angles.attrs['data_type']
154
165
  if rotation_angle_data_type != 'scan_column':
@@ -157,7 +168,7 @@ class TomoCHESSMapConverter(Processor):
157
168
  matched_dimensions.pop(matched_dimensions.index('rotation_angles'))
158
169
  if 'x_translation' in independent_dimensions:
159
170
  x_translation_index = \
160
- tomofields.data.attrs['x_translation_indices']
171
+ tomofields.data.axes.index('x_translation')
161
172
  x_translation_data_type = \
162
173
  tomofields.data.x_translation.attrs['data_type']
163
174
  x_translation_name = \
@@ -170,7 +181,7 @@ class TomoCHESSMapConverter(Processor):
170
181
  x_translation_data_type = None
171
182
  if 'z_translation' in independent_dimensions:
172
183
  z_translation_index = \
173
- tomofields.data.attrs['z_translation_indices']
184
+ tomofields.data.axes.index('z_translation')
174
185
  z_translation_data_type = \
175
186
  tomofields.data.z_translation.attrs['data_type']
176
187
  z_translation_name = \
@@ -188,9 +199,11 @@ class TomoCHESSMapConverter(Processor):
188
199
  '"rotation_angles"}')
189
200
 
190
201
  # Construct base NXentry and add to NXroot
191
- nxentry = NXentry()
192
- nxroot[map_config.title] = nxentry
193
- nxroot.attrs['default'] = map_config.title
202
+ nxentry = NXentry(name=map_config.title)
203
+ nxroot[nxentry.nxname] = nxentry
204
+ nxentry.set_default()
205
+
206
+ # Add configuration fields
194
207
  nxentry.definition = 'NXtomo'
195
208
  nxentry.map_config = tomofields.map_config
196
209
 
@@ -214,12 +227,12 @@ class TomoCHESSMapConverter(Processor):
214
227
  # Add an NXdetector to the NXinstrument
215
228
  # (do not fill in data fields yet)
216
229
  detector_prefix = detector_config.prefix
217
- detectors = list(set(tomofields.data.entries)
218
- - set(independent_dimensions))
230
+ detectors = list(
231
+ set(tomofields.data.entries) - set(independent_dimensions))
219
232
  if detector_prefix not in detectors:
220
233
  raise ValueError(f'Data for detector {detector_prefix} is '
221
234
  f'unavailable (available detectors: {detectors})')
222
- tomo_stacks = np.asarray(tomofields.data[detector_prefix])
235
+ tomo_stacks = tomofields.data[detector_prefix]
223
236
  tomo_stack_shape = tomo_stacks.shape
224
237
  assert len(tomo_stack_shape) == 2+len(independent_dimensions)
225
238
  assert tomo_stack_shape[-2] == detector_config.rows
@@ -272,8 +285,8 @@ class TomoCHESSMapConverter(Processor):
272
285
  num_image = data_shape[0]
273
286
  image_keys += num_image*[2]
274
287
  sequence_numbers += list(range(num_image))
275
- image_stacks.append(np.asarray(
276
- nxcollection.data[detector_prefix]))
288
+ image_stacks.append(
289
+ nxcollection.data[detector_prefix])
277
290
  rotation_angles += num_image*[0.0]
278
291
  if (x_translation_data_type == 'spec_motor' or
279
292
  z_translation_data_type == 'spec_motor'):
@@ -312,8 +325,8 @@ class TomoCHESSMapConverter(Processor):
312
325
  num_image = data_shape[0]
313
326
  image_keys += num_image*[1]
314
327
  sequence_numbers += list(range(num_image))
315
- image_stacks.append(np.asarray(
316
- nxcollection.data[detector_prefix]))
328
+ image_stacks.append(
329
+ nxcollection.data[detector_prefix])
317
330
  rotation_angles += num_image*[0.0]
318
331
  if (x_translation_data_type == 'spec_motor' or
319
332
  z_translation_data_type == 'spec_motor'):
@@ -347,10 +360,11 @@ class TomoCHESSMapConverter(Processor):
347
360
  z_trans = [0.0]
348
361
  tomo_stacks = np.reshape(tomo_stacks, (1,1,*tomo_stacks.shape))
349
362
  else:
350
- if len(list(tomofields.data.z_translation)):
351
- z_trans = list(tomofields.data.z_translation)
352
- else:
353
- z_trans = [float(tomofields.data.z_translation)]
363
+ z_trans = tomofields.data.z_translation.nxdata
364
+ # if len(list(tomofields.data.z_translation)):
365
+ # z_trans = list(tomofields.data.z_translation)
366
+ # else:
367
+ # z_trans = [float(tomofields.data.z_translation)]
354
368
  if rotation_angles_index < z_translation_index:
355
369
  tomo_stacks = np.swapaxes(
356
370
  tomo_stacks, rotation_angles_index,
@@ -363,14 +377,16 @@ class TomoCHESSMapConverter(Processor):
363
377
  tomo_stacks, rotation_angles_index, x_translation_index)
364
378
  tomo_stacks = np.expand_dims(tomo_stacks, 0)
365
379
  else:
366
- if len(list(tomofields.data.x_translation)):
367
- x_trans = list(tomofields.data.x_translation)
368
- else:
369
- x_trans = [float(tomofields.data.x_translation)]
370
- if len(list(tomofields.data.z_translation)):
371
- z_trans = list(tomofields.data.z_translation)
372
- else:
373
- z_trans = [float(tomofields.data.z_translation)]
380
+ x_trans = tomofields.data.x_translation.nxdata
381
+ z_trans = tomofields.data.z_translation.nxdata
382
+ #if tomofields.data.x_translation.size > 1:
383
+ # x_trans = list(tomofields.data.x_translation)
384
+ #else:
385
+ # x_trans = [float(tomofields.data.x_translation)]
386
+ #if len(list(tomofields.data.z_translation)):
387
+ # z_trans = list(tomofields.data.z_translation)
388
+ #else:
389
+ # z_trans = [float(tomofields.data.z_translation)]
374
390
  if (rotation_angles_index
375
391
  < max(x_translation_index, z_translation_index)):
376
392
  tomo_stacks = np.swapaxes(
@@ -380,8 +396,7 @@ class TomoCHESSMapConverter(Processor):
380
396
  tomo_stacks = np.swapaxes(
381
397
  tomo_stacks, x_translation_index, z_translation_index)
382
398
  # Restrict to 180 degrees set of data for now to match old code
383
- thetas = np.asarray(tomofields.data.rotation_angles)
384
- #RV num_image = len(tomofields.data.rotation_angles)
399
+ thetas = tomofields.data.rotation_angles.nxdata
385
400
  assert len(thetas) > 2
386
401
  delta_theta = thetas[1]-thetas[0]
387
402
  if thetas[-1]-thetas[0] > 180-delta_theta:
@@ -394,10 +409,8 @@ class TomoCHESSMapConverter(Processor):
394
409
  for j, x in enumerate(x_trans):
395
410
  image_keys += num_image*[0]
396
411
  sequence_numbers += list(range(num_image))
397
- image_stacks.append(np.asarray(
398
- tomo_stacks[i,j][:image_end,:,:]))
412
+ image_stacks.append(tomo_stacks[i,j,:image_end,:,:])
399
413
  rotation_angles += list(thetas)
400
- #RV rotation_angles += list(tomofields.data.rotation_angles)
401
414
  x_translations += num_image*[x]
402
415
  z_translations += num_image*[z]
403
416
 
@@ -415,18 +428,12 @@ class TomoCHESSMapConverter(Processor):
415
428
  nxsample.z_translation.units = 'mm'
416
429
 
417
430
  # Add an NXdata to NXentry
418
- nxdata = NXdata()
419
- nxentry.data = nxdata
420
- nxdata.makelink(nxentry.instrument.detector.data, name='data')
421
- nxdata.makelink(nxentry.instrument.detector.image_key)
422
- nxdata.makelink(nxentry.sample.rotation_angle)
423
- nxdata.makelink(nxentry.sample.x_translation)
424
- nxdata.makelink(nxentry.sample.z_translation)
425
- nxdata.attrs['signal'] = 'data'
426
- # nxdata.attrs['axes'] = ['field', 'row', 'column']
427
- # nxdata.attrs['field_indices'] = 0
428
- # nxdata.attrs['row_indices'] = 1
429
- # nxdata.attrs['column_indices'] = 2
431
+ nxentry.data = NXdata(NXlink(nxentry.instrument.detector.data))
432
+ nxentry.data.makelink(nxentry.instrument.detector.image_key)
433
+ nxentry.data.makelink(nxentry.sample.rotation_angle)
434
+ nxentry.data.makelink(nxentry.sample.x_translation)
435
+ nxentry.data.makelink(nxentry.sample.z_translation)
436
+ nxentry.data.set_default()
430
437
 
431
438
  return nxroot
432
439
 
@@ -439,9 +446,9 @@ class TomoDataProcessor(Processor):
439
446
  """
440
447
 
441
448
  def process(
442
- self, data, interactive=False, reduce_data=False,
449
+ self, data, outputdir='.', interactive=False, reduce_data=False,
443
450
  find_center=False, calibrate_center=False, reconstruct_data=False,
444
- combine_data=False, output_folder='.', save_figs='no', **kwargs):
451
+ combine_data=False, save_figs='no'):
445
452
  """
446
453
  Process the input map or configuration with the step specific
447
454
  instructions and return either a dictionary or a
@@ -450,6 +457,8 @@ class TomoDataProcessor(Processor):
450
457
  :param data: Input configuration and specific step instructions
451
458
  for tomographic image reduction.
452
459
  :type data: list[PipelineData]
460
+ :param outputdir: Output folder name, defaults to '.'.
461
+ :type outputdir:: str, optional
453
462
  :param interactive: Allows for user interactions,
454
463
  defaults to False.
455
464
  :type interactive: bool, optional
@@ -468,8 +477,6 @@ class TomoDataProcessor(Processor):
468
477
  :param combine_data: Combine the reconstructed tomography
469
478
  stacks, defaults to False.
470
479
  :type combine_data: bool, optional
471
- :param output_folder: Output folder name, defaults to '.'.
472
- :type output_folder:: str, optional
473
480
  :param save_figs: Safe figures to file ('yes' or 'only') and/or
474
481
  display figures ('yes' or 'no'), defaults to 'no'.
475
482
  :type save_figs: Literal['yes', 'no', 'only'], optional
@@ -480,10 +487,7 @@ class TomoDataProcessor(Processor):
480
487
  :rtype: Union[dict, nexusformat.nexus.NXroot]
481
488
  """
482
489
  # Local modules
483
- from nexusformat.nexus import (
484
- nxsetconfig,
485
- NXroot,
486
- )
490
+ from nexusformat.nexus import nxsetconfig
487
491
  from CHAP.pipeline import PipelineItem
488
492
  from CHAP.tomo.models import (
489
493
  TomoReduceConfig,
@@ -529,8 +533,8 @@ class TomoDataProcessor(Processor):
529
533
  nxroot = get_nxroot(data)
530
534
 
531
535
  tomo = Tomo(
532
- interactive=interactive, output_folder=output_folder,
533
- save_figs=save_figs)
536
+ logger=self.logger, interactive=interactive,
537
+ outputdir=outputdir, save_figs=save_figs)
534
538
 
535
539
  nxsetconfig(memory=100000)
536
540
 
@@ -539,7 +543,7 @@ class TomoDataProcessor(Processor):
539
543
  if (reduce_data or find_center
540
544
  or reconstruct_data or reconstruct_data_config is not None
541
545
  or combine_data or combine_data_config is not None):
542
- self._logger.warning('Ignoring any step specific instructions '
546
+ self.logger.warning('Ignoring any step specific instructions '
543
547
  'during center calibration')
544
548
  if nxroot is None:
545
549
  raise RuntimeError('Map info required to calibrate the '
@@ -588,7 +592,7 @@ class TomoDataProcessor(Processor):
588
592
 
589
593
  # Reconstruct tomography stacks
590
594
  # RV pass reconstruct_data_config and center_config directly to
591
- # tomo.reconstruct_data?
595
+ # tomo.reconstruct_data?
592
596
  if reconstruct_data or reconstruct_data_config is not None:
593
597
  if reconstruct_data_config is None:
594
598
  reconstruct_data_config = TomoReconstructConfig()
@@ -606,49 +610,6 @@ class TomoDataProcessor(Processor):
606
610
  return center_config
607
611
  return nxroot
608
612
 
609
- def nxcopy(nxobject, exclude_nxpaths=None, nxpath_prefix=''):
610
- """
611
- Function that returns a copy of a nexus object, optionally exluding
612
- certain child items.
613
-
614
- :param nxobject: The input nexus object to "copy".
615
- :type nxobject: nexusformat.nexus.NXobject
616
- :param exlude_nxpaths: A list of paths to child nexus objects that
617
- should be excluded from the returned "copy", defaults to `[]`.
618
- :type exclude_nxpaths: list[str], optional
619
- :param nxpath_prefix: For use in recursive calls from inside this
620
- function only.
621
- :type nxpath_prefix: str
622
- :return: Copy of the input `nxobject` with some children optionally
623
- exluded.
624
- :rtype: nexusformat.nexus.NXobject
625
- """
626
- # Third party modules
627
- from nexusformat.nexus import NXgroup
628
-
629
- nxobject_copy = nxobject.__class__()
630
- if not nxpath_prefix:
631
- if 'default' in nxobject.attrs:
632
- nxobject_copy.attrs['default'] = nxobject.attrs['default']
633
- else:
634
- for k, v in nxobject.attrs.items():
635
- nxobject_copy.attrs[k] = v
636
-
637
- if exclude_nxpaths is None:
638
- exclude_nxpaths = []
639
- for k, v in nxobject.items():
640
- nxpath = os_path.join(nxpath_prefix, k)
641
- if nxpath in exclude_nxpaths:
642
- continue
643
- if isinstance(v, NXgroup):
644
- nxobject_copy[k] = nxcopy(
645
- v, exclude_nxpaths=exclude_nxpaths,
646
- nxpath_prefix=os_path.join(nxpath_prefix, k))
647
- else:
648
- nxobject_copy[k] = v
649
-
650
- return nxobject_copy
651
-
652
613
 
653
614
  class SetNumexprThreads:
654
615
  """
@@ -694,7 +655,7 @@ class Tomo:
694
655
  """Reconstruct a set of tomographic images."""
695
656
 
696
657
  def __init__(
697
- self, interactive=False, num_core=-1, output_folder='.',
658
+ self, logger=None, outputdir='.', interactive=False, num_core=-1,
698
659
  save_figs='no'):
699
660
  """
700
661
  Initialize Tomo.
@@ -704,28 +665,31 @@ class Tomo:
704
665
  :type interactive: bool, optional
705
666
  :param num_core: Number of processors.
706
667
  :type num_core: int
707
- :param output_folder: Output folder name, defaults to '.'.
708
- :type output_folder:: str, optional
668
+ :param outputdir: Output folder name, defaults to '.'.
669
+ :type outputdir:: str, optional
709
670
  :param save_figs: Safe figures to file ('yes' or 'only') and/or
710
671
  display figures ('yes' or 'no'), defaults to 'no'.
711
672
  :type save_figs: Literal['yes', 'no', 'only'], optional
712
673
  :raises ValueError: Invalid input parameter.
713
674
  """
714
675
  # System modules
715
- from logging import getLogger
716
676
  from multiprocessing import cpu_count
717
677
 
718
678
  self.__name__ = self.__class__.__name__
719
- self._logger = getLogger(self.__name__)
720
- self._logger.propagate = False
679
+ if logger is None:
680
+ # System modules
681
+ from logging import getLogger
682
+
683
+ self._logger = getLogger(self.__name__)
684
+ self._logger.propagate = False
685
+ else:
686
+ self._logger = logger
721
687
 
722
688
  if not isinstance(interactive, bool):
723
689
  raise ValueError(f'Invalid parameter interactive ({interactive})')
690
+ self._outputdir = outputdir
724
691
  self._interactive = interactive
725
692
  self._num_core = num_core
726
- self._output_folder = os_path.abspath(output_folder)
727
- if not os_path.isdir(self._output_folder):
728
- mkdir(self._output_folder)
729
693
  self._test_config = {}
730
694
  if save_figs == 'only':
731
695
  self._save_only = True
@@ -751,6 +715,12 @@ class Tomo:
751
715
  f'num_core = {self._num_core} is larger than the number '
752
716
  f'of available processors and reduced to {cpu_count()}')
753
717
  self._num_core = cpu_count()
718
+ # Tompy py uses numexpr with NUMEXPR_MAX_THREADS = 64
719
+ if self._num_core > 64:
720
+ self._logger.warning(
721
+ f'num_core = {self._num_core} is larger than the number '
722
+ f'of processors suitable to Tomopy and reduced to 64')
723
+ self._num_core = 64
754
724
 
755
725
  def reduce_data(
756
726
  self, nxroot, tool_config=None, calibrate_center_rows=False):
@@ -775,8 +745,9 @@ class Tomo:
775
745
 
776
746
  self._logger.info('Generate the reduced tomography images')
777
747
 
748
+ # Validate input parameter
778
749
  if isinstance(nxroot, NXroot):
779
- nxentry = nxroot[nxroot.attrs['default']]
750
+ nxentry = nxroot[nxroot.default]
780
751
  else:
781
752
  raise ValueError(
782
753
  f'Invalid parameter nxroot {type(nxroot)}:\n{nxroot}')
@@ -796,13 +767,11 @@ class Tomo:
796
767
  self._logger.warning('Ignoring parameter img_row_bounds '
797
768
  'during rotation axis calibration')
798
769
  img_row_bounds = None
799
-
800
770
  image_key = nxentry.instrument.detector.get('image_key', None)
801
771
  if image_key is None or 'data' not in nxentry.instrument.detector:
802
772
  raise ValueError(f'Unable to find image_key or data in '
803
773
  'instrument.detector '
804
774
  f'({nxentry.instrument.detector.tree})')
805
- image_key = np.asarray(image_key)
806
775
 
807
776
  # Create an NXprocess to store data reduction (meta)data
808
777
  reduced_data = NXprocess()
@@ -813,7 +782,7 @@ class Tomo:
813
782
  # Generate bright field
814
783
  reduced_data = self._gen_bright(nxentry, reduced_data, image_key)
815
784
 
816
- # Get rotation angles for image stacks
785
+ # Get rotation angles for image stacks (in degrees)
817
786
  thetas = self._gen_thetas(nxentry, image_key)
818
787
 
819
788
  # Get the image stack mask to remove bad images from stack
@@ -829,7 +798,7 @@ class Tomo:
829
798
  len(thetas)) < drop_fraction/100, 0, 1).astype(bool)
830
799
 
831
800
  # Set zoom and/or rotation angle interval to reduce memory
832
- # requirement
801
+ # requirement
833
802
  if image_mask is None:
834
803
  zoom_perc, delta_theta = self._set_zoom_or_delta_theta(
835
804
  thetas, delta_theta)
@@ -851,12 +820,12 @@ class Tomo:
851
820
  img_row_bounds = self._set_detector_bounds(
852
821
  nxentry, reduced_data, image_key, thetas[0],
853
822
  img_row_bounds, calibrate_center_rows)
854
- self._logger.info(f'img_row_bounds = {img_row_bounds}')
823
+ self._logger.debug(f'img_row_bounds = {img_row_bounds}')
855
824
  if calibrate_center_rows:
856
825
  calibrate_center_rows = tuple(sorted(img_row_bounds))
857
826
  img_row_bounds = None
858
827
  if img_row_bounds is None:
859
- tbf_shape = np.asarray(reduced_data.data.bright_field).shape
828
+ tbf_shape = reduced_data.data.bright_field.shape
860
829
  img_row_bounds = (0, tbf_shape[0])
861
830
  reduced_data.img_row_bounds = img_row_bounds
862
831
  reduced_data.img_row_bounds.units = 'pixels'
@@ -873,34 +842,33 @@ class Tomo:
873
842
  nxentry, reduced_data, image_key, calibrate_center_rows)
874
843
 
875
844
  # Create a copy of the input Nexus object and remove raw and
876
- # any existing reduced data
877
- if isinstance(nxroot, NXroot):
878
- exclude_items = [
879
- f'{nxentry.nxname}/reduced_data/data',
880
- f'{nxentry.nxname}/instrument/detector/data',
881
- f'{nxentry.nxname}/instrument/detector/image_key',
882
- f'{nxentry.nxname}/instrument/detector/sequence_number',
883
- f'{nxentry.nxname}/sample/rotation_angle',
884
- f'{nxentry.nxname}/sample/x_translation',
885
- f'{nxentry.nxname}/sample/z_translation',
886
- f'{nxentry.nxname}/data/data',
887
- f'{nxentry.nxname}/data/image_key',
888
- f'{nxentry.nxname}/data/rotation_angle',
889
- f'{nxentry.nxname}/data/x_translation',
890
- f'{nxentry.nxname}/data/z_translation',
891
- ]
892
- nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
893
- nxentry = nxroot[nxroot.attrs['default']]
845
+ # any existing reduced data
846
+ exclude_items = [
847
+ f'{nxentry.nxname}/reduced_data/data',
848
+ f'{nxentry.nxname}/instrument/detector/data',
849
+ f'{nxentry.nxname}/instrument/detector/image_key',
850
+ f'{nxentry.nxname}/instrument/detector/sequence_number',
851
+ f'{nxentry.nxname}/sample/rotation_angle',
852
+ f'{nxentry.nxname}/sample/x_translation',
853
+ f'{nxentry.nxname}/sample/z_translation',
854
+ f'{nxentry.nxname}/data/data',
855
+ f'{nxentry.nxname}/data/image_key',
856
+ f'{nxentry.nxname}/data/rotation_angle',
857
+ f'{nxentry.nxname}/data/x_translation',
858
+ f'{nxentry.nxname}/data/z_translation',
859
+ ]
860
+ nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
894
861
 
895
862
  # Add the reduced data NXprocess
863
+ nxentry = nxroot[nxroot.default]
896
864
  nxentry.reduced_data = reduced_data
897
865
 
898
866
  if 'data' not in nxentry:
899
867
  nxentry.data = NXdata()
868
+ nxentry.data.set_default()
900
869
  nxentry.data.makelink(
901
870
  nxentry.reduced_data.data.tomo_fields, name='reduced_data')
902
- nxentry.data.makelink(
903
- nxentry.reduced_data.rotation_angle, name='rotation_angle')
871
+ nxentry.data.makelink(nxentry.reduced_data.rotation_angle)
904
872
  nxentry.data.attrs['signal'] = 'reduced_data'
905
873
 
906
874
  return nxroot, calibrate_center_rows
@@ -920,51 +888,48 @@ class Tomo:
920
888
  :rtype: dict
921
889
  """
922
890
  # Third party modules
923
- from nexusformat.nexus import (
924
- NXentry,
925
- NXroot,
926
- )
891
+ from nexusformat.nexus import NXroot
927
892
  from yaml import safe_dump
928
893
 
929
894
  self._logger.info('Find the calibrated center axis info')
930
895
 
931
896
  if isinstance(nxroot, NXroot):
932
- nxentry = nxroot[nxroot.attrs['default']]
897
+ nxentry = nxroot[nxroot.default]
933
898
  else:
934
899
  raise ValueError(f'Invalid parameter nxroot ({nxroot})')
935
900
 
936
901
  # Check if reduced data is available
937
- if ('reduced_data' not in nxentry
938
- or 'reduced_data' not in nxentry.data):
902
+ if 'reduced_data' not in nxentry:
939
903
  raise ValueError(f'Unable to find valid reduced data in {nxentry}.')
940
904
 
941
905
  # Select the image stack to find the calibrated center axis
942
- # reduced data axes order: stack,theta,row,column
906
+ # reduced data axes order: stack,theta,row,column
943
907
  # Note: Nexus can't follow a link if the data it points to is
944
- # too big get the data from the actual place, not from
945
- # nxentry.data
908
+ # too big get the data from the actual place, not from
909
+ # nxentry.data
946
910
  num_tomo_stacks = nxentry.reduced_data.data.tomo_fields.shape[0]
911
+ self._logger.debug(f'num_tomo_stacks = {num_tomo_stacks}')
947
912
  if num_tomo_stacks == 1:
948
913
  center_stack_index = 0
949
914
  else:
950
915
  center_stack_index = tool_config.center_stack_index
951
916
  if calibrate_center_rows:
952
- center_stack_index = int(num_tomo_stacks/2)
917
+ center_stack_index = num_tomo_stacks//2
953
918
  elif self._interactive:
954
919
  if center_stack_index is None:
955
920
  center_stack_index = input_int(
956
921
  '\nEnter tomography stack index to calibrate the '
957
922
  'center axis', ge=0, lt=num_tomo_stacks,
958
- default=int(num_tomo_stacks/2))
923
+ default=num_tomo_stacks//2)
959
924
  else:
960
925
  if center_stack_index is None:
961
- center_stack_index = int(num_tomo_stacks/2)
926
+ center_stack_index = num_tomo_stacks//2
962
927
  self._logger.warning(
963
928
  'center_stack_index unspecified, use stack '
964
929
  f'{center_stack_index} to find center axis info')
965
930
 
966
931
  # Get thetas (in degrees)
967
- thetas = np.asarray(nxentry.reduced_data.rotation_angle)
932
+ thetas = nxentry.reduced_data.rotation_angle.nxdata
968
933
 
969
934
  # Select center rows
970
935
  if calibrate_center_rows:
@@ -975,7 +940,7 @@ class Tomo:
975
940
  import matplotlib.pyplot as plt
976
941
 
977
942
  # Get full bright field
978
- tbf = np.asarray(nxentry.reduced_data.data.bright_field)
943
+ tbf = nxentry.reduced_data.data.bright_field.nxdata
979
944
  tbf_shape = tbf.shape
980
945
 
981
946
  # Get image bounds
@@ -1022,8 +987,7 @@ class Tomo:
1022
987
  # Plot results
1023
988
  if self._save_figs:
1024
989
  fig.savefig(
1025
- os_path.join(
1026
- self._output_folder, 'center_finding_rows.png'))
990
+ os_path.join(self._outputdir, 'center_finding_rows.png'))
1027
991
  plt.close()
1028
992
 
1029
993
  # Get effective pixel_size
@@ -1047,12 +1011,13 @@ class Tomo:
1047
1011
  t0 = time()
1048
1012
  center_offsets.append(
1049
1013
  self._find_center_one_plane(
1050
- nxentry.reduced_data.data.tomo_fields[
1051
- center_stack_index,:,offset_row,:],
1052
- row, thetas, eff_pixel_size, cross_sectional_dim,
1053
- path=self._output_folder, num_core=self._num_core,
1014
+ nxentry.reduced_data.data.tomo_fields, center_stack_index,
1015
+ row, offset_row, np.radians(thetas), eff_pixel_size,
1016
+ cross_sectional_dim, path=self._outputdir,
1017
+ num_core=self._num_core,
1054
1018
  center_offset_min=tool_config.center_offset_min,
1055
1019
  center_offset_max=tool_config.center_offset_max,
1020
+ center_search_range=tool_config.center_search_range,
1056
1021
  gaussian_sigma=tool_config.gaussian_sigma,
1057
1022
  ring_width=tool_config.ring_width,
1058
1023
  prev_center_offset=prev_center_offset))
@@ -1097,9 +1062,8 @@ class Tomo:
1097
1062
  """
1098
1063
  # Third party modules
1099
1064
  from nexusformat.nexus import (
1100
- nxgetconfig,
1101
1065
  NXdata,
1102
- NXentry,
1066
+ NXfield,
1103
1067
  NXprocess,
1104
1068
  NXroot,
1105
1069
  )
@@ -1107,15 +1071,14 @@ class Tomo:
1107
1071
  self._logger.info('Reconstruct the tomography data')
1108
1072
 
1109
1073
  if isinstance(nxroot, NXroot):
1110
- nxentry = nxroot[nxroot.attrs['default']]
1074
+ nxentry = nxroot[nxroot.default]
1111
1075
  else:
1112
1076
  raise ValueError(f'Invalid parameter nxroot ({nxroot})')
1113
1077
  if not isinstance(center_info, dict):
1114
1078
  raise ValueError(f'Invalid parameter center_info ({center_info})')
1115
1079
 
1116
1080
  # Check if reduced data is available
1117
- if ('reduced_data' not in nxentry
1118
- or 'reduced_data' not in nxentry.data):
1081
+ if 'reduced_data' not in nxentry:
1119
1082
  raise ValueError(f'Unable to find valid reduced data in {nxentry}.')
1120
1083
 
1121
1084
  # Create an NXprocess to store image reconstruction (meta)data
@@ -1132,38 +1095,28 @@ class Tomo:
1132
1095
  / (center_rows[1]-center_rows[0])
1133
1096
 
1134
1097
  # Get thetas (in degrees)
1135
- thetas = np.asarray(nxentry.reduced_data.rotation_angle)
1098
+ thetas = nxentry.reduced_data.rotation_angle.nxdata
1136
1099
 
1137
1100
  # Reconstruct tomography data
1138
- # reduced data axes order: stack,theta,row,column
1139
- # reconstructed data: row/-z,y,x
1101
+ # - reduced data axes order: stack,theta,row,column
1102
+ # - reconstructed data axes order: row/-z,y,x
1140
1103
  # Note: Nexus can't follow a link if the data it points to is
1141
- # too big get the data from the actual place, not from
1142
- # nxentry.data
1104
+ # too big get the data from the actual place, not from
1105
+ # nxentry.data
1143
1106
  if 'zoom_perc' in nxentry.reduced_data:
1144
1107
  res_title = f'{nxentry.reduced_data.attrs["zoom_perc"]}p'
1145
1108
  else:
1146
1109
  res_title = 'fullres'
1147
- tomo_stacks = np.asarray(nxentry.reduced_data.data.tomo_fields)
1110
+ tomo_stacks = nxentry.reduced_data.data.tomo_fields
1148
1111
  num_tomo_stacks = tomo_stacks.shape[0]
1149
- tomo_recon_stacks = num_tomo_stacks*[np.array([])]
1112
+ tomo_recon_stacks = []
1150
1113
  img_row_bounds = tuple(nxentry.reduced_data.get(
1151
1114
  'img_row_bounds', (0, tomo_stacks.shape[2])))
1152
1115
  center_rows -= img_row_bounds[0]
1153
1116
  for i in range(num_tomo_stacks):
1154
1117
  # Convert reduced data stack from theta,row,column to
1155
- # row,theta,column
1156
- t0 = time()
1157
- tomo_stack = tomo_stacks[i]
1158
- self._logger.info(
1159
- f'Reading reduced data stack {i} took {time()-t0:.2f} '
1160
- 'seconds')
1161
- if (len(tomo_stack.shape) != 3
1162
- or any(True for dim in tomo_stack.shape if not dim)):
1163
- raise RuntimeError(
1164
- f'Unable to load tomography stack {i} for '
1165
- 'reconstruction')
1166
- tomo_stack = np.swapaxes(tomo_stack, 0, 1)
1118
+ # row,theta,column
1119
+ tomo_stack = np.swapaxes(tomo_stacks[i,:,:,:], 0, 1)
1167
1120
  assert len(thetas) == tomo_stack.shape[1]
1168
1121
  assert 0 <= center_rows[0] < center_rows[1] < tomo_stack.shape[0]
1169
1122
  center_offsets = [
@@ -1173,41 +1126,42 @@ class Tomo:
1173
1126
  ]
1174
1127
  t0 = time()
1175
1128
  tomo_recon_stack = self._reconstruct_one_tomo_stack(
1176
- tomo_stack, thetas, center_offsets=center_offsets,
1129
+ tomo_stack, np.radians(thetas), center_offsets=center_offsets,
1177
1130
  num_core=self._num_core, algorithm='gridrec',
1178
1131
  secondary_iters=tool_config.secondary_iters,
1132
+ gaussian_sigma=tool_config.gaussian_sigma,
1179
1133
  remove_stripe_sigma=tool_config.remove_stripe_sigma,
1180
1134
  ring_width=tool_config.ring_width)
1181
1135
  self._logger.info(
1182
1136
  f'Reconstruction of stack {i} took {time()-t0:.2f} seconds')
1183
1137
 
1184
1138
  # Combine stacks
1185
- tomo_recon_stacks[i] = tomo_recon_stack
1139
+ tomo_recon_stacks.append(tomo_recon_stack)
1186
1140
 
1187
1141
  # Resize the reconstructed tomography data
1188
- # reconstructed data order in each stack: row/-z,y,x
1142
+ # - reconstructed axis data order in each stack: row/-z,y,x
1189
1143
  tomo_recon_shape = tomo_recon_stacks[0].shape
1190
1144
  x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
1191
1145
  tomo_recon_stacks, x_bounds=tool_config.x_bounds,
1192
1146
  y_bounds=tool_config.y_bounds, z_bounds=tool_config.z_bounds)
1193
1147
  if x_bounds is None:
1194
1148
  x_range = (0, tomo_recon_shape[2])
1195
- x_slice = int(x_range[1]/2)
1149
+ x_slice = x_range[1]//2
1196
1150
  else:
1197
1151
  x_range = (min(x_bounds), max(x_bounds))
1198
- x_slice = int((x_bounds[0]+x_bounds[1]) / 2)
1152
+ x_slice = (x_bounds[0]+x_bounds[1])//2
1199
1153
  if y_bounds is None:
1200
1154
  y_range = (0, tomo_recon_shape[1])
1201
- y_slice = int(y_range[1] / 2)
1155
+ y_slice = y_range[1]//2
1202
1156
  else:
1203
1157
  y_range = (min(y_bounds), max(y_bounds))
1204
- y_slice = int((y_bounds[0]+y_bounds[1]) / 2)
1158
+ y_slice = (y_bounds[0]+y_bounds[1])//2
1205
1159
  if z_bounds is None:
1206
1160
  z_range = (0, tomo_recon_shape[0])
1207
- z_slice = int(z_range[1] / 2)
1161
+ z_slice = z_range[1]//2
1208
1162
  else:
1209
1163
  z_range = (min(z_bounds), max(z_bounds))
1210
- z_slice = int((z_bounds[0]+z_bounds[1]) / 2)
1164
+ z_slice = (z_bounds[0]+z_bounds[1])//2
1211
1165
  z_dim_org = tomo_recon_shape[0]
1212
1166
  for i, stack in enumerate(tomo_recon_stacks):
1213
1167
  tomo_recon_stacks[i] = stack[
@@ -1215,18 +1169,17 @@ class Tomo:
1215
1169
  x_range[0]:x_range[1]]
1216
1170
  tomo_recon_stacks = np.asarray(tomo_recon_stacks)
1217
1171
 
1218
- row_pixel_size = float(
1219
- nxentry.instrument.detector.row_pixel_size)
1220
- column_pixel_size = float(
1221
- nxentry.instrument.detector.column_pixel_size)
1172
+ detector = nxentry.instrument.detector
1173
+ row_pixel_size = float(detector.row_pixel_size)
1174
+ column_pixel_size = float(detector.column_pixel_size)
1222
1175
  if num_tomo_stacks == 1:
1223
1176
  # Convert the reconstructed tomography data from internal
1224
- # coordinate frame row/-z,y,x with the origin on the
1225
- # near-left-top corner to an z,y,x coordinate frame
1226
- # with the origin on the par file x,z values, halfway
1227
- # in the y-dimension.
1228
- # Here x is to the right, y along the beam direction
1229
- # and z upwards in the lab frame of reference
1177
+ # coordinate frame row/-z,y,x with the origin on the
1178
+ # near-left-top corner to an z,y,x coordinate frame with
1179
+ # the origin on the par file x,z values, halfway in the
1180
+ # y-dimension.
1181
+ # Here x is to the right, y along the beam direction and
1182
+ # z upwards in the lab frame of reference
1230
1183
  tomo_recon_stack = np.flip(tomo_recon_stacks[0], 0)
1231
1184
  z_range = (z_dim_org-z_range[1], z_dim_org-z_range[0])
1232
1185
 
@@ -1234,19 +1187,17 @@ class Tomo:
1234
1187
  x = column_pixel_size * (
1235
1188
  np.linspace(
1236
1189
  x_range[0], x_range[1], x_range[1]-x_range[0], False)
1237
- - 0.5*nxentry.instrument.detector.columns
1238
- + 0.5)
1190
+ - 0.5*detector.columns + 0.5)
1239
1191
  x = np.asarray(x + nxentry.reduced_data.x_translation[0])
1240
1192
  y = np.asarray(
1241
1193
  column_pixel_size * (
1242
1194
  np.linspace(
1243
1195
  y_range[0], y_range[1], y_range[1]-y_range[0], False)
1244
- - 0.5*nxentry.instrument.detector.columns
1245
- + 0.5))
1196
+ - 0.5*detector.columns + 0.5))
1246
1197
  z = row_pixel_size*(
1247
1198
  np.linspace(
1248
1199
  z_range[0], z_range[1], z_range[1]-z_range[0], False)
1249
- + nxentry.instrument.detector.rows
1200
+ + detector.rows
1250
1201
  - int(nxentry.reduced_data.img_row_bounds[1])
1251
1202
  + 0.5)
1252
1203
  z = np.asarray(z + nxentry.reduced_data.z_translation[0])
@@ -1262,7 +1213,7 @@ class Tomo:
1262
1213
  quick_imshow(
1263
1214
  tomo_recon_stack[:,:,x_index],
1264
1215
  title=f'recon {res_title} x={x[x_index]:.4f}',
1265
- origin='lower', extent=extent, path=self._output_folder,
1216
+ origin='lower', extent=extent, path=self._outputdir,
1266
1217
  save_fig=True, save_only=True)
1267
1218
  y_index = y_slice-y_range[0]
1268
1219
  extent = (
@@ -1273,7 +1224,7 @@ class Tomo:
1273
1224
  quick_imshow(
1274
1225
  tomo_recon_stack[:,y_index,:],
1275
1226
  title=f'recon {res_title} y={y[y_index]:.4f}',
1276
- origin='lower', extent=extent, path=self._output_folder,
1227
+ origin='lower', extent=extent, path=self._outputdir,
1277
1228
  save_fig=True, save_only=True)
1278
1229
  z_index = z_slice-z_range[0]
1279
1230
  extent = (
@@ -1284,7 +1235,7 @@ class Tomo:
1284
1235
  quick_imshow(
1285
1236
  tomo_recon_stack[z_index,:,:],
1286
1237
  title=f'recon {res_title} z={z[z_index]:.4f}',
1287
- origin='lower', extent=extent, path=self._output_folder,
1238
+ origin='lower', extent=extent, path=self._outputdir,
1288
1239
  save_fig=True, save_only=True)
1289
1240
  else:
1290
1241
  # Plot a few reconstructed image slices
@@ -1294,25 +1245,23 @@ class Tomo:
1294
1245
  title = f'{basetitle} {res_title} xslice{x_slice}'
1295
1246
  quick_imshow(
1296
1247
  tomo_recon_stacks[i,:,:,x_slice-x_range[0]],
1297
- title=title, path=self._output_folder,
1298
- save_fig=True, save_only=True)
1248
+ title=title, path=self._outputdir, save_fig=True,
1249
+ save_only=True)
1299
1250
  title = f'{basetitle} {res_title} yslice{y_slice}'
1300
1251
  quick_imshow(
1301
1252
  tomo_recon_stacks[i,:,y_slice-y_range[0],:],
1302
- title=title, path=self._output_folder,
1303
- save_fig=True, save_only=True)
1253
+ title=title, path=self._outputdir, save_fig=True,
1254
+ save_only=True)
1304
1255
  title = f'{basetitle} {res_title} zslice{z_slice}'
1305
1256
  quick_imshow(
1306
1257
  tomo_recon_stacks[i,z_slice-z_range[0],:,:],
1307
- title=title, path=self._output_folder,
1308
- save_fig=True, save_only=True)
1258
+ title=title, path=self._outputdir, save_fig=True,
1259
+ save_only=True)
1309
1260
 
1310
1261
  # Add image reconstruction to reconstructed data NXprocess
1311
- # reconstructed data order:
1312
- # - for one stack: z,y,x
1313
- # - for multiple stacks: row/-z,y,x
1314
- nxprocess.data = NXdata()
1315
- nxprocess.attrs['default'] = 'data'
1262
+ # reconstructed axis data order:
1263
+ # - for one stack: z,y,x
1264
+ # - for multiple stacks: row/-z,y,x
1316
1265
  for k, v in center_info.items():
1317
1266
  nxprocess[k] = v
1318
1267
  if k == 'center_rows' or k == 'center_offsets':
@@ -1335,53 +1284,45 @@ class Tomo:
1335
1284
  nxprocess.z_bounds.units = 'pixels'
1336
1285
  nxprocess.z_bounds.attrs['long_name'] = \
1337
1286
  'z range indices in reduced data frame of reference'
1338
- nxprocess.data.attrs['signal'] = 'reconstructed_data'
1339
1287
  if num_tomo_stacks == 1:
1340
- nxprocess.data.reconstructed_data = tomo_recon_stack
1341
- nxprocess.data.attrs['axes'] = ['z', 'y', 'x']
1342
- nxprocess.data.attrs['x_indices'] = 2
1343
- nxprocess.data.attrs['y_indices'] = 1
1344
- nxprocess.data.attrs['z_indices'] = 0
1345
- nxprocess.data.x = x
1346
- nxprocess.data.x.units = \
1347
- nxentry.instrument.detector.column_pixel_size.units
1348
- nxprocess.data.y = y
1349
- nxprocess.data.y.units = \
1350
- nxentry.instrument.detector.column_pixel_size.units
1351
- nxprocess.data.z = z
1352
- nxprocess.data.z.units = \
1353
- nxentry.instrument.detector.row_pixel_size.units
1288
+ nxprocess.data = NXdata(
1289
+ NXfield(tomo_recon_stack, 'reconstructed_data'),
1290
+ (NXfield(
1291
+ z, 'z', attrs={'units': detector.row_pixel_size.units}),
1292
+ NXfield(
1293
+ y, 'y',
1294
+ attrs={'units': detector.column_pixel_size.units}),
1295
+ NXfield(
1296
+ x, 'x',
1297
+ attrs={'units': detector.column_pixel_size.units}),))
1354
1298
  else:
1355
- nxprocess.data.reconstructed_data = tomo_recon_stacks
1299
+ nxprocess.data = NXdata(
1300
+ NXfield(tomo_recon_stacks, 'reconstructed_data'))
1356
1301
 
1357
1302
  # Create a copy of the input Nexus object and remove reduced
1358
- # data
1303
+ # data
1359
1304
  exclude_items = [
1360
1305
  f'{nxentry.nxname}/reduced_data/data',
1361
1306
  f'{nxentry.nxname}/data/reduced_data',
1362
1307
  f'{nxentry.nxname}/data/rotation_angle',
1363
1308
  ]
1364
- nxroot_copy = nxcopy(nxroot, exclude_nxpaths=exclude_items)
1309
+ nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
1365
1310
 
1366
1311
  # Add the reconstructed data NXprocess to the new Nexus object
1367
- nxentry_copy = nxroot_copy[nxroot_copy.attrs['default']]
1368
- nxentry_copy.reconstructed_data = nxprocess
1369
- if 'data' not in nxentry_copy:
1370
- nxentry_copy.data = NXdata()
1371
- nxentry_copy.attrs['default'] = 'data'
1372
- nxentry_copy.data.makelink(
1373
- nxprocess.data.reconstructed_data, name='reconstructed_data')
1374
- nxentry_copy.data.attrs['signal'] = 'reconstructed_data'
1312
+ nxentry = nxroot[nxroot.default]
1313
+ nxentry.reconstructed_data = nxprocess
1314
+ if 'data' not in nxentry:
1315
+ nxentry.data = NXdata()
1316
+ nxentry.data.set_default()
1317
+ nxentry.data.makelink(nxprocess.data.reconstructed_data)
1375
1318
  if num_tomo_stacks == 1:
1376
- nxentry_copy.data.attrs['axes'] = ['z', 'y', 'x']
1377
- nxentry_copy.data.attrs['x_indices'] = 2
1378
- nxentry_copy.data.attrs['y_indices'] = 1
1379
- nxentry_copy.data.attrs['z_indices'] = 0
1380
- nxentry_copy.data.makelink(nxprocess.data.x, name='x')
1381
- nxentry_copy.data.makelink(nxprocess.data.y, name='y')
1382
- nxentry_copy.data.makelink(nxprocess.data.z, name='z')
1319
+ nxentry.data.attrs['axes'] = ['z', 'y', 'x']
1320
+ nxentry.data.makelink(nxprocess.data.x)
1321
+ nxentry.data.makelink(nxprocess.data.y)
1322
+ nxentry.data.makelink(nxprocess.data.z)
1323
+ nxentry.data.attrs['signal'] = 'reconstructed_data'
1383
1324
 
1384
- return nxroot_copy
1325
+ return nxroot
1385
1326
 
1386
1327
  def combine_data(self, nxroot, tool_config):
1387
1328
  """Combine the reconstructed tomography stacks.
@@ -1399,7 +1340,7 @@ class Tomo:
1399
1340
  # Third party modules
1400
1341
  from nexusformat.nexus import (
1401
1342
  NXdata,
1402
- NXentry,
1343
+ NXfield,
1403
1344
  NXprocess,
1404
1345
  NXroot,
1405
1346
  )
@@ -1407,18 +1348,17 @@ class Tomo:
1407
1348
  self._logger.info('Combine the reconstructed tomography stacks')
1408
1349
 
1409
1350
  if isinstance(nxroot, NXroot):
1410
- nxentry = nxroot[nxroot.attrs['default']]
1351
+ nxentry = nxroot[nxroot.default]
1411
1352
  else:
1412
1353
  raise ValueError(f'Invalid parameter nxroot ({nxroot})')
1413
1354
 
1414
1355
  # Check if reconstructed image data is available
1415
- if ('reconstructed_data' not in nxentry
1416
- or 'reconstructed_data' not in nxentry.data):
1356
+ if 'reconstructed_data' not in nxentry:
1417
1357
  raise KeyError(
1418
1358
  f'Unable to find valid reconstructed image data in {nxentry}')
1419
1359
 
1420
1360
  # Create an NXprocess to store combined image reconstruction
1421
- # (meta)data
1361
+ # (meta)data
1422
1362
  nxprocess = NXprocess()
1423
1363
 
1424
1364
  if nxentry.reconstructed_data.data.reconstructed_data.ndim == 3:
@@ -1431,25 +1371,30 @@ class Tomo:
1431
1371
  return nxroot
1432
1372
 
1433
1373
  # Get and combine the reconstructed stacks
1434
- # reconstructed data order: stack,row/-z,y,x
1374
+ # - reconstructed axis data order: stack,row/-z,y,x
1435
1375
  # Note: Nexus can't follow a link if the data it points to is
1436
- # too big. So get the data from the actual place, not from
1437
- # nxentry.data
1438
- # (load one stack at a time to reduce risk of hitting Nexus
1439
- # data access limit)
1376
+ # too big. So get the data from the actual place, not from
1377
+ # nxentry.data
1378
+ # Also load one stack at a time to reduce risk of hitting Nexus
1379
+ # data access limit
1440
1380
  t0 = time()
1441
1381
  tomo_recon_combined = \
1442
1382
  nxentry.reconstructed_data.data.reconstructed_data[0,:,:,:]
1383
+ # RV check this out more
1384
+ # tomo_recon_combined = np.concatenate(
1385
+ # [tomo_recon_combined]
1386
+ # + [nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
1387
+ # for i in range(1, num_tomo_stacks)])
1443
1388
  tomo_recon_combined = np.concatenate(
1444
- [tomo_recon_combined]
1445
- + [nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
1446
- for i in range(1, num_tomo_stacks)])
1389
+ [nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
1390
+ for i in range(num_tomo_stacks-1, 0, -1)]
1391
+ + [tomo_recon_combined])
1447
1392
  self._logger.info(
1448
1393
  f'Combining the reconstructed stacks took {time()-t0:.2f} seconds')
1449
1394
  tomo_shape = tomo_recon_combined.shape
1450
1395
 
1451
1396
  # Resize the combined tomography data stacks
1452
- # combined data order: row/-z,y,x
1397
+ # - combined axis data order: row/-z,y,x
1453
1398
  if self._interactive:
1454
1399
  x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
1455
1400
  tomo_recon_combined, combine_data=True)
@@ -1477,59 +1422,55 @@ class Tomo:
1477
1422
  raise ValueError(f'Invalid parameter z_bounds ({z_bounds})')
1478
1423
  if x_bounds is None:
1479
1424
  x_range = (0, tomo_shape[2])
1480
- x_slice = int(x_range[1]/2)
1425
+ x_slice = x_range[1]//2
1481
1426
  else:
1482
1427
  x_range = (min(x_bounds), max(x_bounds))
1483
- x_slice = int((x_bounds[0]+x_bounds[1]) / 2)
1428
+ x_slice = (x_bounds[0]+x_bounds[1])//2
1484
1429
  if y_bounds is None:
1485
1430
  y_range = (0, tomo_shape[1])
1486
- y_slice = int(y_range[1]/2)
1431
+ y_slice = y_range[1]//2
1487
1432
  else:
1488
1433
  y_range = (min(y_bounds), max(y_bounds))
1489
- y_slice = int((y_bounds[0]+y_bounds[1]) / 2)
1434
+ y_slice = (y_bounds[0]+y_bounds[1])//2
1490
1435
  if z_bounds is None:
1491
1436
  z_range = (0, tomo_shape[0])
1492
- z_slice = int(z_range[1]/2)
1437
+ z_slice = z_range[1]//2
1493
1438
  else:
1494
1439
  z_range = (min(z_bounds), max(z_bounds))
1495
- z_slice = int((z_bounds[0]+z_bounds[1]) / 2)
1440
+ z_slice = (z_bounds[0]+z_bounds[1])//2
1496
1441
  z_dim_org = tomo_shape[0]
1497
1442
  tomo_recon_combined = tomo_recon_combined[
1498
1443
  z_range[0]:z_range[1],y_range[0]:y_range[1],x_range[0]:x_range[1]]
1499
1444
 
1500
1445
  # Convert the reconstructed tomography data from internal
1501
- # coordinate frame row/-z,y,x with the origin on the
1502
- # near-left-top corner to an z,y,x coordinate frame.
1503
- # Here x is to the right, y along the beam direction
1504
- # and z upwards in the lab frame of reference
1446
+ # coordinate frame row/-z,y,x with the origin on the
1447
+ # near-left-top corner to an z,y,x coordinate frame.
1448
+ # Here x is to the right, y along the beam direction and
1449
+ # z upwards in the lab frame of reference
1505
1450
  tomo_recon_combined = np.flip(tomo_recon_combined, 0)
1506
1451
  tomo_shape = tomo_recon_combined.shape
1507
1452
  z_range = (z_dim_org-z_range[1], z_dim_org-z_range[0])
1508
1453
 
1509
1454
  # Get coordinate axes
1510
- row_pixel_size = float(
1511
- nxentry.instrument.detector.row_pixel_size)
1512
- column_pixel_size = float(
1513
- nxentry.instrument.detector.column_pixel_size)
1455
+ detector = nxentry.instrument.detector
1456
+ row_pixel_size = float(detector.row_pixel_size)
1457
+ column_pixel_size = float(detector.column_pixel_size)
1514
1458
  x = column_pixel_size * (
1515
1459
  np.linspace(x_range[0], x_range[1], x_range[1]-x_range[0], False)
1516
- - 0.5*nxentry.instrument.detector.columns
1517
- + 0.5)
1460
+ - 0.5*detector.columns + 0.5)
1518
1461
  if nxentry.reconstructed_data.get('x_bounds', None) is not None:
1519
1462
  x += column_pixel_size*nxentry.reconstructed_data.x_bounds[0]
1520
1463
  x = np.asarray(x + nxentry.reduced_data.x_translation[0])
1521
1464
  y = column_pixel_size * (
1522
1465
  np.linspace(y_range[0], y_range[1], y_range[1]-y_range[0], False)
1523
- - 0.5*nxentry.instrument.detector.columns
1524
- + 0.5)
1466
+ - 0.5*detector.columns + 0.5)
1525
1467
  if nxentry.reconstructed_data.get('y_bounds', None) is not None:
1526
1468
  y += column_pixel_size*nxentry.reconstructed_data.y_bounds[0]
1527
1469
  y = np.asarray(y)
1528
1470
  z = row_pixel_size*(
1529
1471
  np.linspace(z_range[0], z_range[1], z_range[1]-z_range[0], False)
1530
1472
  - int(nxentry.reduced_data.img_row_bounds[0])
1531
- + 0.5*(nxentry.instrument.detector.rows)
1532
- -0.5)
1473
+ + 0.5*detector.rows - 0.5)
1533
1474
  z = np.asarray(z + nxentry.reduced_data.z_translation[0])
1534
1475
 
1535
1476
  # Plot a few combined image slices
@@ -1539,39 +1480,37 @@ class Tomo:
1539
1480
  y[-1],
1540
1481
  z[0],
1541
1482
  z[-1])
1542
- x_slice = int(tomo_shape[2]/2)
1483
+ x_slice = tomo_shape[2]//2
1543
1484
  quick_imshow(
1544
1485
  tomo_recon_combined[:,:,x_slice],
1545
1486
  title=f'recon combined x={x[x_slice]:.4f}', origin='lower',
1546
- extent=extent, path=self._output_folder, save_fig=True,
1487
+ extent=extent, path=self._outputdir, save_fig=True,
1547
1488
  save_only=True)
1548
1489
  extent = (
1549
1490
  x[0],
1550
1491
  x[-1],
1551
1492
  z[0],
1552
1493
  z[-1])
1553
- y_slice = int(tomo_shape[1]/2)
1494
+ y_slice = tomo_shape[1]//2
1554
1495
  quick_imshow(
1555
1496
  tomo_recon_combined[:,y_slice,:],
1556
1497
  title=f'recon combined y={y[y_slice]:.4f}', origin='lower',
1557
- extent=extent, path=self._output_folder, save_fig=True,
1498
+ extent=extent, path=self._outputdir, save_fig=True,
1558
1499
  save_only=True)
1559
1500
  extent = (
1560
1501
  x[0],
1561
1502
  x[-1],
1562
1503
  y[0],
1563
1504
  y[-1])
1564
- z_slice = int(tomo_shape[0]/2)
1505
+ z_slice = tomo_shape[0]//2
1565
1506
  quick_imshow(
1566
1507
  tomo_recon_combined[z_slice,:,:],
1567
1508
  title=f'recon combined z={z[z_slice]:.4f}', origin='lower',
1568
- extent=extent, path=self._output_folder, save_fig=True,
1509
+ extent=extent, path=self._outputdir, save_fig=True,
1569
1510
  save_only=True)
1570
1511
 
1571
1512
  # Add image reconstruction to reconstructed data NXprocess
1572
- # combined data order: z,y,x
1573
- nxprocess.data = NXdata()
1574
- nxprocess.attrs['default'] = 'data'
1513
+ # - combined axis data order: z,y,x
1575
1514
  if x_bounds is not None and x_bounds != (0, tomo_shape[2]):
1576
1515
  nxprocess.x_bounds = x_bounds
1577
1516
  nxprocess.x_bounds.units = 'pixels'
@@ -1587,48 +1526,36 @@ class Tomo:
1587
1526
  nxprocess.z_bounds.units = 'pixels'
1588
1527
  nxprocess.z_bounds.attrs['long_name'] = \
1589
1528
  'z range indices in reconstructed data frame of reference'
1590
- nxprocess.data.combined_data = tomo_recon_combined
1591
- nxprocess.data.attrs['signal'] = 'combined_data'
1592
- nxprocess.data.attrs['axes'] = ['z', 'y', 'x']
1593
- nxprocess.data.attrs['x_indices'] = 2
1594
- nxprocess.data.attrs['y_indices'] = 1
1595
- nxprocess.data.attrs['z_indices'] = 0
1596
- nxprocess.data.x = x
1597
- nxprocess.data.x.units = \
1598
- nxentry.instrument.detector.column_pixel_size.units
1599
- nxprocess.data.y = y
1600
- nxprocess.data.y.units = \
1601
- nxentry.instrument.detector.column_pixel_size.units
1602
- nxprocess.data.z = z
1603
- nxprocess.data.z.units = \
1604
- nxentry.instrument.detector.row_pixel_size.units
1529
+ nxprocess.data = NXdata(
1530
+ NXfield(tomo_recon_combined, 'combined_data'),
1531
+ (NXfield(z, 'z', attrs={'units': detector.row_pixel_size.units}),
1532
+ NXfield(
1533
+ y, 'y', attrs={'units': detector.column_pixel_size.units}),
1534
+ NXfield(
1535
+ x, 'x', attrs={'units': detector.column_pixel_size.units}),))
1605
1536
 
1606
1537
  # Create a copy of the input Nexus object and remove
1607
- # reconstructed data
1538
+ # reconstructed data
1608
1539
  exclude_items = [
1609
1540
  f'{nxentry.nxname}/reconstructed_data/data',
1610
1541
  f'{nxentry.nxname}/data/reconstructed_data',
1611
1542
  ]
1612
- nxroot_copy = nxcopy(nxroot, exclude_nxpaths=exclude_items)
1543
+ nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
1613
1544
 
1614
1545
  # Add the combined data NXprocess to the new Nexus object
1615
- nxentry_copy = nxroot_copy[nxroot_copy.attrs['default']]
1616
- nxentry_copy.combined_data = nxprocess
1617
- if 'data' not in nxentry_copy:
1618
- nxentry_copy.data = NXdata()
1619
- nxentry_copy.attrs['default'] = 'data'
1620
- nxentry_copy.data.makelink(
1621
- nxprocess.data.combined_data, name='combined_data')
1622
- nxentry_copy.data.attrs['signal'] = 'combined_data'
1623
- nxentry_copy.data.attrs['axes'] = ['z', 'y', 'x']
1624
- nxentry_copy.data.attrs['x_indices'] = 2
1625
- nxentry_copy.data.attrs['y_indices'] = 1
1626
- nxentry_copy.data.attrs['z_indices'] = 0
1627
- nxentry_copy.data.makelink(nxprocess.data.x, name='x')
1628
- nxentry_copy.data.makelink(nxprocess.data.y, name='y')
1629
- nxentry_copy.data.makelink(nxprocess.data.z, name='z')
1630
-
1631
- return nxroot_copy
1546
+ nxentry = nxroot[nxroot.default]
1547
+ nxentry.combined_data = nxprocess
1548
+ if 'data' not in nxentry:
1549
+ nxentry.data = NXdata()
1550
+ nxentry.data.set_default()
1551
+ nxentry.data.makelink(nxprocess.data.combined_data)
1552
+ nxentry.data.attrs['axes'] = ['z', 'y', 'x']
1553
+ nxentry.data.makelink(nxprocess.data.x)
1554
+ nxentry.data.makelink(nxprocess.data.y)
1555
+ nxentry.data.makelink(nxprocess.data.z)
1556
+ nxentry.data.attrs['signal'] = 'combined_data'
1557
+
1558
+ return nxroot
1632
1559
 
1633
1560
  def _gen_dark(self, nxentry, reduced_data, image_key):
1634
1561
  """Generate dark field."""
@@ -1639,8 +1566,7 @@ class Tomo:
1639
1566
  field_indices = [
1640
1567
  index for index, key in enumerate(image_key) if key == 2]
1641
1568
  if field_indices:
1642
- tdf_stack = np.asarray(
1643
- nxentry.instrument.detector.data[field_indices,:,:])
1569
+ tdf_stack = nxentry.instrument.detector.data[field_indices,:,:]
1644
1570
  else:
1645
1571
  self._logger.warning('Dark field unavailable')
1646
1572
  return reduced_data
@@ -1655,7 +1581,6 @@ class Tomo:
1655
1581
  raise RuntimeError(f'Invalid tdf_stack shape ({tdf_stack.shape})')
1656
1582
 
1657
1583
  # Remove dark field intensities above the cutoff
1658
- # tdf_cutoff = None
1659
1584
  tdf_cutoff = tdf.min() + 2 * (np.median(tdf)-tdf.min())
1660
1585
  self._logger.debug(f'tdf_cutoff = {tdf_cutoff}')
1661
1586
  if tdf_cutoff is not None:
@@ -1676,7 +1601,7 @@ class Tomo:
1676
1601
  if self._save_figs:
1677
1602
  quick_imshow(
1678
1603
  tdf, title='Dark field', name='dark_field',
1679
- path=self._output_folder, save_fig=True, save_only=True)
1604
+ path=self._outputdir, save_fig=True, save_only=True)
1680
1605
 
1681
1606
  # Add dark field to reduced data NXprocess
1682
1607
  reduced_data.data = NXdata()
@@ -1693,8 +1618,7 @@ class Tomo:
1693
1618
  field_indices = [
1694
1619
  index for index, key in enumerate(image_key) if key == 1]
1695
1620
  if field_indices:
1696
- tbf_stack = np.asarray(
1697
- nxentry.instrument.detector.data[field_indices,:,:])
1621
+ tbf_stack = nxentry.instrument.detector.data[field_indices,:,:]
1698
1622
  else:
1699
1623
  raise ValueError('Bright field unavailable')
1700
1624
 
@@ -1727,7 +1651,7 @@ class Tomo:
1727
1651
  if self._save_figs:
1728
1652
  quick_imshow(
1729
1653
  tbf, title='Bright field', name='bright_field',
1730
- path=self._output_folder, save_fig=True, save_only=True)
1654
+ path=self._outputdir, save_fig=True, save_only=True)
1731
1655
 
1732
1656
  # Add bright field to reduced data NXprocess
1733
1657
  if 'data' not in reduced_data:
@@ -1754,31 +1678,29 @@ class Tomo:
1754
1678
  if image_mask is None:
1755
1679
  first_image_index = 0
1756
1680
  else:
1757
- image_mask = np.asarray(image_mask)
1758
1681
  first_image_index = int(np.argmax(image_mask))
1759
1682
  field_indices_all = [
1760
1683
  index for index, key in enumerate(image_key) if key == 0]
1761
1684
  if not field_indices_all:
1762
1685
  raise ValueError('Tomography field(s) unavailable')
1763
- z_translation_all = np.asarray(
1764
- nxentry.sample.z_translation)[field_indices_all]
1686
+ z_translation_all = nxentry.sample.z_translation[field_indices_all]
1765
1687
  z_translation_levels = sorted(list(set(z_translation_all)))
1766
1688
  num_tomo_stacks = len(z_translation_levels)
1767
- center_stack_index = int(num_tomo_stacks/2)
1689
+ center_stack_index = num_tomo_stacks//2
1768
1690
  z_translation = z_translation_levels[center_stack_index]
1769
1691
  try:
1770
1692
  field_indices = [
1771
1693
  field_indices_all[index]
1772
1694
  for index, z in enumerate(z_translation_all)
1773
1695
  if z == z_translation]
1774
- first_image = np.asarray(nxentry.instrument.detector.data[
1775
- field_indices[first_image_index]])
1696
+ first_image = nxentry.instrument.detector.data[
1697
+ field_indices[first_image_index]]
1776
1698
  except:
1777
1699
  raise RuntimeError('Unable to load the tomography images '
1778
1700
  f'for stack {i}')
1779
1701
 
1780
1702
  # Set initial image bounds or rotation calibration rows
1781
- tbf = np.asarray(reduced_data.data.bright_field)
1703
+ tbf = reduced_data.data.bright_field.nxdata
1782
1704
  if (not isinstance(calibrate_center_rows, bool)
1783
1705
  and is_int_pair(calibrate_center_rows)):
1784
1706
  img_row_bounds = calibrate_center_rows
@@ -1829,12 +1751,12 @@ class Tomo:
1829
1751
  self._logger.debug(f'num_row_min = {num_row_min}')
1830
1752
  if have_fit:
1831
1753
  # Center the default range relative to the fitted
1832
- # window
1833
- row_low = int((row_low_fit+row_upp_fit-num_row_min) / 2)
1754
+ # window
1755
+ row_low = int((row_low_fit+row_upp_fit-num_row_min)/2)
1834
1756
  row_upp = row_low+num_row_min
1835
1757
  else:
1836
1758
  # Center the default range
1837
- row_low = int((tbf.shape[0]-num_row_min) / 2)
1759
+ row_low = int((tbf.shape[0]-num_row_min)/2)
1838
1760
  row_upp = row_low+num_row_min
1839
1761
  img_row_bounds = (row_low, row_upp)
1840
1762
  if calibrate_center_rows:
@@ -1865,7 +1787,7 @@ class Tomo:
1865
1787
  if calibrate_center_rows:
1866
1788
  title='Select two detector image row indices to '\
1867
1789
  'calibrate rotation axis (in range '\
1868
- f'[0, {first_image.shape[0]-1}])'
1790
+ f'[0, {first_image.shape[0]}])'
1869
1791
  else:
1870
1792
  title='Select detector image row bounds for data '\
1871
1793
  f'reduction (in range [0, {first_image.shape[0]}])'
@@ -1885,21 +1807,20 @@ class Tomo:
1885
1807
  if self._save_figs:
1886
1808
  if calibrate_center_rows:
1887
1809
  fig.savefig(os_path.join(
1888
- self._output_folder, 'rotation_calibration_rows.png'))
1810
+ self._outputdir, 'rotation_calibration_rows.png'))
1889
1811
  else:
1890
1812
  fig.savefig(os_path.join(
1891
- self._output_folder, 'detector_image_bounds.png'))
1813
+ self._outputdir, 'detector_image_bounds.png'))
1892
1814
  plt.close()
1893
1815
 
1894
1816
  return img_row_bounds
1895
1817
 
1896
1818
  def _gen_thetas(self, nxentry, image_key):
1897
1819
  """Get the rotation angles for the image stacks."""
1898
- # Get the rotation angles
1820
+ # Get the rotation angles (in degrees)
1899
1821
  field_indices_all = [
1900
1822
  index for index, key in enumerate(image_key) if key == 0]
1901
- z_translation_all = np.asarray(
1902
- nxentry.sample.z_translation)[field_indices_all]
1823
+ z_translation_all = nxentry.sample.z_translation[field_indices_all]
1903
1824
  z_translation_levels = sorted(list(set(z_translation_all)))
1904
1825
  thetas = None
1905
1826
  for i, z_translation in enumerate(z_translation_levels):
@@ -1907,22 +1828,20 @@ class Tomo:
1907
1828
  field_indices_all[index]
1908
1829
  for index, z in enumerate(z_translation_all)
1909
1830
  if z == z_translation]
1910
- sequence_numbers = np.asarray(
1911
- nxentry.instrument.detector.sequence_number)[field_indices]
1831
+ sequence_numbers = \
1832
+ nxentry.instrument.detector.sequence_number[field_indices]
1912
1833
  assert (list(sequence_numbers)
1913
1834
  == list(range((len(sequence_numbers)))))
1914
1835
  if thetas is None:
1915
- thetas = np.asarray(
1916
- nxentry.sample.rotation_angle)[
1917
- field_indices][sequence_numbers]
1836
+ thetas = nxentry.sample.rotation_angle[
1837
+ field_indices][sequence_numbers]
1918
1838
  else:
1919
1839
  assert all(
1920
- thetas[i] == np.asarray(
1921
- nxentry.sample.rotation_angle)[
1922
- field_indices[index]]
1840
+ thetas[i] == nxentry.sample.rotation_angle[
1841
+ field_indices[index]]
1923
1842
  for i, index in enumerate(sequence_numbers))
1924
1843
 
1925
- return thetas
1844
+ return np.asarray(thetas)
1926
1845
 
1927
1846
  def _set_zoom_or_delta_theta(self, thetas, delta_theta=None):
1928
1847
  """
@@ -1949,7 +1868,7 @@ class Tomo:
1949
1868
  if self._interactive:
1950
1869
  if delta_theta is None:
1951
1870
  delta_theta = thetas[1]-thetas[0]
1952
- print(f'Available \u03b8 range: [{thetas[0]}, {thetas[-1]}]')
1871
+ print(f'\nAvailable \u03b8 range: [{thetas[0]}, {thetas[-1]}]')
1953
1872
  print(f'Current \u03b8 interval: {delta_theta}')
1954
1873
  if input_yesno(
1955
1874
  'Do you want to change the \u03b8 interval to reduce the '
@@ -1973,13 +1892,13 @@ class Tomo:
1973
1892
 
1974
1893
  # Get dark field
1975
1894
  if 'dark_field' in reduced_data.data:
1976
- tdf = np.asarray(reduced_data.data.dark_field)
1895
+ tdf = reduced_data.data.dark_field.nxdata
1977
1896
  else:
1978
1897
  self._logger.warning('Dark field unavailable')
1979
1898
  tdf = None
1980
1899
 
1981
1900
  # Get bright field
1982
- tbf = np.asarray(reduced_data.data.bright_field)
1901
+ tbf = reduced_data.data.bright_field.nxdata
1983
1902
  tbf_shape = tbf.shape
1984
1903
 
1985
1904
  # Subtract dark field
@@ -2018,26 +1937,26 @@ class Tomo:
2018
1937
  img_column_bounds[0]:img_column_bounds[1]]
2019
1938
 
2020
1939
  # Get thetas (in degrees)
2021
- thetas = np.asarray(reduced_data.rotation_angle)
1940
+ thetas = reduced_data.rotation_angle.nxdata
2022
1941
 
2023
1942
  # Get or create image mask
2024
1943
  image_mask = reduced_data.get('image_mask')
2025
1944
  if image_mask is None:
2026
- image_mask = np.ones(len(thetas), dtype=bool)
1945
+ image_mask = [True]*len(thetas)
2027
1946
  else:
2028
- image_mask = np.asarray(image_mask)
1947
+ image_mask = list(image_mask)
2029
1948
 
2030
1949
  # Get the tomography images
2031
1950
  field_indices_all = [
2032
1951
  index for index, key in enumerate(image_key) if key == 0]
2033
1952
  if not field_indices_all:
2034
1953
  raise ValueError('Tomography field(s) unavailable')
2035
- z_translation_all = np.asarray(
2036
- nxentry.sample.z_translation)[field_indices_all]
1954
+ z_translation_all = nxentry.sample.z_translation[
1955
+ field_indices_all]
2037
1956
  z_translation_levels = sorted(list(set(z_translation_all)))
2038
1957
  num_tomo_stacks = len(z_translation_levels)
2039
1958
  if calibrate_center_rows:
2040
- center_stack_index = int(num_tomo_stacks/2)
1959
+ center_stack_index = num_tomo_stacks//2
2041
1960
  tomo_stacks = num_tomo_stacks*[np.array([])]
2042
1961
  horizontal_shifts = []
2043
1962
  vertical_shifts = []
@@ -2046,25 +1965,25 @@ class Tomo:
2046
1965
  continue
2047
1966
  try:
2048
1967
  field_indices = [
2049
- field_indices_all[index]
2050
- for index, z in enumerate(z_translation_all)
1968
+ field_indices_all[i]
1969
+ for i, z in enumerate(z_translation_all)
2051
1970
  if z == z_translation]
2052
- field_indices_masked = np.asarray(field_indices)[image_mask]
2053
- horizontal_shift = list(set(np.asarray(
2054
- nxentry.sample.x_translation)[field_indices_masked]))
1971
+ field_indices_masked = [
1972
+ v for i, v in enumerate(field_indices) if image_mask[i]]
1973
+ horizontal_shift = list(
1974
+ set(nxentry.sample.x_translation[field_indices_masked]))
2055
1975
  assert len(horizontal_shift) == 1
2056
1976
  horizontal_shifts += horizontal_shift
2057
- vertical_shift = list(set(np.asarray(
2058
- nxentry.sample.z_translation)[field_indices_masked]))
1977
+ vertical_shift = list(
1978
+ set(nxentry.sample.z_translation[field_indices_masked]))
2059
1979
  assert len(vertical_shift) == 1
2060
1980
  vertical_shifts += vertical_shift
2061
- sequence_numbers = np.asarray(
2062
- nxentry.instrument.detector.sequence_number)[
2063
- field_indices]
1981
+ sequence_numbers = \
1982
+ nxentry.instrument.detector.sequence_number[field_indices]
2064
1983
  assert (list(sequence_numbers)
2065
1984
  == list(range((len(sequence_numbers)))))
2066
- tomo_stack = np.asarray(
2067
- nxentry.instrument.detector.data)[field_indices_masked]
1985
+ tomo_stack = nxentry.instrument.detector.data[
1986
+ field_indices_masked]
2068
1987
  except:
2069
1988
  raise RuntimeError('Unable to load the tomography images '
2070
1989
  f'for stack {i}')
@@ -2147,7 +2066,7 @@ class Tomo:
2147
2066
  name = f'reduced_data_stack_{i}_theta_{theta}'
2148
2067
  quick_imshow(
2149
2068
  tomo_stack[0,:,:], title=title, name=name,
2150
- path=self._output_folder, save_fig=self._save_figs,
2069
+ path=self._outputdir, save_fig=self._save_figs,
2151
2070
  save_only=self._save_only, block=self._block)
2152
2071
  zoom_perc = 100
2153
2072
  if zoom_perc != 100:
@@ -2163,7 +2082,7 @@ class Tomo:
2163
2082
  f'{round(thetas[0], 2)+0}'
2164
2083
  quick_imshow(
2165
2084
  tomo_stack[0,:,:], title=title,
2166
- path=self._output_folder, save_fig=self._save_figs,
2085
+ path=self._outputdir, save_fig=self._save_figs,
2167
2086
  save_only=self._save_only, block=self._block)
2168
2087
  del tomo_zoom_list
2169
2088
 
@@ -2179,11 +2098,11 @@ class Tomo:
2179
2098
  reduced_tomo_stacks[i] = np.zeros(tomo_stack_shape)
2180
2099
 
2181
2100
  # Add tomo field info to reduced data NXprocess
2182
- reduced_data.x_translation = np.asarray(horizontal_shifts)
2101
+ reduced_data.x_translation = horizontal_shifts
2183
2102
  reduced_data.x_translation.units = 'mm'
2184
- reduced_data.z_translation = np.asarray(vertical_shifts)
2103
+ reduced_data.z_translation = vertical_shifts
2185
2104
  reduced_data.z_translation.units = 'mm'
2186
- reduced_data.data.tomo_fields = np.asarray(reduced_tomo_stacks)
2105
+ reduced_data.data.tomo_fields = reduced_tomo_stacks
2187
2106
  reduced_data.data.attrs['signal'] = 'tomo_fields'
2188
2107
 
2189
2108
  if tdf is not None:
@@ -2193,22 +2112,35 @@ class Tomo:
2193
2112
  return reduced_data
2194
2113
 
2195
2114
  def _find_center_one_plane(
2196
- self, sinogram, row, thetas, eff_pixel_size, cross_sectional_dim,
2197
- path=None, num_core=1, center_offset_min=-50, center_offset_max=50,
2198
- gaussian_sigma=None, ring_width=None, prev_center_offset=None):
2199
- """Find center for a single tomography plane."""
2115
+ self, tomo_stacks, stack_index, row, offset_row, thetas,
2116
+ eff_pixel_size, cross_sectional_dim, path=None, num_core=1,
2117
+ center_offset_min=-50, center_offset_max=50,
2118
+ center_search_range=None, gaussian_sigma=None, ring_width=None,
2119
+ prev_center_offset=None):
2120
+ """
2121
+ Find center for a single tomography plane.
2122
+
2123
+ tomo_stacks data axes order: stack,theta,row,column
2124
+ thetas in radians
2125
+ """
2200
2126
  # Third party modules
2201
2127
  import matplotlib.pyplot as plt
2202
- from tomopy import find_center_vo
2128
+ from tomopy import (
2129
+ # find_center,
2130
+ find_center_vo,
2131
+ find_center_pc,
2132
+ )
2203
2133
 
2204
- # sinogram index order: theta,column
2205
- sinogram = np.asarray(sinogram)
2206
2134
  if not gaussian_sigma:
2207
2135
  gaussian_sigma = None
2208
2136
  if not ring_width:
2209
2137
  ring_width = None
2210
2138
 
2211
- # Try Nghia Vo's method to get the default center offset
2139
+ # Get the sinogram for the selected plane
2140
+ sinogram = tomo_stacks[stack_index,:,offset_row,:]
2141
+ center_offset_range = sinogram.shape[1]/2
2142
+
2143
+ # Try Nghia Vo's method to find the center
2212
2144
  t0 = time()
2213
2145
  if center_offset_min is None:
2214
2146
  center_offset_min = -50
@@ -2228,298 +2160,419 @@ class Tomo:
2228
2160
  self._logger.info(
2229
2161
  f'Finding center using Nghia Vo\'s method took {time()-t0:.2f} '
2230
2162
  'seconds')
2231
- center_offset_range = sinogram.shape[1]/2
2232
2163
  center_offset_vo = float(tomo_center-center_offset_range)
2233
2164
  self._logger.info(
2234
2165
  f'Center at row {row} using Nghia Vo\'s method = '
2235
2166
  f'{center_offset_vo:.2f}')
2236
2167
 
2237
- center_offset_default = None
2168
+ selected_center_offset = center_offset_vo
2238
2169
  if self._interactive or self._save_figs:
2239
2170
 
2240
- # Need column,theta for iradon, so take transpose
2241
- sinogram_t = sinogram.T
2242
-
2243
- # Reconstruct the plane for the Nghia Vo's center offset
2171
+ # Try Guizar-Sicairos's phase correlation method to find
2172
+ # the center
2244
2173
  t0 = time()
2245
- recon_plane = self._reconstruct_one_plane(
2246
- sinogram_t, center_offset_vo, thetas, eff_pixel_size,
2247
- cross_sectional_dim, False, num_core, gaussian_sigma,
2248
- ring_width)
2174
+ tomo_center = find_center_pc(
2175
+ tomo_stacks[stack_index,0,:,:],
2176
+ tomo_stacks[stack_index,-1,:,:])
2177
+ self._logger.info(
2178
+ 'Finding center using Guizar-Sicairos\'s phase correlation '
2179
+ f'method took {time()-t0:.2f} seconds')
2180
+ center_offset_pc = float(tomo_center-center_offset_range)
2181
+ self._logger.info(
2182
+ f'Center at row {row} using Guizar-Sicairos\'s image entropy '
2183
+ f'method = {center_offset_pc:.2f}')
2184
+
2185
+ # Try Donath's image entropy method to find the center
2186
+ # Skip this method, it seems flawed somehow or I'm doing something wrong
2187
+ # t0 = time()
2188
+ # tomo_center = find_center(
2189
+ # tomo_stacks[stack_index,:,:,:], thetas,
2190
+ # ind=offset_row)
2191
+ # self._logger.info(
2192
+ # 'Finding center using Donath\'s image entropy method took '
2193
+ # f'{time()-t0:.2f} seconds')
2194
+ # center_offset_ie = float(tomo_center-center_offset_range)
2195
+ # self._logger.info(
2196
+ # f'Center at row {row} using Donath\'s image entropy method = '
2197
+ # f'{center_offset_ie:.2f}')
2198
+
2199
+ # Reconstruct the plane for the Nghia Vo's center
2200
+ t0 = time()
2201
+ center_offsets = [center_offset_vo]
2202
+ fig_titles = [f'Vo\'s method: center offset = '
2203
+ f'{center_offset_vo:.2f}']
2204
+ recon_planes = [self._reconstruct_planes(
2205
+ sinogram, center_offset_vo, thetas, num_core=num_core,
2206
+ gaussian_sigma=gaussian_sigma, ring_width=ring_width)]
2249
2207
  self._logger.info(
2250
2208
  f'Reconstructing row {row} with center at '
2251
2209
  f'{center_offset_vo} took {time()-t0:.2f} seconds')
2252
2210
 
2253
- recon_edges = [self._get_edges_one_plane(recon_plane)]
2254
- if (not self._interactive or prev_center_offset is None
2255
- or prev_center_offset == center_offset_vo):
2256
- fig, accept, center_offset_default = \
2257
- self._select_center_offset(
2258
- recon_edges, row, center_offset_vo, vo=True)
2259
- # Plot results
2260
- if self._save_figs:
2261
- fig.savefig(
2262
- os_path.join(
2263
- self._output_folder,
2264
- f'edges_center_row_{row}_vo.png'))
2265
- plt.close()
2266
-
2267
- if self._interactive:
2268
-
2269
- if center_offset_default is None:
2270
- # Reconstruct the plane at the previous center offset
2211
+ # Reconstruct the plane for the Guizar-Sicairos's center
2212
+ t0 = time()
2213
+ center_offsets.append(center_offset_pc)
2214
+ fig_titles.append(f'Guizar-Sicairos\'s method: center offset = '
2215
+ f'{center_offset_pc:.2f}')
2216
+ recon_planes.append(self._reconstruct_planes(
2217
+ sinogram, center_offset_pc, thetas, num_core=num_core,
2218
+ gaussian_sigma=gaussian_sigma, ring_width=ring_width))
2219
+ self._logger.info(
2220
+ f'Reconstructing row {row} with center at '
2221
+ f'{center_offset_pc} took {time()-t0:.2f} seconds')
2222
+
2223
+ # Reconstruct the plane for the Donath's center
2224
+ # t0 = time()
2225
+ # center_offsets.append(center_offset_ie)
2226
+ # fig_titles.append(f'Donath\'s method: center offset = '
2227
+ # f'{center_offset_ie:.2f}')
2228
+ # recon_planes.append(self._reconstruct_planes(
2229
+ # sinogram, center_offset_ie, thetas, num_core=num_core,
2230
+ # gaussian_sigma=gaussian_sigma, ring_width=ring_width))
2231
+ # self._logger.info(
2232
+ # f'Reconstructing row {row} with center at '
2233
+ # f'{center_offset_ie} took {time()-t0:.2f} seconds')
2234
+
2235
+ # Reconstruct the plane at the previous row's center
2236
+ if (prev_center_offset is not None
2237
+ and prev_center_offset not in center_offsets):
2271
2238
  t0 = time()
2272
- recon_plane = self._reconstruct_one_plane(
2273
- sinogram_t, prev_center_offset, thetas, eff_pixel_size,
2274
- cross_sectional_dim, False, num_core, gaussian_sigma,
2275
- ring_width)
2239
+ center_offsets.append(prev_center_offset)
2240
+ fig_titles.append(f'Previous row\'s: center offset = '
2241
+ f'{prev_center_offset:.2f}')
2242
+ recon_planes.append(self._reconstruct_planes(
2243
+ sinogram, prev_center_offset, thetas, num_core=num_core,
2244
+ gaussian_sigma=gaussian_sigma, ring_width=ring_width))
2276
2245
  self._logger.info(
2277
2246
  f'Reconstructing row {row} with center at '
2278
2247
  f'{prev_center_offset} took {time()-t0:.2f} seconds')
2279
2248
 
2280
- recon_edges.insert(0, self._get_edges_one_plane(recon_plane))
2281
- fig, accept, center_offset_default = \
2282
- self._select_center_offset(
2283
- recon_edges, row,
2284
- (prev_center_offset, center_offset_vo),
2285
- vo=True, include_all_bad=True)
2286
- # Plot results
2287
- if self._save_figs:
2288
- fig.savefig(
2289
- os_path.join(
2290
- self._output_folder,
2291
- f'edges_center_row_{row}_'
2292
- f'{prev_center_offset}_{center_offset_vo}.png'))
2293
- plt.close()
2249
+ # t0 = time()
2250
+ # recon_edges = []
2251
+ # for recon_plane in recon_planes:
2252
+ # recon_edges.append(self._get_edges_one_plane(recon_plane))
2253
+ # print(f'\nGetting edges for row {row} with centers at '
2254
+ # f'{center_offsets} took {time()-t0:.2f} seconds\n')
2294
2255
 
2295
- if center_offset_default == center_offset_vo:
2296
- recon_edges.pop(0)
2297
- fig, accept, _ = self._select_center_offset(
2298
- recon_edges, row, center_offset_vo, vo=True)
2299
- # Plot results
2300
- if self._save_figs:
2301
- fig.savefig(
2302
- os_path.join(
2303
- self._output_folder,
2304
- f'edges_center_row_{row}_vo.png'))
2305
- plt.close()
2306
- else:
2307
- recon_edges.pop()
2308
- if center_offset_default == prev_center_offset:
2309
- fig, accept, _ = self._select_center_offset(
2310
- recon_edges, row, prev_center_offset, vo=False)
2311
- # Plot results
2312
- if self._save_figs:
2313
- fig.savefig(
2314
- os_path.join(
2315
- self._output_folder,
2316
- f'edges_center_row_{row}_'
2317
- f'{prev_center_offset}.png'))
2318
- plt.close()
2319
- else:
2320
- center_offset_default = prev_center_offset
2256
+ # Select the best center
2257
+ fig, accept, selected_center_offset = \
2258
+ self._select_center_offset(
2259
+ recon_planes, row, center_offsets, default_offset_index=0,
2260
+ fig_titles=fig_titles, search_button=False,
2261
+ include_all_bad=True)
2321
2262
 
2322
- # Perform center finding search
2323
- prev_index = None
2324
- up = True
2325
- include_all_bad = True
2326
- selected_center_offset = center_offset_default
2327
- center_offsets = [center_offset_default]
2328
- step_size = 4
2329
- if not accept:
2330
- max_step_size = min(
2331
- center_offset_range+center_offset_default,
2332
- center_offset_range-center_offset_default-1)
2333
- max_step_size = 1 << int(np.log2(max_step_size))-1
2334
- step_size = input_int(
2335
- '\nEnter the intial step size in the center '
2336
- 'calibration search (will be truncated to the nearest '
2337
- 'lower power of 2)', ge=2, le=max_step_size,
2338
- default=4)
2339
- step_size = 1 << int(np.log2(step_size))
2340
- while not accept and step_size:
2263
+ # Plot results
2264
+ if self._save_figs:
2265
+ fig.savefig(
2266
+ os_path.join(
2267
+ self._outputdir,
2268
+ f'recon_row_{row}_default_centers.png'))
2269
+ plt.close()
2270
+
2271
+ # Create reconstructions for a specified search range
2272
+ if self._interactive:
2273
+ if (center_search_range is None
2274
+ and input_yesno('\nDo you want to reconstruct images '
2275
+ 'for a range of rotation centers', 'n')):
2276
+ center_search_range = input_num_list(
2277
+ 'Enter up to 3 numbers (start, end, step), '
2278
+ '(range, step), or range', remove_duplicates=False,
2279
+ sort=False)
2280
+ if center_search_range is not None:
2281
+ if len(center_search_range) != 3:
2282
+ search_range = center_search_range[0]
2283
+ if len(center_search_range) == 1:
2284
+ step = search_range
2285
+ else:
2286
+ step = center_search_range[1]
2341
2287
  if selected_center_offset == 'all bad':
2342
- selected_center_offset = round(center_offset_default)
2343
- preselected_offsets = (
2344
- selected_center_offset-step_size,
2345
- selected_center_offset+step_size)
2288
+ center_search_range = [
2289
+ - search_range/2, search_range/2, step]
2346
2290
  else:
2347
- selected_center_offset = round(center_offset_default)
2348
- preselected_offsets = (
2349
- selected_center_offset-step_size,
2350
- selected_center_offset,
2351
- selected_center_offset+step_size)
2291
+ center_search_range = [
2292
+ selected_center_offset - search_range/2,
2293
+ selected_center_offset + search_range/2,
2294
+ step]
2295
+ center_search_range[1] += 1 # Make upper bound inclusive
2296
+ search_center_offsets = list(np.arange(*center_search_range))
2297
+ search_recon_planes = self._reconstruct_planes(
2298
+ sinogram, search_center_offsets, thetas, num_core=num_core,
2299
+ gaussian_sigma=gaussian_sigma, ring_width=ring_width)
2300
+ for i, center in enumerate(search_center_offsets):
2301
+ title = f'Reconstruction for row {row}, center offset: ' \
2302
+ f'{center:.2f}'
2303
+ name = f'recon_row_{row}_center_{center:.2f}.png'
2304
+ if self._interactive:
2305
+ save_only = False
2306
+ block = True
2307
+ else:
2308
+ save_only = True
2309
+ block = False
2310
+ quick_imshow(
2311
+ search_recon_planes[i], title=title, row_label='y',
2312
+ column_label='x', path=self._outputdir, name=name,
2313
+ save_only=save_only, save_fig=True, block=block)
2314
+ center_offsets.append(center)
2315
+ recon_planes.append(search_recon_planes[i])
2316
+
2317
+ # Perform an interactive center finding search
2318
+ calibrate_interactively = False
2319
+ if self._interactive:
2320
+ if selected_center_offset == 'all bad':
2321
+ calibrate_interactively = input_yesno(
2322
+ '\nDo you want to perform an interactive search to '
2323
+ 'calibrate the rotation center (y/n)?', 'n')
2324
+ else:
2325
+ calibrate_interactively = input_yesno(
2326
+ '\nDo you want to perform an interactive search to '
2327
+ 'calibrate the rotation center around the selected value '
2328
+ f'of {selected_center_offset} (y/n)?', 'n')
2329
+ if calibrate_interactively:
2330
+ include_all_bad = True
2331
+ low = None
2332
+ upp = None
2333
+ if selected_center_offset == 'all bad':
2334
+ selected_center_offset = None
2335
+ selected_center_offset = input_num(
2336
+ '\nEnter the initial center offset in the center calibration '
2337
+ 'search', ge=-center_offset_range, le=center_offset_range,
2338
+ default=selected_center_offset)
2339
+ max_step_size = min(
2340
+ center_offset_range+selected_center_offset,
2341
+ center_offset_range-selected_center_offset-1)
2342
+ max_step_size = 1 << int(np.log2(max_step_size))-1
2343
+ step_size = input_int(
2344
+ '\nEnter the intial step size in the center calibration '
2345
+ 'search (will be truncated to the nearest lower power of 2)',
2346
+ ge=2, le=max_step_size, default=4)
2347
+ step_size = 1 << int(np.log2(step_size))
2348
+ selected_center_offset_prev = round(selected_center_offset)
2349
+ while step_size:
2350
+ preselected_offsets = (
2351
+ selected_center_offset_prev-step_size,
2352
+ selected_center_offset_prev,
2353
+ selected_center_offset_prev+step_size)
2352
2354
  indices = []
2353
2355
  for i, preselected_offset in enumerate(preselected_offsets):
2354
2356
  if preselected_offset in center_offsets:
2355
2357
  indices.append(
2356
2358
  center_offsets.index(preselected_offset))
2357
2359
  else:
2358
- recon_plane = self._reconstruct_one_plane(
2359
- sinogram_t, preselected_offset, thetas,
2360
- eff_pixel_size, cross_sectional_dim, False,
2361
- num_core, gaussian_sigma, ring_width)
2362
2360
  indices.append(len(center_offsets))
2363
2361
  center_offsets.append(preselected_offset)
2364
- recon_edges.append(
2365
- self._get_edges_one_plane(recon_plane))
2362
+ recon_planes.append(self._reconstruct_planes(
2363
+ sinogram, preselected_offset, thetas,
2364
+ num_core=num_core, gaussian_sigma=gaussian_sigma,
2365
+ ring_width=ring_width))
2366
2366
  fig, accept, selected_center_offset = \
2367
2367
  self._select_center_offset(
2368
- [recon_edges[i] for i in indices],
2369
- row, preselected_offsets,
2368
+ [recon_planes[i] for i in indices],
2369
+ row, preselected_offsets, default_offset_index=1,
2370
2370
  include_all_bad=include_all_bad)
2371
- if selected_center_offset == 'all bad':
2372
- step_size *=2
2373
- else:
2374
- include_all_bad = False
2375
- index = preselected_offsets.index(selected_center_offset)
2376
- if index != 1 and prev_index in (None, index) and up:
2377
- step_size *=2
2378
- else:
2379
- step_size = int(step_size/2)
2380
- up = False
2381
- prev_index = index
2382
- if step_size > max_step_size:
2383
- self._logger.warning('Exceeding maximum step size'
2384
- f'of {max_step_size}')
2385
- step_size = max_step_size
2386
2371
  # Plot results
2387
2372
  if self._save_figs:
2388
2373
  fig.savefig(
2389
2374
  os_path.join(
2390
- self._output_folder,
2391
- f'edges_center_{row}_{min(preselected_offsets)}_'\
2375
+ self._outputdir,
2376
+ f'recon_row_{row}_center_range_'
2377
+ f'{min(preselected_offsets)}_'\
2392
2378
  f'{max(preselected_offsets)}.png'))
2393
2379
  plt.close()
2394
- del sinogram_t
2395
- del recon_plane
2380
+ if accept and input_yesno(
2381
+ f'Accept center offset {selected_center_offset} '
2382
+ f'for row {row}? (y/n)', 'y'):
2383
+ break
2384
+ if selected_center_offset == 'all bad':
2385
+ step_size *=2
2386
+ else:
2387
+ if selected_center_offset == preselected_offsets[0]:
2388
+ upp = preselected_offsets[1]
2389
+ elif selected_center_offset == preselected_offsets[1]:
2390
+ low = preselected_offsets[0]
2391
+ upp = preselected_offsets[2]
2392
+ else:
2393
+ low = preselected_offsets[1]
2394
+ if None in (low, upp):
2395
+ step_size *= 2
2396
+ else:
2397
+ step_size = step_size//2
2398
+ include_all_bad = False
2399
+ selected_center_offset_prev = round(selected_center_offset)
2400
+ if step_size > max_step_size:
2401
+ self._logger.warning(
2402
+ 'Exceeding maximum step size of {max_step_size}')
2403
+ step_size = max_step_size
2396
2404
 
2397
- # Select center location
2398
- if self._interactive:
2399
- center_offset = selected_center_offset
2400
- else:
2401
- center_offset = center_offset_vo
2405
+ # Collect info for the currently selected center
2406
+ recon_planes = [recon_planes[
2407
+ center_offsets.index(selected_center_offset)]]
2408
+ center_offsets = [selected_center_offset]
2409
+ fig_titles = [f'Reconstruction for center offset = '
2410
+ f'{selected_center_offset:.2f}']
2411
+
2412
+ # Try Nghia Vo's method with the selected center
2413
+ step_size = min(step_size, 10)
2414
+ center_offset_min = selected_center_offset-step_size
2415
+ center_offset_max = selected_center_offset+step_size
2416
+ if num_core > NUM_CORE_TOMOPY_LIMIT:
2417
+ self._logger.debug(
2418
+ f'Running find_center_vo on {NUM_CORE_TOMOPY_LIMIT} '
2419
+ 'cores ...')
2420
+ tomo_center = find_center_vo(
2421
+ sinogram, ncore=NUM_CORE_TOMOPY_LIMIT,
2422
+ smin=center_offset_min, smax=center_offset_max)
2423
+ else:
2424
+ tomo_center = find_center_vo(
2425
+ sinogram, ncore=num_core, smin=center_offset_min,
2426
+ smax=center_offset_max)
2427
+ center_offset_vo = float(tomo_center-center_offset_range)
2428
+ self._logger.info(
2429
+ f'Center at row {row} using Nghia Vo\'s method = '
2430
+ f'{center_offset_vo:.2f}')
2431
+
2432
+ # Reconstruct the plane for the Nghia Vo's center
2433
+ center_offsets.append(center_offset_vo)
2434
+ fig_titles.append(f'Vo\'s method: center offset = '
2435
+ f'{center_offset_vo:.2f}')
2436
+ recon_planes.append(self._reconstruct_planes(
2437
+ sinogram, center_offset_vo, thetas, num_core=num_core,
2438
+ gaussian_sigma=gaussian_sigma, ring_width=ring_width))
2439
+
2440
+ # Select the best center
2441
+ fig, accept, selected_center_offset = \
2442
+ self._select_center_offset(
2443
+ recon_planes, row, center_offsets, default_offset_index=0,
2444
+ fig_titles=fig_titles, search_button=False)
2402
2445
 
2403
- return float(center_offset)
2446
+ # Plot results
2447
+ if self._save_figs:
2448
+ fig.savefig(
2449
+ os_path.join(
2450
+ self._outputdir,
2451
+ f'recon_row_{row}_center_'
2452
+ f'{selected_center_offset:.2f}.png'))
2453
+ plt.close()
2454
+
2455
+ del sinogram
2456
+ del recon_planes
2404
2457
 
2405
- def _reconstruct_one_plane(
2406
- self, tomo_plane_t, center_offset, thetas, eff_pixel_size,
2407
- cross_sectional_dim, plot_sinogram=False, num_core=1,
2458
+ # Return the center location
2459
+ if self._interactive:
2460
+ if selected_center_offset == 'all bad':
2461
+ print('\nUnable to successfully calibrate center axis')
2462
+ selected_center_offset = input_num(
2463
+ 'Enter the center offset for row {row}',
2464
+ ge=-center_offset_range, le=center_offset_range)
2465
+ return float(selected_center_offset)
2466
+ return float(center_offset_vo)
2467
+
2468
+ def _reconstruct_planes(
2469
+ self, tomo_planes, center_offset, thetas, num_core=1,
2408
2470
  gaussian_sigma=None, ring_width=None):
2409
- """Invert the sinogram for a single tomography plane."""
2471
+ """Invert the sinogram for a single or multiple tomography
2472
+ planes using tomopy's recon routine."""
2410
2473
  # Third party modules
2411
2474
  from scipy.ndimage import gaussian_filter
2412
- from skimage.transform import iradon
2413
- from tomopy import misc
2414
-
2415
- # tomo_plane_t index order: column,theta
2416
- two_offset = 2 * int(np.round(center_offset))
2417
- two_offset_abs = np.abs(two_offset)
2418
- # Add 10% slack to max_rad to avoid edge effects
2419
- max_rad = int(0.55 * (cross_sectional_dim/eff_pixel_size))
2420
- if max_rad > 0.5*tomo_plane_t.shape[0]:
2421
- max_rad = 0.5*tomo_plane_t.shape[0]
2422
- dist_from_edge = max(1, int(np.floor(
2423
- (tomo_plane_t.shape[0] - two_offset_abs) / 2.) - max_rad))
2424
- if two_offset >= 0:
2425
- self._logger.debug(
2426
- f'sinogram range = [{two_offset+dist_from_edge}, '
2427
- f'{-dist_from_edge}]')
2428
- sinogram = tomo_plane_t[
2429
- two_offset+dist_from_edge:-dist_from_edge,:]
2430
- else:
2431
- self._logger.debug(
2432
- f'sinogram range = [{dist_from_edge}, '
2433
- f'{two_offset-dist_from_edge}]')
2434
- sinogram = tomo_plane_t[dist_from_edge:two_offset-dist_from_edge,:]
2435
- if plot_sinogram:
2436
- quick_imshow(
2437
- sinogram.T,
2438
- title=f'Sinogram for a center offset of {center_offset:.2f}',
2439
- name=f'sinogram_center_offset{center_offset:.2f}',
2440
- path=self._output_folder, save_fig=self._save_figs,
2441
- save_only=self._save_only, block=self._block, aspect='auto')
2475
+ from tomopy import (
2476
+ misc,
2477
+ recon,
2478
+ )
2442
2479
 
2443
- # Inverting sinogram
2444
- t0 = time()
2445
- recon_sinogram = iradon(sinogram, theta=thetas, circle=True)
2446
- self._logger.info(f'Inverting sinogram took {time()-t0:.2f} seconds')
2447
- del sinogram
2480
+ # Reconstruct the planes
2481
+ # tomo_planes axis data order: (row,)theta,column
2482
+ # thetas in radians
2483
+ if isinstance(center_offset, (int, float)):
2484
+ tomo_planes = np.expand_dims(tomo_planes, 0)
2485
+ center_offset = center_offset + tomo_planes.shape[2]/2
2486
+ elif is_num_series(center_offset):
2487
+ tomo_planes = np.array([tomo_planes]*len(center_offset))
2488
+ center_offset = np.asarray(center_offset) + tomo_planes.shape[2]/2
2489
+ else:
2490
+ raise ValueError(
2491
+ f'Invalid parameter center_offset ({center_offset})')
2492
+ recon_planes = recon(
2493
+ tomo_planes, thetas, center=center_offset, sinogram_order=True,
2494
+ algorithm='gridrec', ncore=num_core)
2448
2495
 
2449
2496
  # Performing Gaussian filtering and removing ring artifacts
2450
2497
  if gaussian_sigma is not None and gaussian_sigma:
2451
- recon_sinogram = gaussian_filter(
2452
- recon_sinogram, gaussian_sigma, mode='nearest')
2453
- recon_clean = np.expand_dims(recon_sinogram, axis=0)
2454
- del recon_sinogram
2498
+ recon_planes = gaussian_filter(
2499
+ recon_planes, gaussian_sigma, mode='nearest')
2455
2500
  if ring_width is not None and ring_width:
2456
- recon_clean = misc.corr.remove_ring(
2457
- recon_clean, rwidth=ring_width, ncore=num_core)
2458
-
2459
- return recon_clean
2460
-
2461
- def _get_edges_one_plane(self, recon_plane):
2462
- """
2463
- Create an "edges plot" image for a single reconstructed
2464
- tomography data plane.
2465
- """
2466
- # Third party modules
2467
- from skimage.restoration import denoise_tv_chambolle
2468
-
2469
- vis_parameters = None # self._config.get('vis_parameters')
2470
- if vis_parameters is None:
2471
- weight = 0.1
2472
- else:
2473
- weight = vis_parameters.get('denoise_weight', 0.1)
2474
- if not is_num(weight, ge=0.):
2475
- self._logger.warning(
2476
- f'Invalid weight ({weight}) in _get_edges_one_plane, '
2477
- 'set to a default of 0.1')
2478
- weight = 0.1
2479
- return denoise_tv_chambolle(recon_plane, weight=weight)[0]
2501
+ recon_planes = misc.corr.remove_ring(
2502
+ recon_planes, rwidth=ring_width, ncore=num_core)
2503
+
2504
+ # Apply a circular mask
2505
+ recon_planes = misc.corr.circ_mask(recon_planes, axis=0)
2506
+
2507
+ return np.squeeze(recon_planes)
2508
+
2509
+ # def _get_edges_one_plane(self, recon_plane):
2510
+ # """
2511
+ # Create an "edges plot" image for a single reconstructed
2512
+ # tomography data plane.
2513
+ # """
2514
+ # # Third party modules
2515
+ # from skimage.restoration import denoise_tv_chambolle
2516
+ #
2517
+ # vis_parameters = None # RV self._config.get('vis_parameters')
2518
+ # if vis_parameters is None:
2519
+ # weight = 0.1
2520
+ # else:
2521
+ # weight = vis_parameters.get('denoise_weight', 0.1)
2522
+ # if not is_num(weight, ge=0.):
2523
+ # self._logger.warning(
2524
+ # f'Invalid weight ({weight}) in _get_edges_one_plane, '
2525
+ # 'set to a default of 0.1')
2526
+ # weight = 0.1
2527
+ # return denoise_tv_chambolle(recon_plane, weight=weight)
2480
2528
 
2481
2529
  def _select_center_offset(
2482
- self, recon_edges, row, preselected_offsets, vo=False,
2530
+ self, recon_planes, row, preselected_offsets,
2531
+ default_offset_index=0, fig_titles=None, search_button=True,
2483
2532
  include_all_bad=False):
2484
- """Select a center offset value from an "edges plot" image
2533
+ """Select a center offset value from reconstructed images
2485
2534
  for a single reconstructed tomography data plane."""
2486
2535
  # Third party modules
2487
2536
  import matplotlib.pyplot as plt
2488
2537
  from matplotlib.widgets import RadioButtons, Button
2489
- from matplotlib.widgets import Button
2490
2538
 
2491
2539
  def select_offset(offset):
2492
2540
  """Callback function for the "Select offset" input."""
2493
- if offset in ('both bad', 'all bad'):
2494
- selected_offset.append(((False, 'all bad')))
2495
- else:
2496
- selected_offset.append(
2497
- ((False,
2498
- preselected_offsets[preselected_offsets.index(
2499
- float(radio_btn.value_selected))])))
2500
- plt.close()
2541
+ pass
2501
2542
 
2502
- def reject(event):
2503
- """Callback function for the "Reject" button."""
2504
- selected_offset.append((False, preselected_offsets[0]))
2543
+ def search(event):
2544
+ """Callback function for the "Search" button."""
2545
+ if num_plots == 1:
2546
+ selected_offset.append(
2547
+ (False, preselected_offsets[default_offset_index]))
2548
+ else:
2549
+ offset = radio_btn.value_selected
2550
+ if offset in ('both bad', 'all bad'):
2551
+ selected_offset.append((False, 'all bad'))
2552
+ else:
2553
+ selected_offset.append((False, float(offset)))
2505
2554
  plt.close()
2506
2555
 
2507
2556
  def accept(event):
2508
2557
  """Callback function for the "Accept" button."""
2509
2558
  if num_plots == 1:
2510
- selected_offset.append((True, preselected_offsets[0]))
2511
- else:
2512
2559
  selected_offset.append(
2513
- ((False,
2514
- preselected_offsets[preselected_offsets.index(
2515
- float(radio_btn.value_selected))])))
2560
+ (True, preselected_offsets[default_offset_index]))
2561
+ else:
2562
+ offset = radio_btn.value_selected
2563
+ if offset in ('both bad', 'all bad'):
2564
+ selected_offset.append((False, 'all bad'))
2565
+ else:
2566
+ selected_offset.append((True, float(offset)))
2516
2567
  plt.close()
2517
2568
 
2518
- if not isinstance(recon_edges, (tuple, list)):
2519
- recon_edges = [recon_edges]
2569
+ if not isinstance(recon_planes, (tuple, list)):
2570
+ recon_planes = [recon_planes]
2520
2571
  if not isinstance(preselected_offsets, (tuple, list)):
2521
2572
  preselected_offsets = [preselected_offsets]
2522
- assert len(recon_edges) == len(preselected_offsets)
2573
+ assert len(recon_planes) == len(preselected_offsets)
2574
+ if fig_titles is not None:
2575
+ assert len(fig_titles) == len(preselected_offsets)
2523
2576
 
2524
2577
  selected_offset = []
2525
2578
 
@@ -2531,68 +2584,58 @@ class Tomo:
2531
2584
  'horizontalalignment': 'center',
2532
2585
  'verticalalignment': 'bottom'}
2533
2586
 
2534
- num_plots = len(recon_edges)
2587
+ num_plots = len(recon_planes)
2535
2588
  if num_plots == 1:
2536
2589
  fig, axs = plt.subplots(figsize=(11, 8.5))
2537
2590
  axs = [axs]
2538
- vmax = np.max(recon_edges[0][:,:])
2591
+ vmax = np.max(recon_planes[0][:,:])
2539
2592
  else:
2540
2593
  fig, axs = plt.subplots(ncols=num_plots, figsize=(17, 8.5))
2541
2594
  axs = list(axs)
2542
- vmax = np.max(recon_edges[1][:,:])
2543
- for i, (ax, recon_edge, preselected_offset) in enumerate(zip(
2544
- axs, recon_edges, preselected_offsets)):
2545
- ax.imshow(recon_edge, vmin=-vmax, vmax=vmax, cmap='gray')
2546
- if num_plots == 1:
2547
- ax.set_title(
2548
- f'Reconstruction for row {row}, center offset: ' \
2549
- f'{preselected_offset:.2f}', fontsize='x-large')
2550
- else:
2551
- ax.set_title(
2552
- f'Center offset: {preselected_offset}',
2553
- fontsize='x-large')
2595
+ vmax = np.max(recon_planes[1][:,:])
2596
+ for i, (ax, recon_plane, preselected_offset) in enumerate(zip(
2597
+ axs, recon_planes, preselected_offsets)):
2598
+ ax.imshow(recon_plane, vmin=-vmax, vmax=vmax, cmap='gray')
2599
+ if fig_titles is None:
2600
+ if num_plots == 1:
2601
+ ax.set_title(
2602
+ f'Reconstruction for row {row}, center offset: ' \
2603
+ f'{preselected_offset:.2f}', fontsize='x-large')
2604
+ else:
2605
+ ax.set_title(
2606
+ f'Center offset: {preselected_offset}',
2607
+ fontsize='x-large')
2554
2608
  ax.set_xlabel('x', fontsize='x-large')
2555
2609
  if not i:
2556
2610
  ax.set_ylabel('y', fontsize='x-large')
2611
+ if fig_titles is not None:
2612
+ for (ax, fig_title) in zip(axs, fig_titles):
2613
+ ax.set_title(fig_title, fontsize='x-large')
2557
2614
 
2615
+ fig_title = plt.figtext(
2616
+ *title_pos, f'Reconstruction for row {row}', **title_props)
2558
2617
  if num_plots == 1:
2559
- if vo:
2560
- fig_title = plt.figtext(
2561
- *title_pos,
2562
- f'Reconstruction for row {row} (Nghia Vo\'s center '
2563
- f'offset: {preselected_offsets[0]})',
2564
- **title_props)
2565
- else:
2566
- fig_title = plt.figtext(
2567
- *title_pos,
2568
- f'Reconstruction for row {row} (previous center offset: '
2569
- f'{preselected_offsets[0]})',
2570
- **title_props)
2571
2618
  fig_subtitle = plt.figtext(
2572
2619
  *subtitle_pos,
2573
- 'Press "Accept" to accept this value or "Reject" to start a '
2574
- 'center calibration search',
2620
+ 'Press "Accept" to accept this value or "Reject" if not',
2575
2621
  **subtitle_props)
2576
2622
  else:
2577
- if vo:
2578
- fig_title = plt.figtext(
2579
- *title_pos,
2580
- f'Reconstruction for row {row} (previous center offset: '
2581
- f'{preselected_offsets[0]}, Nghia Vo\'s center '
2582
- f'offset: {preselected_offsets[1]})',
2583
- **title_props)
2623
+ if search_button:
2624
+ fig_subtitle = plt.figtext(
2625
+ *subtitle_pos,
2626
+ 'Select the best offset and press "Accept" to accept or '
2627
+ '"Search" to continue the search',
2628
+ **subtitle_props)
2584
2629
  else:
2585
- fig_title = plt.figtext(
2586
- *title_pos, f'Reconstruction for row {row}', **title_props)
2587
- fig_subtitle = plt.figtext(
2588
- *subtitle_pos,
2589
- 'Select the best offset or press "Accept" to accept the '
2590
- f'default value of {preselected_offsets[1]}',
2591
- **subtitle_props)
2630
+ fig_subtitle = plt.figtext(
2631
+ *subtitle_pos,
2632
+ 'Select the best offset and press "Accept" to accept',
2633
+ **subtitle_props)
2592
2634
 
2593
2635
  if not self._interactive:
2594
2636
 
2595
- selected_offset.append((True, preselected_offsets[0]))
2637
+ selected_offset.append(
2638
+ (True, preselected_offsets[default_offset_index]))
2596
2639
 
2597
2640
  else:
2598
2641
 
@@ -2605,7 +2648,6 @@ class Tomo:
2605
2648
  plt.axes([0.15, 0.05, 0.15, 0.075]), 'Reject')
2606
2649
  reject_cid = reject_btn.on_clicked(reject)
2607
2650
 
2608
-
2609
2651
  else:
2610
2652
 
2611
2653
  # Setup RadioButtons
@@ -2621,9 +2663,15 @@ class Tomo:
2621
2663
  labels = preselected_offsets
2622
2664
  radio_btn = RadioButtons(
2623
2665
  plt.axes([0.175, 0.05, 0.1, 0.1]),
2624
- labels = labels, active=1)
2666
+ labels = labels, active=default_offset_index)
2625
2667
  radio_cid = radio_btn.on_clicked(select_offset)
2626
2668
 
2669
+ # Setup "Search" button
2670
+ if search_button:
2671
+ search_btn = Button(
2672
+ plt.axes([0.4125, 0.05, 0.15, 0.075]), 'Search')
2673
+ search_cid = search_btn.on_clicked(search)
2674
+
2627
2675
  # Setup "Accept" button
2628
2676
  accept_btn = Button(
2629
2677
  plt.axes([0.7, 0.05, 0.15, 0.075]), 'Accept')
@@ -2639,6 +2687,9 @@ class Tomo:
2639
2687
  else:
2640
2688
  radio_btn.disconnect(radio_cid)
2641
2689
  radio_btn.ax.remove()
2690
+ if search_button:
2691
+ search_btn.disconnect(search_cid)
2692
+ search_btn.ax.remove()
2642
2693
  accept_btn.disconnect(accept_cid)
2643
2694
  accept_btn.ax.remove()
2644
2695
 
@@ -2646,18 +2697,20 @@ class Tomo:
2646
2697
  fig_title.remove()
2647
2698
  else:
2648
2699
  fig_title.set_in_layout(True)
2649
- select_text.remove()
2700
+ if self._interactive:
2701
+ select_text.remove()
2650
2702
  fig_subtitle.remove()
2651
2703
  fig.tight_layout(rect=(0, 0, 1, 0.95))
2652
- if not selected_offset and num_plots == 1:
2653
- selected_offset.append((True, preselected_offsets[0]))
2704
+ if not selected_offset:# and num_plots == 1:
2705
+ selected_offset.append(
2706
+ (True, preselected_offsets[default_offset_index]))
2654
2707
 
2655
2708
  return fig, *selected_offset[0]
2656
2709
 
2657
2710
  def _reconstruct_one_tomo_stack(
2658
2711
  self, tomo_stack, thetas, center_offsets=None, num_core=1,
2659
- algorithm='gridrec', secondary_iters=0, remove_stripe_sigma=None,
2660
- ring_width=None):
2712
+ algorithm='gridrec', secondary_iters=0, gaussian_sigma=None,
2713
+ remove_stripe_sigma=None, ring_width=None):
2661
2714
  """Reconstruct a single tomography stack."""
2662
2715
  # Third party modules
2663
2716
  from tomopy import (
@@ -2667,16 +2720,10 @@ class Tomo:
2667
2720
  recon,
2668
2721
  )
2669
2722
 
2670
- # tomo_stack order: row,theta,column
2671
- # input thetas must be in degrees
2723
+ # tomo_stack axis data order: row,theta,column
2724
+ # thetas in radians
2672
2725
  # centers_offset: tomography axis shift in pixels relative
2673
- # to column center
2674
- # RV should we remove stripes?
2675
- # https://tomopy.readthedocs.io/en/latest/api/tomopy.prep.stripe.html
2676
- # RV should we remove rings?
2677
- # https://tomopy.readthedocs.io/en/latest/api/tomopy.misc.corr.html
2678
- # RV add an option to do (extra) secondary iterations later or
2679
- # to do some sort of convergence test?
2726
+ # to column center
2680
2727
  if center_offsets is None:
2681
2728
  centers = np.zeros((tomo_stack.shape[0]))
2682
2729
  elif len(center_offsets) == 2:
@@ -2690,24 +2737,9 @@ class Tomo:
2690
2737
  centers = center_offsets
2691
2738
  centers += tomo_stack.shape[2]/2
2692
2739
 
2693
- # tomo_recon_stack = []
2694
- # eff_pixel_size = 0.05
2695
- # cross_sectional_dim = 20.0
2696
- # gaussian_sigma = 0.05
2697
- # ring_width = 1
2698
- # for i in range(tomo_stack.shape[0]):
2699
- # sinogram_t = tomo_stack[i,:,:].T
2700
- # recon_plane = self._reconstruct_one_plane(
2701
- # sinogram_t, centers[i], thetas, eff_pixel_size,
2702
- # cross_sectional_dim, False, num_core, gaussian_sigma,
2703
- # ring_width)
2704
- # tomo_recon_stack.append(recon_plane[0,:,:])
2705
- # tomo_recon_stack = np.asarray(tomo_recon_stack)
2706
- # return tomo_recon_stack
2707
-
2708
2740
  # Remove horizontal stripe
2709
2741
  # RV prep.stripe.remove_stripe_fw seems flawed for hollow brick
2710
- # accross multiple stacks
2742
+ # accross multiple stacks
2711
2743
  if remove_stripe_sigma is not None and remove_stripe_sigma:
2712
2744
  if num_core > NUM_CORE_TOMOPY_LIMIT:
2713
2745
  tomo_stack = prep.stripe.remove_stripe_fw(
@@ -2721,7 +2753,7 @@ class Tomo:
2721
2753
  self._logger.debug('Performing initial image reconstruction')
2722
2754
  t0 = time()
2723
2755
  tomo_recon_stack = recon(
2724
- tomo_stack, np.radians(thetas), centers, sinogram_order=True,
2756
+ tomo_stack, thetas, centers, sinogram_order=True,
2725
2757
  algorithm=algorithm, ncore=num_core)
2726
2758
  self._logger.info(
2727
2759
  f'Performing initial image reconstruction took {time()-t0:.2f} '
@@ -2759,9 +2791,9 @@ class Tomo:
2759
2791
  }
2760
2792
  t0 = time()
2761
2793
  tomo_recon_stack = recon(
2762
- tomo_stack, np.radians(thetas), centers,
2763
- init_recon=tomo_recon_stack, options=options,
2764
- sinogram_order=True, algorithm=astra, ncore=num_core)
2794
+ tomo_stack, thetas, centers, init_recon=tomo_recon_stack,
2795
+ options=options, sinogram_order=True, algorithm=astra,
2796
+ ncore=num_core)
2765
2797
  self._logger.info(
2766
2798
  f'Performing secondary iterations took {time()-t0:.2f} '
2767
2799
  'seconds')
@@ -2772,6 +2804,11 @@ class Tomo:
2772
2804
  tomo_recon_stack, rwidth=ring_width, out=tomo_recon_stack,
2773
2805
  ncore=num_core)
2774
2806
 
2807
+ # Performing Gaussian filtering
2808
+ if gaussian_sigma is not None and gaussian_sigma:
2809
+ tomo_recon_stack = misc.corr.gaussian_filter(
2810
+ tomo_recon_stack, sigma=gaussian_sigma, ncore=num_core)
2811
+
2775
2812
  return tomo_recon_stack
2776
2813
 
2777
2814
  def _resize_reconstructed_data(
@@ -2845,7 +2882,7 @@ class Tomo:
2845
2882
  if self._save_figs:
2846
2883
  fig.savefig(
2847
2884
  os_path.join(
2848
- self._output_folder, 'reconstructed_data_xy_roi.png'))
2885
+ self._outputdir, 'reconstructed_data_xy_roi.png'))
2849
2886
  plt.close()
2850
2887
 
2851
2888
  # Selecting z bounds (in xy-plane)
@@ -2877,7 +2914,7 @@ class Tomo:
2877
2914
  if self._save_figs:
2878
2915
  fig.savefig(
2879
2916
  os_path.join(
2880
- self._output_folder, 'reconstructed_data_z_roi.png'))
2917
+ self._outputdir, 'reconstructed_data_z_roi.png'))
2881
2918
  plt.close()
2882
2919
 
2883
2920
  return x_bounds, y_bounds, z_bounds
@@ -2890,7 +2927,7 @@ class TomoSimFieldProcessor(Processor):
2890
2927
  tomography detector images.
2891
2928
  """
2892
2929
 
2893
- def process(self, data, **kwargs):
2930
+ def process(self, data):
2894
2931
  """
2895
2932
  Process the input configuration and return a
2896
2933
  `nexusformat.nexus.NXroot` object with the simulated
@@ -2944,30 +2981,30 @@ class TomoSimFieldProcessor(Processor):
2944
2981
  f'({detector_size[0]*pixel_size[0]})')
2945
2982
 
2946
2983
  # Get the rotation angles (start at a arbitrarily choose angle
2947
- # and add thetas for a full 360 degrees rotation series)
2984
+ # and add thetas for a full 360 degrees rotation series)
2948
2985
  if station in ('id1a3', 'id3a'):
2949
2986
  theta_start = 0.
2950
2987
  else:
2951
2988
  theta_start = -17
2952
- #RV theta_end = theta_start + 360.
2989
+ # RV theta_end = theta_start + 360.
2953
2990
  theta_end = theta_start + 180.
2954
2991
  thetas = list(
2955
2992
  np.arange(theta_start, theta_end+0.5*theta_step, theta_step))
2956
2993
 
2957
2994
  # Get the number of horizontal stacks bases on the diagonal
2958
- # of the square and for now don't allow more than one
2995
+ # of the square and for now don't allow more than one
2959
2996
  num_tomo_stack = 1 + int((sample_size[1]*np.sqrt(2)-pixel_size[1])
2960
2997
  / (detector_size[1]*pixel_size[1]))
2961
2998
  if num_tomo_stack > 1:
2962
2999
  raise ValueError('Sample is too wide for the detector')
2963
3000
 
2964
3001
  # Create the x-ray path length through a solid square
2965
- # crosssection for a set of rotation angles.
3002
+ # crosssection for a set of rotation angles.
2966
3003
  path_lengths_solid = self._create_pathlength_solid_square(
2967
3004
  sample_size[1], thetas, pixel_size[1], detector_size[1])
2968
3005
 
2969
3006
  # Create the x-ray path length through a hollow square
2970
- # crosssection for a set of rotation angles.
3007
+ # crosssection for a set of rotation angles.
2971
3008
  path_lengths_hollow = None
2972
3009
  if sample_type in ('square_pipe', 'hollow_cube', 'hollow_brick'):
2973
3010
  path_lengths_hollow = path_lengths_solid \
@@ -3088,10 +3125,6 @@ class TomoSimFieldProcessor(Processor):
3088
3125
  nxdetector.z_translation = vertical_shifts
3089
3126
  nxdetector.starting_image_index = starting_image_index
3090
3127
  nxdetector.starting_image_offset = starting_image_offset
3091
- # nxdetector.path_lengths_solid = path_lengths_solid
3092
- # nxdetector.path_lengths_hollow = path_lengths_hollow
3093
- # nxdetector.intensities_solid = intensities_solid
3094
- # nxdetector.intensities_hollow = intensities_hollow
3095
3128
 
3096
3129
  return nxroot
3097
3130
 
@@ -3145,7 +3178,7 @@ class TomoDarkFieldProcessor(Processor):
3145
3178
  tomography data set created by TomoSimProcessor.
3146
3179
  """
3147
3180
 
3148
- def process(self, data, num_image=5, **kwargs):
3181
+ def process(self, data, num_image=5):
3149
3182
  """
3150
3183
  Process the input configuration and return a
3151
3184
  `nexusformat.nexus.NXroot` object with the simulated
@@ -3218,7 +3251,7 @@ class TomoBrightFieldProcessor(Processor):
3218
3251
  tomography data set created by TomoSimProcessor.
3219
3252
  """
3220
3253
 
3221
- def process(self, data, num_image=5, **kwargs):
3254
+ def process(self, data, num_image=5):
3222
3255
  """
3223
3256
  Process the input configuration and return a
3224
3257
  `nexusformat.nexus.NXroot` object with the simulated
@@ -3235,7 +3268,6 @@ class TomoBrightFieldProcessor(Processor):
3235
3268
  """
3236
3269
  # Third party modules
3237
3270
  from nexusformat.nexus import (
3238
- NeXusError,
3239
3271
  NXroot,
3240
3272
  NXentry,
3241
3273
  NXinstrument,
@@ -3272,7 +3304,7 @@ class TomoBrightFieldProcessor(Processor):
3272
3304
  bright_field = np.concatenate((dummy_fields, bright_field))
3273
3305
  num_image += num_dummy_start
3274
3306
  # Add 20% to slit size to make the bright beam slightly taller
3275
- # than the vertical displacements between stacks
3307
+ # than the vertical displacements between stacks
3276
3308
  slit_size = 1.2*source.slit_size
3277
3309
  if slit_size < float(detector.row_pixel_size*detector_size[0]):
3278
3310
  img_row_coords = float(detector.row_pixel_size) \
@@ -3307,7 +3339,7 @@ class TomoSpecProcessor(Processor):
3307
3339
  simulated tomography data set created by TomoSimProcessor.
3308
3340
  """
3309
3341
 
3310
- def process(self, data, scan_numbers=[1], **kwargs):
3342
+ def process(self, data, scan_numbers=[1]):
3311
3343
  """
3312
3344
  Process the input configuration and return a list of strings
3313
3345
  representing a plain text SPEC file.
@@ -3325,17 +3357,14 @@ class TomoSpecProcessor(Processor):
3325
3357
  from json import dumps
3326
3358
  from datetime import datetime
3327
3359
 
3328
- # Third party modules
3329
3360
  from nexusformat.nexus import (
3330
- NeXusError,
3331
- NXcollection,
3332
3361
  NXentry,
3333
3362
  NXroot,
3334
3363
  NXsubentry,
3335
3364
  )
3336
3365
 
3337
3366
  # Get and validate the TomoSimField, TomoDarkField, or
3338
- # TomoBrightField configuration object in data
3367
+ # TomoBrightField configuration object in data
3339
3368
  configs = {}
3340
3369
  nxroot = get_nxroot(data, 'tomo.models.TomoDarkField')
3341
3370
  if nxroot is not None:
@@ -3364,7 +3393,7 @@ class TomoSpecProcessor(Processor):
3364
3393
  raise ValueError('Inconsistent sample_type among scans')
3365
3394
  detector = nxroot.entry.instrument.detector
3366
3395
  if 'z_translation' in detector:
3367
- num_stack = np.asarray(detector.z_translation).size
3396
+ num_stack = detector.z_translation.size
3368
3397
  else:
3369
3398
  num_stack = 1
3370
3399
  data_shape = detector.data.shape
@@ -3398,9 +3427,9 @@ class TomoSpecProcessor(Processor):
3398
3427
  if station in ('id1a3', 'id3a'):
3399
3428
  spec_file.append('#O0 ramsx ramsz')
3400
3429
  else:
3401
- #RV Fix main code to use independent dim info
3430
+ # RV Fix main code to use independent dim info
3402
3431
  spec_file.append('#O0 GI_samx GI_samz GI_samphi')
3403
- spec_file.append('#o0 samx samz samphi') #RV do I need this line?
3432
+ spec_file.append('#o0 samx samz samphi') # RV do I need this line?
3404
3433
  spec_file.append('')
3405
3434
 
3406
3435
  # Create the SPEC file scan info (and image and parfile data for SMB)
@@ -3412,10 +3441,10 @@ class TomoSpecProcessor(Processor):
3412
3441
  for schema, nxroot in configs.items():
3413
3442
  detector = nxroot.entry.instrument.detector
3414
3443
  if 'z_translation' in detector:
3415
- z_translations = list(np.asarray(detector.z_translation))
3444
+ z_translations = list(detector.z_translation.nxdata)
3416
3445
  else:
3417
3446
  z_translations = [0.]
3418
- thetas = np.asarray(detector.thetas)
3447
+ thetas = detector.thetas
3419
3448
  num_theta = thetas.size
3420
3449
  if schema == 'tomo.models.TomoDarkField':
3421
3450
  if station in ('id1a3', 'id3a'):
@@ -3454,9 +3483,11 @@ class TomoSpecProcessor(Processor):
3454
3483
  spec_file.append('#N 1')
3455
3484
  spec_file.append('#L ome')
3456
3485
  if scan_type == 'ts1':
3457
- image_sets.append(np.asarray(detector.data)[n])
3486
+ #image_sets.append(detector.data.nxdata[n])
3487
+ image_sets.append(detector.data[n])
3458
3488
  else:
3459
- image_sets.append(np.asarray(detector.data))
3489
+ #image_sets.append(detector.data.nxdata)
3490
+ image_sets.append(detector.data)
3460
3491
  par_file.append(
3461
3492
  f'{datetime.now().strftime("%Y%m%d")} '
3462
3493
  f'{datetime.now().strftime("%H%M%S")} '
@@ -3546,7 +3577,7 @@ class TomoSpecProcessor(Processor):
3546
3577
 
3547
3578
  nxroot = NXroot()
3548
3579
  nxroot[sample_type] = nxentry
3549
- nxroot.attrs['default'] = sample_type
3580
+ nxroot[sample_type].set_default()
3550
3581
 
3551
3582
  return nxroot
3552
3583