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/__init__.py +1 -1
- CHAP/common/__init__.py +1 -0
- CHAP/common/models/map.py +95 -70
- CHAP/common/processor.py +653 -54
- CHAP/common/reader.py +9 -8
- CHAP/common/writer.py +20 -6
- CHAP/edd/__init__.py +2 -0
- CHAP/edd/models.py +94 -48
- CHAP/edd/processor.py +625 -169
- CHAP/edd/utils.py +186 -6
- CHAP/pipeline.py +35 -3
- CHAP/runner.py +40 -13
- CHAP/tomo/models.py +8 -0
- CHAP/tomo/processor.py +758 -727
- CHAP/utils/fit.py +98 -45
- CHAP/utils/general.py +177 -54
- CHAP/utils/scanparsers.py +326 -56
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/RECORD +23 -23
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/WHEEL +0 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/top_level.txt +0 -0
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
|
|
129
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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[
|
|
193
|
-
|
|
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(
|
|
218
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
419
|
-
nxentry.data
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
#
|
|
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,
|
|
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
|
|
708
|
-
:type
|
|
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
|
-
|
|
720
|
-
|
|
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.
|
|
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
|
-
#
|
|
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.
|
|
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 =
|
|
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
|
-
#
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
945
|
-
#
|
|
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 =
|
|
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=
|
|
923
|
+
default=num_tomo_stacks//2)
|
|
959
924
|
else:
|
|
960
925
|
if center_stack_index is None:
|
|
961
|
-
center_stack_index =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
1098
|
+
thetas = nxentry.reduced_data.rotation_angle.nxdata
|
|
1136
1099
|
|
|
1137
1100
|
# Reconstruct tomography data
|
|
1138
|
-
#
|
|
1139
|
-
#
|
|
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
|
-
#
|
|
1142
|
-
#
|
|
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 =
|
|
1110
|
+
tomo_stacks = nxentry.reduced_data.data.tomo_fields
|
|
1148
1111
|
num_tomo_stacks = tomo_stacks.shape[0]
|
|
1149
|
-
tomo_recon_stacks =
|
|
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
|
-
#
|
|
1156
|
-
|
|
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
|
|
1139
|
+
tomo_recon_stacks.append(tomo_recon_stack)
|
|
1186
1140
|
|
|
1187
1141
|
# Resize the reconstructed tomography data
|
|
1188
|
-
#
|
|
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 =
|
|
1149
|
+
x_slice = x_range[1]//2
|
|
1196
1150
|
else:
|
|
1197
1151
|
x_range = (min(x_bounds), max(x_bounds))
|
|
1198
|
-
x_slice =
|
|
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 =
|
|
1155
|
+
y_slice = y_range[1]//2
|
|
1202
1156
|
else:
|
|
1203
1157
|
y_range = (min(y_bounds), max(y_bounds))
|
|
1204
|
-
y_slice =
|
|
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 =
|
|
1161
|
+
z_slice = z_range[1]//2
|
|
1208
1162
|
else:
|
|
1209
1163
|
z_range = (min(z_bounds), max(z_bounds))
|
|
1210
|
-
z_slice =
|
|
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
|
-
|
|
1219
|
-
|
|
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
|
-
#
|
|
1225
|
-
#
|
|
1226
|
-
#
|
|
1227
|
-
#
|
|
1228
|
-
#
|
|
1229
|
-
#
|
|
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*
|
|
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*
|
|
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
|
-
+
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1298
|
-
|
|
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.
|
|
1303
|
-
|
|
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.
|
|
1308
|
-
|
|
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
|
-
#
|
|
1312
|
-
#
|
|
1313
|
-
#
|
|
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
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
1309
|
+
nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
|
|
1365
1310
|
|
|
1366
1311
|
# Add the reconstructed data NXprocess to the new Nexus object
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
if 'data' not in
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
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
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
1437
|
-
#
|
|
1438
|
-
#
|
|
1439
|
-
#
|
|
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
|
-
[
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
-
#
|
|
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 =
|
|
1425
|
+
x_slice = x_range[1]//2
|
|
1481
1426
|
else:
|
|
1482
1427
|
x_range = (min(x_bounds), max(x_bounds))
|
|
1483
|
-
x_slice =
|
|
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 =
|
|
1431
|
+
y_slice = y_range[1]//2
|
|
1487
1432
|
else:
|
|
1488
1433
|
y_range = (min(y_bounds), max(y_bounds))
|
|
1489
|
-
y_slice =
|
|
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 =
|
|
1437
|
+
z_slice = z_range[1]//2
|
|
1493
1438
|
else:
|
|
1494
1439
|
z_range = (min(z_bounds), max(z_bounds))
|
|
1495
|
-
z_slice =
|
|
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
|
-
#
|
|
1502
|
-
#
|
|
1503
|
-
#
|
|
1504
|
-
#
|
|
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
|
-
|
|
1511
|
-
|
|
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*
|
|
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*
|
|
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*
|
|
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 =
|
|
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.
|
|
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 =
|
|
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.
|
|
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 =
|
|
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.
|
|
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
|
-
#
|
|
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
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
1543
|
+
nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
|
|
1613
1544
|
|
|
1614
1545
|
# Add the combined data NXprocess to the new Nexus object
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if 'data' not in
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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 =
|
|
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.
|
|
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 =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
#
|
|
1833
|
-
row_low = int((row_low_fit+row_upp_fit-num_row_min)
|
|
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)
|
|
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]
|
|
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.
|
|
1810
|
+
self._outputdir, 'rotation_calibration_rows.png'))
|
|
1889
1811
|
else:
|
|
1890
1812
|
fig.savefig(os_path.join(
|
|
1891
|
-
self.
|
|
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 =
|
|
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 =
|
|
1911
|
-
nxentry.instrument.detector.sequence_number
|
|
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 =
|
|
1916
|
-
|
|
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] ==
|
|
1921
|
-
|
|
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'
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1945
|
+
image_mask = [True]*len(thetas)
|
|
2027
1946
|
else:
|
|
2028
|
-
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 =
|
|
2036
|
-
|
|
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 =
|
|
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[
|
|
2050
|
-
for
|
|
1968
|
+
field_indices_all[i]
|
|
1969
|
+
for i, z in enumerate(z_translation_all)
|
|
2051
1970
|
if z == z_translation]
|
|
2052
|
-
field_indices_masked =
|
|
2053
|
-
|
|
2054
|
-
|
|
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(
|
|
2058
|
-
nxentry.sample.z_translation
|
|
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 =
|
|
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 =
|
|
2067
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
2101
|
+
reduced_data.x_translation = horizontal_shifts
|
|
2183
2102
|
reduced_data.x_translation.units = 'mm'
|
|
2184
|
-
reduced_data.z_translation =
|
|
2103
|
+
reduced_data.z_translation = vertical_shifts
|
|
2185
2104
|
reduced_data.z_translation.units = 'mm'
|
|
2186
|
-
reduced_data.data.tomo_fields =
|
|
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,
|
|
2197
|
-
path=None, num_core=1,
|
|
2198
|
-
|
|
2199
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
2168
|
+
selected_center_offset = center_offset_vo
|
|
2238
2169
|
if self._interactive or self._save_figs:
|
|
2239
2170
|
|
|
2240
|
-
#
|
|
2241
|
-
|
|
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
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
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
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
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
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
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
|
-
#
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
-
|
|
2343
|
-
|
|
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
|
-
|
|
2348
|
-
|
|
2349
|
-
selected_center_offset
|
|
2350
|
-
|
|
2351
|
-
|
|
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
|
-
|
|
2365
|
-
|
|
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
|
-
[
|
|
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.
|
|
2391
|
-
f'
|
|
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
|
-
|
|
2395
|
-
|
|
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
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
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
|
|
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
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
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
|
-
#
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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
|
-
|
|
2452
|
-
|
|
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
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
"""
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
2503
|
-
"""Callback function for the "
|
|
2504
|
-
|
|
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
|
-
(
|
|
2514
|
-
|
|
2515
|
-
|
|
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(
|
|
2519
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2543
|
-
for i, (ax,
|
|
2544
|
-
axs,
|
|
2545
|
-
ax.imshow(
|
|
2546
|
-
if
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
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"
|
|
2574
|
-
'center calibration search',
|
|
2620
|
+
'Press "Accept" to accept this value or "Reject" if not',
|
|
2575
2621
|
**subtitle_props)
|
|
2576
2622
|
else:
|
|
2577
|
-
if
|
|
2578
|
-
|
|
2579
|
-
*
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
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
|
-
|
|
2586
|
-
*
|
|
2587
|
-
|
|
2588
|
-
|
|
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(
|
|
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=
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|
-
#
|
|
2723
|
+
# tomo_stack axis data order: row,theta,column
|
|
2724
|
+
# thetas in radians
|
|
2672
2725
|
# centers_offset: tomography axis shift in pixels relative
|
|
2673
|
-
#
|
|
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
|
-
#
|
|
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,
|
|
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,
|
|
2763
|
-
|
|
2764
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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]
|
|
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
|
-
#
|
|
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 =
|
|
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(
|
|
3444
|
+
z_translations = list(detector.z_translation.nxdata)
|
|
3416
3445
|
else:
|
|
3417
3446
|
z_translations = [0.]
|
|
3418
|
-
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(
|
|
3486
|
+
#image_sets.append(detector.data.nxdata[n])
|
|
3487
|
+
image_sets.append(detector.data[n])
|
|
3458
3488
|
else:
|
|
3459
|
-
image_sets.append(
|
|
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
|
|
3580
|
+
nxroot[sample_type].set_default()
|
|
3550
3581
|
|
|
3551
3582
|
return nxroot
|
|
3552
3583
|
|