ChessAnalysisPipeline 0.0.13__py3-none-any.whl → 0.0.15__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 +10 -0
- CHAP/common/models/map.py +389 -124
- CHAP/common/processor.py +1494 -59
- CHAP/common/reader.py +180 -8
- CHAP/common/writer.py +192 -15
- CHAP/edd/__init__.py +12 -3
- CHAP/edd/models.py +868 -451
- CHAP/edd/processor.py +2383 -462
- CHAP/edd/reader.py +672 -0
- CHAP/edd/utils.py +906 -172
- CHAP/foxden/__init__.py +6 -0
- CHAP/foxden/processor.py +42 -0
- CHAP/foxden/writer.py +65 -0
- CHAP/pipeline.py +35 -3
- CHAP/runner.py +43 -16
- CHAP/tomo/models.py +15 -5
- CHAP/tomo/processor.py +871 -761
- CHAP/utils/__init__.py +1 -0
- CHAP/utils/fit.py +1339 -1309
- CHAP/utils/general.py +568 -105
- CHAP/utils/models.py +567 -0
- CHAP/utils/scanparsers.py +460 -77
- ChessAnalysisPipeline-0.0.15.dist-info/LICENSE +60 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/WHEEL +1 -1
- ChessAnalysisPipeline-0.0.13.dist-info/LICENSE +0 -21
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/top_level.txt +0 -0
CHAP/tomo/processor.py
CHANGED
|
@@ -8,7 +8,6 @@ Description: Module for Processors used only by tomography experiments
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# System modules
|
|
11
|
-
from os import mkdir
|
|
12
11
|
from os import path as os_path
|
|
13
12
|
from sys import exit as sys_exit
|
|
14
13
|
from time import time
|
|
@@ -19,16 +18,18 @@ import numpy as np
|
|
|
19
18
|
# Local modules
|
|
20
19
|
from CHAP.utils.general import (
|
|
21
20
|
is_num,
|
|
21
|
+
is_num_series,
|
|
22
22
|
is_int_pair,
|
|
23
23
|
input_int,
|
|
24
24
|
input_num,
|
|
25
|
+
input_num_list,
|
|
25
26
|
input_yesno,
|
|
26
27
|
select_image_indices,
|
|
27
28
|
select_roi_1d,
|
|
28
29
|
select_roi_2d,
|
|
29
30
|
quick_imshow,
|
|
31
|
+
nxcopy,
|
|
30
32
|
)
|
|
31
|
-
from CHAP.utils.fit import Fit
|
|
32
33
|
from CHAP.processor import Processor
|
|
33
34
|
from CHAP.reader import main
|
|
34
35
|
|
|
@@ -111,6 +112,7 @@ class TomoCHESSMapConverter(Processor):
|
|
|
111
112
|
NXdetector,
|
|
112
113
|
NXentry,
|
|
113
114
|
NXinstrument,
|
|
115
|
+
NXlink,
|
|
114
116
|
NXroot,
|
|
115
117
|
NXsample,
|
|
116
118
|
NXsource,
|
|
@@ -125,12 +127,19 @@ class TomoCHESSMapConverter(Processor):
|
|
|
125
127
|
tomofields = get_nxroot(data, 'tomofields')
|
|
126
128
|
detector_config = self.get_config(data, 'tomo.models.Detector')
|
|
127
129
|
|
|
128
|
-
if darkfield is not None
|
|
129
|
-
|
|
130
|
+
if darkfield is not None:
|
|
131
|
+
if isinstance(darkfield, NXroot):
|
|
132
|
+
darkfield = darkfield[darkfield.default]
|
|
133
|
+
if not isinstance(darkfield, NXentry):
|
|
134
|
+
raise ValueError(f'Invalid parameter darkfield ({darkfield})')
|
|
135
|
+
if isinstance(brightfield, NXroot):
|
|
136
|
+
brightfield = brightfield[brightfield.default]
|
|
130
137
|
if not isinstance(brightfield, NXentry):
|
|
131
|
-
raise ValueError('Invalid parameter brightfield ({brightfield})')
|
|
138
|
+
raise ValueError(f'Invalid parameter brightfield ({brightfield})')
|
|
139
|
+
if isinstance(tomofields, NXroot):
|
|
140
|
+
tomofields = tomofields[tomofields.default]
|
|
132
141
|
if not isinstance(tomofields, NXentry):
|
|
133
|
-
raise ValueError('Invalid parameter tomofields {tomofields})')
|
|
142
|
+
raise ValueError(f'Invalid parameter tomofields {tomofields})')
|
|
134
143
|
|
|
135
144
|
# Construct NXroot
|
|
136
145
|
nxroot = NXroot()
|
|
@@ -148,7 +157,7 @@ class TomoCHESSMapConverter(Processor):
|
|
|
148
157
|
'(available independent dimensions: '
|
|
149
158
|
f'{independent_dimensions})')
|
|
150
159
|
rotation_angles_index = \
|
|
151
|
-
tomofields.data.
|
|
160
|
+
tomofields.data.axes.index('rotation_angles')
|
|
152
161
|
rotation_angle_data_type = \
|
|
153
162
|
tomofields.data.rotation_angles.attrs['data_type']
|
|
154
163
|
if rotation_angle_data_type != 'scan_column':
|
|
@@ -157,7 +166,7 @@ class TomoCHESSMapConverter(Processor):
|
|
|
157
166
|
matched_dimensions.pop(matched_dimensions.index('rotation_angles'))
|
|
158
167
|
if 'x_translation' in independent_dimensions:
|
|
159
168
|
x_translation_index = \
|
|
160
|
-
tomofields.data.
|
|
169
|
+
tomofields.data.axes.index('x_translation')
|
|
161
170
|
x_translation_data_type = \
|
|
162
171
|
tomofields.data.x_translation.attrs['data_type']
|
|
163
172
|
x_translation_name = \
|
|
@@ -170,7 +179,7 @@ class TomoCHESSMapConverter(Processor):
|
|
|
170
179
|
x_translation_data_type = None
|
|
171
180
|
if 'z_translation' in independent_dimensions:
|
|
172
181
|
z_translation_index = \
|
|
173
|
-
tomofields.data.
|
|
182
|
+
tomofields.data.axes.index('z_translation')
|
|
174
183
|
z_translation_data_type = \
|
|
175
184
|
tomofields.data.z_translation.attrs['data_type']
|
|
176
185
|
z_translation_name = \
|
|
@@ -188,9 +197,11 @@ class TomoCHESSMapConverter(Processor):
|
|
|
188
197
|
'"rotation_angles"}')
|
|
189
198
|
|
|
190
199
|
# Construct base NXentry and add to NXroot
|
|
191
|
-
nxentry = NXentry()
|
|
192
|
-
nxroot[
|
|
193
|
-
|
|
200
|
+
nxentry = NXentry(name=map_config.title)
|
|
201
|
+
nxroot[nxentry.nxname] = nxentry
|
|
202
|
+
nxentry.set_default()
|
|
203
|
+
|
|
204
|
+
# Add configuration fields
|
|
194
205
|
nxentry.definition = 'NXtomo'
|
|
195
206
|
nxentry.map_config = tomofields.map_config
|
|
196
207
|
|
|
@@ -214,12 +225,12 @@ class TomoCHESSMapConverter(Processor):
|
|
|
214
225
|
# Add an NXdetector to the NXinstrument
|
|
215
226
|
# (do not fill in data fields yet)
|
|
216
227
|
detector_prefix = detector_config.prefix
|
|
217
|
-
detectors = list(
|
|
218
|
-
|
|
228
|
+
detectors = list(
|
|
229
|
+
set(tomofields.data.entries) - set(independent_dimensions))
|
|
219
230
|
if detector_prefix not in detectors:
|
|
220
231
|
raise ValueError(f'Data for detector {detector_prefix} is '
|
|
221
232
|
f'unavailable (available detectors: {detectors})')
|
|
222
|
-
tomo_stacks =
|
|
233
|
+
tomo_stacks = tomofields.data[detector_prefix]
|
|
223
234
|
tomo_stack_shape = tomo_stacks.shape
|
|
224
235
|
assert len(tomo_stack_shape) == 2+len(independent_dimensions)
|
|
225
236
|
assert tomo_stack_shape[-2] == detector_config.rows
|
|
@@ -272,8 +283,8 @@ class TomoCHESSMapConverter(Processor):
|
|
|
272
283
|
num_image = data_shape[0]
|
|
273
284
|
image_keys += num_image*[2]
|
|
274
285
|
sequence_numbers += list(range(num_image))
|
|
275
|
-
image_stacks.append(
|
|
276
|
-
nxcollection.data[detector_prefix])
|
|
286
|
+
image_stacks.append(
|
|
287
|
+
nxcollection.data[detector_prefix])
|
|
277
288
|
rotation_angles += num_image*[0.0]
|
|
278
289
|
if (x_translation_data_type == 'spec_motor' or
|
|
279
290
|
z_translation_data_type == 'spec_motor'):
|
|
@@ -312,8 +323,8 @@ class TomoCHESSMapConverter(Processor):
|
|
|
312
323
|
num_image = data_shape[0]
|
|
313
324
|
image_keys += num_image*[1]
|
|
314
325
|
sequence_numbers += list(range(num_image))
|
|
315
|
-
image_stacks.append(
|
|
316
|
-
nxcollection.data[detector_prefix])
|
|
326
|
+
image_stacks.append(
|
|
327
|
+
nxcollection.data[detector_prefix])
|
|
317
328
|
rotation_angles += num_image*[0.0]
|
|
318
329
|
if (x_translation_data_type == 'spec_motor' or
|
|
319
330
|
z_translation_data_type == 'spec_motor'):
|
|
@@ -347,10 +358,11 @@ class TomoCHESSMapConverter(Processor):
|
|
|
347
358
|
z_trans = [0.0]
|
|
348
359
|
tomo_stacks = np.reshape(tomo_stacks, (1,1,*tomo_stacks.shape))
|
|
349
360
|
else:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
361
|
+
z_trans = tomofields.data.z_translation.nxdata
|
|
362
|
+
# if len(list(tomofields.data.z_translation)):
|
|
363
|
+
# z_trans = list(tomofields.data.z_translation)
|
|
364
|
+
# else:
|
|
365
|
+
# z_trans = [float(tomofields.data.z_translation)]
|
|
354
366
|
if rotation_angles_index < z_translation_index:
|
|
355
367
|
tomo_stacks = np.swapaxes(
|
|
356
368
|
tomo_stacks, rotation_angles_index,
|
|
@@ -363,14 +375,16 @@ class TomoCHESSMapConverter(Processor):
|
|
|
363
375
|
tomo_stacks, rotation_angles_index, x_translation_index)
|
|
364
376
|
tomo_stacks = np.expand_dims(tomo_stacks, 0)
|
|
365
377
|
else:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
378
|
+
x_trans = tomofields.data.x_translation.nxdata
|
|
379
|
+
z_trans = tomofields.data.z_translation.nxdata
|
|
380
|
+
#if tomofields.data.x_translation.size > 1:
|
|
381
|
+
# x_trans = list(tomofields.data.x_translation)
|
|
382
|
+
#else:
|
|
383
|
+
# x_trans = [float(tomofields.data.x_translation)]
|
|
384
|
+
#if len(list(tomofields.data.z_translation)):
|
|
385
|
+
# z_trans = list(tomofields.data.z_translation)
|
|
386
|
+
#else:
|
|
387
|
+
# z_trans = [float(tomofields.data.z_translation)]
|
|
374
388
|
if (rotation_angles_index
|
|
375
389
|
< max(x_translation_index, z_translation_index)):
|
|
376
390
|
tomo_stacks = np.swapaxes(
|
|
@@ -380,8 +394,7 @@ class TomoCHESSMapConverter(Processor):
|
|
|
380
394
|
tomo_stacks = np.swapaxes(
|
|
381
395
|
tomo_stacks, x_translation_index, z_translation_index)
|
|
382
396
|
# Restrict to 180 degrees set of data for now to match old code
|
|
383
|
-
thetas =
|
|
384
|
-
#RV num_image = len(tomofields.data.rotation_angles)
|
|
397
|
+
thetas = tomofields.data.rotation_angles.nxdata
|
|
385
398
|
assert len(thetas) > 2
|
|
386
399
|
delta_theta = thetas[1]-thetas[0]
|
|
387
400
|
if thetas[-1]-thetas[0] > 180-delta_theta:
|
|
@@ -394,10 +407,8 @@ class TomoCHESSMapConverter(Processor):
|
|
|
394
407
|
for j, x in enumerate(x_trans):
|
|
395
408
|
image_keys += num_image*[0]
|
|
396
409
|
sequence_numbers += list(range(num_image))
|
|
397
|
-
image_stacks.append(
|
|
398
|
-
tomo_stacks[i,j][:image_end,:,:]))
|
|
410
|
+
image_stacks.append(tomo_stacks[i,j,:image_end,:,:])
|
|
399
411
|
rotation_angles += list(thetas)
|
|
400
|
-
#RV rotation_angles += list(tomofields.data.rotation_angles)
|
|
401
412
|
x_translations += num_image*[x]
|
|
402
413
|
z_translations += num_image*[z]
|
|
403
414
|
|
|
@@ -415,18 +426,12 @@ class TomoCHESSMapConverter(Processor):
|
|
|
415
426
|
nxsample.z_translation.units = 'mm'
|
|
416
427
|
|
|
417
428
|
# 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
|
|
429
|
+
nxentry.data = NXdata(NXlink(nxentry.instrument.detector.data))
|
|
430
|
+
nxentry.data.makelink(nxentry.instrument.detector.image_key)
|
|
431
|
+
nxentry.data.makelink(nxentry.sample.rotation_angle)
|
|
432
|
+
nxentry.data.makelink(nxentry.sample.x_translation)
|
|
433
|
+
nxentry.data.makelink(nxentry.sample.z_translation)
|
|
434
|
+
nxentry.data.set_default()
|
|
430
435
|
|
|
431
436
|
return nxroot
|
|
432
437
|
|
|
@@ -439,9 +444,9 @@ class TomoDataProcessor(Processor):
|
|
|
439
444
|
"""
|
|
440
445
|
|
|
441
446
|
def process(
|
|
442
|
-
self, data, interactive=False, reduce_data=False,
|
|
447
|
+
self, data, outputdir='.', interactive=False, reduce_data=False,
|
|
443
448
|
find_center=False, calibrate_center=False, reconstruct_data=False,
|
|
444
|
-
combine_data=False,
|
|
449
|
+
combine_data=False, save_figs='no'):
|
|
445
450
|
"""
|
|
446
451
|
Process the input map or configuration with the step specific
|
|
447
452
|
instructions and return either a dictionary or a
|
|
@@ -450,6 +455,8 @@ class TomoDataProcessor(Processor):
|
|
|
450
455
|
:param data: Input configuration and specific step instructions
|
|
451
456
|
for tomographic image reduction.
|
|
452
457
|
:type data: list[PipelineData]
|
|
458
|
+
:param outputdir: Output folder name, defaults to '.'.
|
|
459
|
+
:type outputdir:: str, optional
|
|
453
460
|
:param interactive: Allows for user interactions,
|
|
454
461
|
defaults to False.
|
|
455
462
|
:type interactive: bool, optional
|
|
@@ -468,8 +475,6 @@ class TomoDataProcessor(Processor):
|
|
|
468
475
|
:param combine_data: Combine the reconstructed tomography
|
|
469
476
|
stacks, defaults to False.
|
|
470
477
|
:type combine_data: bool, optional
|
|
471
|
-
:param output_folder: Output folder name, defaults to '.'.
|
|
472
|
-
:type output_folder:: str, optional
|
|
473
478
|
:param save_figs: Safe figures to file ('yes' or 'only') and/or
|
|
474
479
|
display figures ('yes' or 'no'), defaults to 'no'.
|
|
475
480
|
:type save_figs: Literal['yes', 'no', 'only'], optional
|
|
@@ -480,10 +485,7 @@ class TomoDataProcessor(Processor):
|
|
|
480
485
|
:rtype: Union[dict, nexusformat.nexus.NXroot]
|
|
481
486
|
"""
|
|
482
487
|
# Local modules
|
|
483
|
-
from nexusformat.nexus import
|
|
484
|
-
nxsetconfig,
|
|
485
|
-
NXroot,
|
|
486
|
-
)
|
|
488
|
+
from nexusformat.nexus import nxsetconfig
|
|
487
489
|
from CHAP.pipeline import PipelineItem
|
|
488
490
|
from CHAP.tomo.models import (
|
|
489
491
|
TomoReduceConfig,
|
|
@@ -529,8 +531,8 @@ class TomoDataProcessor(Processor):
|
|
|
529
531
|
nxroot = get_nxroot(data)
|
|
530
532
|
|
|
531
533
|
tomo = Tomo(
|
|
532
|
-
|
|
533
|
-
save_figs=save_figs)
|
|
534
|
+
logger=self.logger, interactive=interactive,
|
|
535
|
+
outputdir=outputdir, save_figs=save_figs)
|
|
534
536
|
|
|
535
537
|
nxsetconfig(memory=100000)
|
|
536
538
|
|
|
@@ -539,7 +541,7 @@ class TomoDataProcessor(Processor):
|
|
|
539
541
|
if (reduce_data or find_center
|
|
540
542
|
or reconstruct_data or reconstruct_data_config is not None
|
|
541
543
|
or combine_data or combine_data_config is not None):
|
|
542
|
-
self.
|
|
544
|
+
self.logger.warning('Ignoring any step specific instructions '
|
|
543
545
|
'during center calibration')
|
|
544
546
|
if nxroot is None:
|
|
545
547
|
raise RuntimeError('Map info required to calibrate the '
|
|
@@ -588,7 +590,7 @@ class TomoDataProcessor(Processor):
|
|
|
588
590
|
|
|
589
591
|
# Reconstruct tomography stacks
|
|
590
592
|
# RV pass reconstruct_data_config and center_config directly to
|
|
591
|
-
#
|
|
593
|
+
# tomo.reconstruct_data?
|
|
592
594
|
if reconstruct_data or reconstruct_data_config is not None:
|
|
593
595
|
if reconstruct_data_config is None:
|
|
594
596
|
reconstruct_data_config = TomoReconstructConfig()
|
|
@@ -606,49 +608,6 @@ class TomoDataProcessor(Processor):
|
|
|
606
608
|
return center_config
|
|
607
609
|
return nxroot
|
|
608
610
|
|
|
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
611
|
|
|
653
612
|
class SetNumexprThreads:
|
|
654
613
|
"""
|
|
@@ -694,7 +653,7 @@ class Tomo:
|
|
|
694
653
|
"""Reconstruct a set of tomographic images."""
|
|
695
654
|
|
|
696
655
|
def __init__(
|
|
697
|
-
self, interactive=False, num_core=-1,
|
|
656
|
+
self, logger=None, outputdir='.', interactive=False, num_core=-1,
|
|
698
657
|
save_figs='no'):
|
|
699
658
|
"""
|
|
700
659
|
Initialize Tomo.
|
|
@@ -704,28 +663,31 @@ class Tomo:
|
|
|
704
663
|
:type interactive: bool, optional
|
|
705
664
|
:param num_core: Number of processors.
|
|
706
665
|
:type num_core: int
|
|
707
|
-
:param
|
|
708
|
-
:type
|
|
666
|
+
:param outputdir: Output folder name, defaults to '.'.
|
|
667
|
+
:type outputdir:: str, optional
|
|
709
668
|
:param save_figs: Safe figures to file ('yes' or 'only') and/or
|
|
710
669
|
display figures ('yes' or 'no'), defaults to 'no'.
|
|
711
670
|
:type save_figs: Literal['yes', 'no', 'only'], optional
|
|
712
671
|
:raises ValueError: Invalid input parameter.
|
|
713
672
|
"""
|
|
714
673
|
# System modules
|
|
715
|
-
from logging import getLogger
|
|
716
674
|
from multiprocessing import cpu_count
|
|
717
675
|
|
|
718
676
|
self.__name__ = self.__class__.__name__
|
|
719
|
-
|
|
720
|
-
|
|
677
|
+
if logger is None:
|
|
678
|
+
# System modules
|
|
679
|
+
from logging import getLogger
|
|
680
|
+
|
|
681
|
+
self._logger = getLogger(self.__name__)
|
|
682
|
+
self._logger.propagate = False
|
|
683
|
+
else:
|
|
684
|
+
self._logger = logger
|
|
721
685
|
|
|
722
686
|
if not isinstance(interactive, bool):
|
|
723
687
|
raise ValueError(f'Invalid parameter interactive ({interactive})')
|
|
688
|
+
self._outputdir = outputdir
|
|
724
689
|
self._interactive = interactive
|
|
725
690
|
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
691
|
self._test_config = {}
|
|
730
692
|
if save_figs == 'only':
|
|
731
693
|
self._save_only = True
|
|
@@ -751,6 +713,12 @@ class Tomo:
|
|
|
751
713
|
f'num_core = {self._num_core} is larger than the number '
|
|
752
714
|
f'of available processors and reduced to {cpu_count()}')
|
|
753
715
|
self._num_core = cpu_count()
|
|
716
|
+
# Tompy py uses numexpr with NUMEXPR_MAX_THREADS = 64
|
|
717
|
+
if self._num_core > 64:
|
|
718
|
+
self._logger.warning(
|
|
719
|
+
f'num_core = {self._num_core} is larger than the number '
|
|
720
|
+
f'of processors suitable to Tomopy and reduced to 64')
|
|
721
|
+
self._num_core = 64
|
|
754
722
|
|
|
755
723
|
def reduce_data(
|
|
756
724
|
self, nxroot, tool_config=None, calibrate_center_rows=False):
|
|
@@ -775,8 +743,9 @@ class Tomo:
|
|
|
775
743
|
|
|
776
744
|
self._logger.info('Generate the reduced tomography images')
|
|
777
745
|
|
|
746
|
+
# Validate input parameter
|
|
778
747
|
if isinstance(nxroot, NXroot):
|
|
779
|
-
nxentry = nxroot[nxroot.
|
|
748
|
+
nxentry = nxroot[nxroot.default]
|
|
780
749
|
else:
|
|
781
750
|
raise ValueError(
|
|
782
751
|
f'Invalid parameter nxroot {type(nxroot)}:\n{nxroot}')
|
|
@@ -796,13 +765,11 @@ class Tomo:
|
|
|
796
765
|
self._logger.warning('Ignoring parameter img_row_bounds '
|
|
797
766
|
'during rotation axis calibration')
|
|
798
767
|
img_row_bounds = None
|
|
799
|
-
|
|
800
768
|
image_key = nxentry.instrument.detector.get('image_key', None)
|
|
801
769
|
if image_key is None or 'data' not in nxentry.instrument.detector:
|
|
802
770
|
raise ValueError(f'Unable to find image_key or data in '
|
|
803
771
|
'instrument.detector '
|
|
804
772
|
f'({nxentry.instrument.detector.tree})')
|
|
805
|
-
image_key = np.asarray(image_key)
|
|
806
773
|
|
|
807
774
|
# Create an NXprocess to store data reduction (meta)data
|
|
808
775
|
reduced_data = NXprocess()
|
|
@@ -813,7 +780,7 @@ class Tomo:
|
|
|
813
780
|
# Generate bright field
|
|
814
781
|
reduced_data = self._gen_bright(nxentry, reduced_data, image_key)
|
|
815
782
|
|
|
816
|
-
# Get rotation angles for image stacks
|
|
783
|
+
# Get rotation angles for image stacks (in degrees)
|
|
817
784
|
thetas = self._gen_thetas(nxentry, image_key)
|
|
818
785
|
|
|
819
786
|
# Get the image stack mask to remove bad images from stack
|
|
@@ -829,7 +796,7 @@ class Tomo:
|
|
|
829
796
|
len(thetas)) < drop_fraction/100, 0, 1).astype(bool)
|
|
830
797
|
|
|
831
798
|
# Set zoom and/or rotation angle interval to reduce memory
|
|
832
|
-
#
|
|
799
|
+
# requirement
|
|
833
800
|
if image_mask is None:
|
|
834
801
|
zoom_perc, delta_theta = self._set_zoom_or_delta_theta(
|
|
835
802
|
thetas, delta_theta)
|
|
@@ -851,12 +818,12 @@ class Tomo:
|
|
|
851
818
|
img_row_bounds = self._set_detector_bounds(
|
|
852
819
|
nxentry, reduced_data, image_key, thetas[0],
|
|
853
820
|
img_row_bounds, calibrate_center_rows)
|
|
854
|
-
self._logger.
|
|
821
|
+
self._logger.debug(f'img_row_bounds = {img_row_bounds}')
|
|
855
822
|
if calibrate_center_rows:
|
|
856
823
|
calibrate_center_rows = tuple(sorted(img_row_bounds))
|
|
857
824
|
img_row_bounds = None
|
|
858
825
|
if img_row_bounds is None:
|
|
859
|
-
tbf_shape =
|
|
826
|
+
tbf_shape = reduced_data.data.bright_field.shape
|
|
860
827
|
img_row_bounds = (0, tbf_shape[0])
|
|
861
828
|
reduced_data.img_row_bounds = img_row_bounds
|
|
862
829
|
reduced_data.img_row_bounds.units = 'pixels'
|
|
@@ -873,34 +840,33 @@ class Tomo:
|
|
|
873
840
|
nxentry, reduced_data, image_key, calibrate_center_rows)
|
|
874
841
|
|
|
875
842
|
# 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']]
|
|
843
|
+
# any existing reduced data
|
|
844
|
+
exclude_items = [
|
|
845
|
+
f'{nxentry.nxname}/reduced_data/data',
|
|
846
|
+
f'{nxentry.nxname}/instrument/detector/data',
|
|
847
|
+
f'{nxentry.nxname}/instrument/detector/image_key',
|
|
848
|
+
f'{nxentry.nxname}/instrument/detector/sequence_number',
|
|
849
|
+
f'{nxentry.nxname}/sample/rotation_angle',
|
|
850
|
+
f'{nxentry.nxname}/sample/x_translation',
|
|
851
|
+
f'{nxentry.nxname}/sample/z_translation',
|
|
852
|
+
f'{nxentry.nxname}/data/data',
|
|
853
|
+
f'{nxentry.nxname}/data/image_key',
|
|
854
|
+
f'{nxentry.nxname}/data/rotation_angle',
|
|
855
|
+
f'{nxentry.nxname}/data/x_translation',
|
|
856
|
+
f'{nxentry.nxname}/data/z_translation',
|
|
857
|
+
]
|
|
858
|
+
nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
|
|
894
859
|
|
|
895
860
|
# Add the reduced data NXprocess
|
|
861
|
+
nxentry = nxroot[nxroot.default]
|
|
896
862
|
nxentry.reduced_data = reduced_data
|
|
897
863
|
|
|
898
864
|
if 'data' not in nxentry:
|
|
899
865
|
nxentry.data = NXdata()
|
|
866
|
+
nxentry.data.set_default()
|
|
900
867
|
nxentry.data.makelink(
|
|
901
868
|
nxentry.reduced_data.data.tomo_fields, name='reduced_data')
|
|
902
|
-
nxentry.data.makelink(
|
|
903
|
-
nxentry.reduced_data.rotation_angle, name='rotation_angle')
|
|
869
|
+
nxentry.data.makelink(nxentry.reduced_data.rotation_angle)
|
|
904
870
|
nxentry.data.attrs['signal'] = 'reduced_data'
|
|
905
871
|
|
|
906
872
|
return nxroot, calibrate_center_rows
|
|
@@ -920,51 +886,48 @@ class Tomo:
|
|
|
920
886
|
:rtype: dict
|
|
921
887
|
"""
|
|
922
888
|
# Third party modules
|
|
923
|
-
from nexusformat.nexus import
|
|
924
|
-
NXentry,
|
|
925
|
-
NXroot,
|
|
926
|
-
)
|
|
889
|
+
from nexusformat.nexus import NXroot
|
|
927
890
|
from yaml import safe_dump
|
|
928
891
|
|
|
929
892
|
self._logger.info('Find the calibrated center axis info')
|
|
930
893
|
|
|
931
894
|
if isinstance(nxroot, NXroot):
|
|
932
|
-
nxentry = nxroot[nxroot.
|
|
895
|
+
nxentry = nxroot[nxroot.default]
|
|
933
896
|
else:
|
|
934
897
|
raise ValueError(f'Invalid parameter nxroot ({nxroot})')
|
|
935
898
|
|
|
936
899
|
# Check if reduced data is available
|
|
937
|
-
if
|
|
938
|
-
or 'reduced_data' not in nxentry.data):
|
|
900
|
+
if 'reduced_data' not in nxentry:
|
|
939
901
|
raise ValueError(f'Unable to find valid reduced data in {nxentry}.')
|
|
940
902
|
|
|
941
903
|
# Select the image stack to find the calibrated center axis
|
|
942
|
-
#
|
|
904
|
+
# reduced data axes order: stack,theta,row,column
|
|
943
905
|
# Note: Nexus can't follow a link if the data it points to is
|
|
944
|
-
#
|
|
945
|
-
#
|
|
906
|
+
# too big get the data from the actual place, not from
|
|
907
|
+
# nxentry.data
|
|
946
908
|
num_tomo_stacks = nxentry.reduced_data.data.tomo_fields.shape[0]
|
|
909
|
+
self._logger.debug(f'num_tomo_stacks = {num_tomo_stacks}')
|
|
947
910
|
if num_tomo_stacks == 1:
|
|
948
911
|
center_stack_index = 0
|
|
949
912
|
else:
|
|
950
913
|
center_stack_index = tool_config.center_stack_index
|
|
951
914
|
if calibrate_center_rows:
|
|
952
|
-
center_stack_index =
|
|
915
|
+
center_stack_index = num_tomo_stacks//2
|
|
953
916
|
elif self._interactive:
|
|
954
917
|
if center_stack_index is None:
|
|
955
918
|
center_stack_index = input_int(
|
|
956
919
|
'\nEnter tomography stack index to calibrate the '
|
|
957
920
|
'center axis', ge=0, lt=num_tomo_stacks,
|
|
958
|
-
default=
|
|
921
|
+
default=num_tomo_stacks//2)
|
|
959
922
|
else:
|
|
960
923
|
if center_stack_index is None:
|
|
961
|
-
center_stack_index =
|
|
924
|
+
center_stack_index = num_tomo_stacks//2
|
|
962
925
|
self._logger.warning(
|
|
963
926
|
'center_stack_index unspecified, use stack '
|
|
964
927
|
f'{center_stack_index} to find center axis info')
|
|
965
928
|
|
|
966
929
|
# Get thetas (in degrees)
|
|
967
|
-
thetas =
|
|
930
|
+
thetas = nxentry.reduced_data.rotation_angle.nxdata
|
|
968
931
|
|
|
969
932
|
# Select center rows
|
|
970
933
|
if calibrate_center_rows:
|
|
@@ -975,7 +938,7 @@ class Tomo:
|
|
|
975
938
|
import matplotlib.pyplot as plt
|
|
976
939
|
|
|
977
940
|
# Get full bright field
|
|
978
|
-
tbf =
|
|
941
|
+
tbf = nxentry.reduced_data.data.bright_field.nxdata
|
|
979
942
|
tbf_shape = tbf.shape
|
|
980
943
|
|
|
981
944
|
# Get image bounds
|
|
@@ -1022,8 +985,7 @@ class Tomo:
|
|
|
1022
985
|
# Plot results
|
|
1023
986
|
if self._save_figs:
|
|
1024
987
|
fig.savefig(
|
|
1025
|
-
os_path.join(
|
|
1026
|
-
self._output_folder, 'center_finding_rows.png'))
|
|
988
|
+
os_path.join(self._outputdir, 'center_finding_rows.png'))
|
|
1027
989
|
plt.close()
|
|
1028
990
|
|
|
1029
991
|
# Get effective pixel_size
|
|
@@ -1047,12 +1009,13 @@ class Tomo:
|
|
|
1047
1009
|
t0 = time()
|
|
1048
1010
|
center_offsets.append(
|
|
1049
1011
|
self._find_center_one_plane(
|
|
1050
|
-
nxentry.reduced_data.data.tomo_fields
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1012
|
+
nxentry.reduced_data.data.tomo_fields, center_stack_index,
|
|
1013
|
+
row, offset_row, np.radians(thetas), eff_pixel_size,
|
|
1014
|
+
cross_sectional_dim, path=self._outputdir,
|
|
1015
|
+
num_core=self._num_core,
|
|
1054
1016
|
center_offset_min=tool_config.center_offset_min,
|
|
1055
1017
|
center_offset_max=tool_config.center_offset_max,
|
|
1018
|
+
center_search_range=tool_config.center_search_range,
|
|
1056
1019
|
gaussian_sigma=tool_config.gaussian_sigma,
|
|
1057
1020
|
ring_width=tool_config.ring_width,
|
|
1058
1021
|
prev_center_offset=prev_center_offset))
|
|
@@ -1097,9 +1060,8 @@ class Tomo:
|
|
|
1097
1060
|
"""
|
|
1098
1061
|
# Third party modules
|
|
1099
1062
|
from nexusformat.nexus import (
|
|
1100
|
-
nxgetconfig,
|
|
1101
1063
|
NXdata,
|
|
1102
|
-
|
|
1064
|
+
NXfield,
|
|
1103
1065
|
NXprocess,
|
|
1104
1066
|
NXroot,
|
|
1105
1067
|
)
|
|
@@ -1107,15 +1069,14 @@ class Tomo:
|
|
|
1107
1069
|
self._logger.info('Reconstruct the tomography data')
|
|
1108
1070
|
|
|
1109
1071
|
if isinstance(nxroot, NXroot):
|
|
1110
|
-
nxentry = nxroot[nxroot.
|
|
1072
|
+
nxentry = nxroot[nxroot.default]
|
|
1111
1073
|
else:
|
|
1112
1074
|
raise ValueError(f'Invalid parameter nxroot ({nxroot})')
|
|
1113
1075
|
if not isinstance(center_info, dict):
|
|
1114
1076
|
raise ValueError(f'Invalid parameter center_info ({center_info})')
|
|
1115
1077
|
|
|
1116
1078
|
# Check if reduced data is available
|
|
1117
|
-
if
|
|
1118
|
-
or 'reduced_data' not in nxentry.data):
|
|
1079
|
+
if 'reduced_data' not in nxentry:
|
|
1119
1080
|
raise ValueError(f'Unable to find valid reduced data in {nxentry}.')
|
|
1120
1081
|
|
|
1121
1082
|
# Create an NXprocess to store image reconstruction (meta)data
|
|
@@ -1132,38 +1093,28 @@ class Tomo:
|
|
|
1132
1093
|
/ (center_rows[1]-center_rows[0])
|
|
1133
1094
|
|
|
1134
1095
|
# Get thetas (in degrees)
|
|
1135
|
-
thetas =
|
|
1096
|
+
thetas = nxentry.reduced_data.rotation_angle.nxdata
|
|
1136
1097
|
|
|
1137
1098
|
# Reconstruct tomography data
|
|
1138
|
-
#
|
|
1139
|
-
#
|
|
1099
|
+
# - reduced data axes order: stack,theta,row,column
|
|
1100
|
+
# - reconstructed data axes order: row/-z,y,x
|
|
1140
1101
|
# Note: Nexus can't follow a link if the data it points to is
|
|
1141
|
-
#
|
|
1142
|
-
#
|
|
1102
|
+
# too big get the data from the actual place, not from
|
|
1103
|
+
# nxentry.data
|
|
1143
1104
|
if 'zoom_perc' in nxentry.reduced_data:
|
|
1144
1105
|
res_title = f'{nxentry.reduced_data.attrs["zoom_perc"]}p'
|
|
1145
1106
|
else:
|
|
1146
1107
|
res_title = 'fullres'
|
|
1147
|
-
tomo_stacks =
|
|
1108
|
+
tomo_stacks = nxentry.reduced_data.data.tomo_fields
|
|
1148
1109
|
num_tomo_stacks = tomo_stacks.shape[0]
|
|
1149
|
-
tomo_recon_stacks =
|
|
1110
|
+
tomo_recon_stacks = []
|
|
1150
1111
|
img_row_bounds = tuple(nxentry.reduced_data.get(
|
|
1151
1112
|
'img_row_bounds', (0, tomo_stacks.shape[2])))
|
|
1152
1113
|
center_rows -= img_row_bounds[0]
|
|
1153
1114
|
for i in range(num_tomo_stacks):
|
|
1154
1115
|
# 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)
|
|
1116
|
+
# row,theta,column
|
|
1117
|
+
tomo_stack = np.swapaxes(tomo_stacks[i,:,:,:], 0, 1)
|
|
1167
1118
|
assert len(thetas) == tomo_stack.shape[1]
|
|
1168
1119
|
assert 0 <= center_rows[0] < center_rows[1] < tomo_stack.shape[0]
|
|
1169
1120
|
center_offsets = [
|
|
@@ -1173,41 +1124,42 @@ class Tomo:
|
|
|
1173
1124
|
]
|
|
1174
1125
|
t0 = time()
|
|
1175
1126
|
tomo_recon_stack = self._reconstruct_one_tomo_stack(
|
|
1176
|
-
tomo_stack, thetas, center_offsets=center_offsets,
|
|
1127
|
+
tomo_stack, np.radians(thetas), center_offsets=center_offsets,
|
|
1177
1128
|
num_core=self._num_core, algorithm='gridrec',
|
|
1178
1129
|
secondary_iters=tool_config.secondary_iters,
|
|
1130
|
+
gaussian_sigma=tool_config.gaussian_sigma,
|
|
1179
1131
|
remove_stripe_sigma=tool_config.remove_stripe_sigma,
|
|
1180
1132
|
ring_width=tool_config.ring_width)
|
|
1181
1133
|
self._logger.info(
|
|
1182
1134
|
f'Reconstruction of stack {i} took {time()-t0:.2f} seconds')
|
|
1183
1135
|
|
|
1184
1136
|
# Combine stacks
|
|
1185
|
-
tomo_recon_stacks
|
|
1137
|
+
tomo_recon_stacks.append(tomo_recon_stack)
|
|
1186
1138
|
|
|
1187
1139
|
# Resize the reconstructed tomography data
|
|
1188
|
-
#
|
|
1140
|
+
# - reconstructed axis data order in each stack: row/-z,y,x
|
|
1189
1141
|
tomo_recon_shape = tomo_recon_stacks[0].shape
|
|
1190
1142
|
x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
|
|
1191
1143
|
tomo_recon_stacks, x_bounds=tool_config.x_bounds,
|
|
1192
1144
|
y_bounds=tool_config.y_bounds, z_bounds=tool_config.z_bounds)
|
|
1193
1145
|
if x_bounds is None:
|
|
1194
1146
|
x_range = (0, tomo_recon_shape[2])
|
|
1195
|
-
x_slice =
|
|
1147
|
+
x_slice = x_range[1]//2
|
|
1196
1148
|
else:
|
|
1197
1149
|
x_range = (min(x_bounds), max(x_bounds))
|
|
1198
|
-
x_slice =
|
|
1150
|
+
x_slice = (x_bounds[0]+x_bounds[1])//2
|
|
1199
1151
|
if y_bounds is None:
|
|
1200
1152
|
y_range = (0, tomo_recon_shape[1])
|
|
1201
|
-
y_slice =
|
|
1153
|
+
y_slice = y_range[1]//2
|
|
1202
1154
|
else:
|
|
1203
1155
|
y_range = (min(y_bounds), max(y_bounds))
|
|
1204
|
-
y_slice =
|
|
1156
|
+
y_slice = (y_bounds[0]+y_bounds[1])//2
|
|
1205
1157
|
if z_bounds is None:
|
|
1206
1158
|
z_range = (0, tomo_recon_shape[0])
|
|
1207
|
-
z_slice =
|
|
1159
|
+
z_slice = z_range[1]//2
|
|
1208
1160
|
else:
|
|
1209
1161
|
z_range = (min(z_bounds), max(z_bounds))
|
|
1210
|
-
z_slice =
|
|
1162
|
+
z_slice = (z_bounds[0]+z_bounds[1])//2
|
|
1211
1163
|
z_dim_org = tomo_recon_shape[0]
|
|
1212
1164
|
for i, stack in enumerate(tomo_recon_stacks):
|
|
1213
1165
|
tomo_recon_stacks[i] = stack[
|
|
@@ -1215,18 +1167,17 @@ class Tomo:
|
|
|
1215
1167
|
x_range[0]:x_range[1]]
|
|
1216
1168
|
tomo_recon_stacks = np.asarray(tomo_recon_stacks)
|
|
1217
1169
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
column_pixel_size = float(
|
|
1221
|
-
nxentry.instrument.detector.column_pixel_size)
|
|
1170
|
+
detector = nxentry.instrument.detector
|
|
1171
|
+
row_pixel_size = float(detector.row_pixel_size)
|
|
1172
|
+
column_pixel_size = float(detector.column_pixel_size)
|
|
1222
1173
|
if num_tomo_stacks == 1:
|
|
1223
1174
|
# Convert the reconstructed tomography data from internal
|
|
1224
|
-
#
|
|
1225
|
-
#
|
|
1226
|
-
#
|
|
1227
|
-
#
|
|
1228
|
-
#
|
|
1229
|
-
#
|
|
1175
|
+
# coordinate frame row/-z,y,x with the origin on the
|
|
1176
|
+
# near-left-top corner to an z,y,x coordinate frame with
|
|
1177
|
+
# the origin on the par file x,z values, halfway in the
|
|
1178
|
+
# y-dimension.
|
|
1179
|
+
# Here x is to the right, y along the beam direction and
|
|
1180
|
+
# z upwards in the lab frame of reference
|
|
1230
1181
|
tomo_recon_stack = np.flip(tomo_recon_stacks[0], 0)
|
|
1231
1182
|
z_range = (z_dim_org-z_range[1], z_dim_org-z_range[0])
|
|
1232
1183
|
|
|
@@ -1234,19 +1185,17 @@ class Tomo:
|
|
|
1234
1185
|
x = column_pixel_size * (
|
|
1235
1186
|
np.linspace(
|
|
1236
1187
|
x_range[0], x_range[1], x_range[1]-x_range[0], False)
|
|
1237
|
-
- 0.5*
|
|
1238
|
-
+ 0.5)
|
|
1188
|
+
- 0.5*detector.columns + 0.5)
|
|
1239
1189
|
x = np.asarray(x + nxentry.reduced_data.x_translation[0])
|
|
1240
1190
|
y = np.asarray(
|
|
1241
1191
|
column_pixel_size * (
|
|
1242
1192
|
np.linspace(
|
|
1243
1193
|
y_range[0], y_range[1], y_range[1]-y_range[0], False)
|
|
1244
|
-
- 0.5*
|
|
1245
|
-
+ 0.5))
|
|
1194
|
+
- 0.5*detector.columns + 0.5))
|
|
1246
1195
|
z = row_pixel_size*(
|
|
1247
1196
|
np.linspace(
|
|
1248
1197
|
z_range[0], z_range[1], z_range[1]-z_range[0], False)
|
|
1249
|
-
+
|
|
1198
|
+
+ detector.rows
|
|
1250
1199
|
- int(nxentry.reduced_data.img_row_bounds[1])
|
|
1251
1200
|
+ 0.5)
|
|
1252
1201
|
z = np.asarray(z + nxentry.reduced_data.z_translation[0])
|
|
@@ -1262,7 +1211,7 @@ class Tomo:
|
|
|
1262
1211
|
quick_imshow(
|
|
1263
1212
|
tomo_recon_stack[:,:,x_index],
|
|
1264
1213
|
title=f'recon {res_title} x={x[x_index]:.4f}',
|
|
1265
|
-
origin='lower', extent=extent, path=self.
|
|
1214
|
+
origin='lower', extent=extent, path=self._outputdir,
|
|
1266
1215
|
save_fig=True, save_only=True)
|
|
1267
1216
|
y_index = y_slice-y_range[0]
|
|
1268
1217
|
extent = (
|
|
@@ -1273,7 +1222,7 @@ class Tomo:
|
|
|
1273
1222
|
quick_imshow(
|
|
1274
1223
|
tomo_recon_stack[:,y_index,:],
|
|
1275
1224
|
title=f'recon {res_title} y={y[y_index]:.4f}',
|
|
1276
|
-
origin='lower', extent=extent, path=self.
|
|
1225
|
+
origin='lower', extent=extent, path=self._outputdir,
|
|
1277
1226
|
save_fig=True, save_only=True)
|
|
1278
1227
|
z_index = z_slice-z_range[0]
|
|
1279
1228
|
extent = (
|
|
@@ -1284,7 +1233,7 @@ class Tomo:
|
|
|
1284
1233
|
quick_imshow(
|
|
1285
1234
|
tomo_recon_stack[z_index,:,:],
|
|
1286
1235
|
title=f'recon {res_title} z={z[z_index]:.4f}',
|
|
1287
|
-
origin='lower', extent=extent, path=self.
|
|
1236
|
+
origin='lower', extent=extent, path=self._outputdir,
|
|
1288
1237
|
save_fig=True, save_only=True)
|
|
1289
1238
|
else:
|
|
1290
1239
|
# Plot a few reconstructed image slices
|
|
@@ -1294,25 +1243,23 @@ class Tomo:
|
|
|
1294
1243
|
title = f'{basetitle} {res_title} xslice{x_slice}'
|
|
1295
1244
|
quick_imshow(
|
|
1296
1245
|
tomo_recon_stacks[i,:,:,x_slice-x_range[0]],
|
|
1297
|
-
title=title, path=self.
|
|
1298
|
-
|
|
1246
|
+
title=title, path=self._outputdir, save_fig=True,
|
|
1247
|
+
save_only=True)
|
|
1299
1248
|
title = f'{basetitle} {res_title} yslice{y_slice}'
|
|
1300
1249
|
quick_imshow(
|
|
1301
1250
|
tomo_recon_stacks[i,:,y_slice-y_range[0],:],
|
|
1302
|
-
title=title, path=self.
|
|
1303
|
-
|
|
1251
|
+
title=title, path=self._outputdir, save_fig=True,
|
|
1252
|
+
save_only=True)
|
|
1304
1253
|
title = f'{basetitle} {res_title} zslice{z_slice}'
|
|
1305
1254
|
quick_imshow(
|
|
1306
1255
|
tomo_recon_stacks[i,z_slice-z_range[0],:,:],
|
|
1307
|
-
title=title, path=self.
|
|
1308
|
-
|
|
1256
|
+
title=title, path=self._outputdir, save_fig=True,
|
|
1257
|
+
save_only=True)
|
|
1309
1258
|
|
|
1310
1259
|
# Add image reconstruction to reconstructed data NXprocess
|
|
1311
|
-
#
|
|
1312
|
-
#
|
|
1313
|
-
#
|
|
1314
|
-
nxprocess.data = NXdata()
|
|
1315
|
-
nxprocess.attrs['default'] = 'data'
|
|
1260
|
+
# reconstructed axis data order:
|
|
1261
|
+
# - for one stack: z,y,x
|
|
1262
|
+
# - for multiple stacks: row/-z,y,x
|
|
1316
1263
|
for k, v in center_info.items():
|
|
1317
1264
|
nxprocess[k] = v
|
|
1318
1265
|
if k == 'center_rows' or k == 'center_offsets':
|
|
@@ -1335,53 +1282,45 @@ class Tomo:
|
|
|
1335
1282
|
nxprocess.z_bounds.units = 'pixels'
|
|
1336
1283
|
nxprocess.z_bounds.attrs['long_name'] = \
|
|
1337
1284
|
'z range indices in reduced data frame of reference'
|
|
1338
|
-
nxprocess.data.attrs['signal'] = 'reconstructed_data'
|
|
1339
1285
|
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
|
|
1286
|
+
nxprocess.data = NXdata(
|
|
1287
|
+
NXfield(tomo_recon_stack, 'reconstructed_data'),
|
|
1288
|
+
(NXfield(
|
|
1289
|
+
z, 'z', attrs={'units': detector.row_pixel_size.units}),
|
|
1290
|
+
NXfield(
|
|
1291
|
+
y, 'y',
|
|
1292
|
+
attrs={'units': detector.column_pixel_size.units}),
|
|
1293
|
+
NXfield(
|
|
1294
|
+
x, 'x',
|
|
1295
|
+
attrs={'units': detector.column_pixel_size.units}),))
|
|
1354
1296
|
else:
|
|
1355
|
-
nxprocess.data
|
|
1297
|
+
nxprocess.data = NXdata(
|
|
1298
|
+
NXfield(tomo_recon_stacks, 'reconstructed_data'))
|
|
1356
1299
|
|
|
1357
1300
|
# Create a copy of the input Nexus object and remove reduced
|
|
1358
|
-
#
|
|
1301
|
+
# data
|
|
1359
1302
|
exclude_items = [
|
|
1360
1303
|
f'{nxentry.nxname}/reduced_data/data',
|
|
1361
1304
|
f'{nxentry.nxname}/data/reduced_data',
|
|
1362
1305
|
f'{nxentry.nxname}/data/rotation_angle',
|
|
1363
1306
|
]
|
|
1364
|
-
|
|
1307
|
+
nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
|
|
1365
1308
|
|
|
1366
1309
|
# 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'
|
|
1310
|
+
nxentry = nxroot[nxroot.default]
|
|
1311
|
+
nxentry.reconstructed_data = nxprocess
|
|
1312
|
+
if 'data' not in nxentry:
|
|
1313
|
+
nxentry.data = NXdata()
|
|
1314
|
+
nxentry.data.set_default()
|
|
1315
|
+
nxentry.data.makelink(nxprocess.data.reconstructed_data)
|
|
1375
1316
|
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')
|
|
1317
|
+
nxentry.data.attrs['axes'] = ['z', 'y', 'x']
|
|
1318
|
+
nxentry.data.makelink(nxprocess.data.x)
|
|
1319
|
+
nxentry.data.makelink(nxprocess.data.y)
|
|
1320
|
+
nxentry.data.makelink(nxprocess.data.z)
|
|
1321
|
+
nxentry.data.attrs['signal'] = 'reconstructed_data'
|
|
1383
1322
|
|
|
1384
|
-
return
|
|
1323
|
+
return nxroot
|
|
1385
1324
|
|
|
1386
1325
|
def combine_data(self, nxroot, tool_config):
|
|
1387
1326
|
"""Combine the reconstructed tomography stacks.
|
|
@@ -1399,7 +1338,7 @@ class Tomo:
|
|
|
1399
1338
|
# Third party modules
|
|
1400
1339
|
from nexusformat.nexus import (
|
|
1401
1340
|
NXdata,
|
|
1402
|
-
|
|
1341
|
+
NXfield,
|
|
1403
1342
|
NXprocess,
|
|
1404
1343
|
NXroot,
|
|
1405
1344
|
)
|
|
@@ -1407,18 +1346,17 @@ class Tomo:
|
|
|
1407
1346
|
self._logger.info('Combine the reconstructed tomography stacks')
|
|
1408
1347
|
|
|
1409
1348
|
if isinstance(nxroot, NXroot):
|
|
1410
|
-
nxentry = nxroot[nxroot.
|
|
1349
|
+
nxentry = nxroot[nxroot.default]
|
|
1411
1350
|
else:
|
|
1412
1351
|
raise ValueError(f'Invalid parameter nxroot ({nxroot})')
|
|
1413
1352
|
|
|
1414
1353
|
# Check if reconstructed image data is available
|
|
1415
|
-
if
|
|
1416
|
-
or 'reconstructed_data' not in nxentry.data):
|
|
1354
|
+
if 'reconstructed_data' not in nxentry:
|
|
1417
1355
|
raise KeyError(
|
|
1418
1356
|
f'Unable to find valid reconstructed image data in {nxentry}')
|
|
1419
1357
|
|
|
1420
1358
|
# Create an NXprocess to store combined image reconstruction
|
|
1421
|
-
#
|
|
1359
|
+
# (meta)data
|
|
1422
1360
|
nxprocess = NXprocess()
|
|
1423
1361
|
|
|
1424
1362
|
if nxentry.reconstructed_data.data.reconstructed_data.ndim == 3:
|
|
@@ -1431,105 +1369,106 @@ class Tomo:
|
|
|
1431
1369
|
return nxroot
|
|
1432
1370
|
|
|
1433
1371
|
# Get and combine the reconstructed stacks
|
|
1434
|
-
#
|
|
1372
|
+
# - reconstructed axis data order: stack,row/-z,y,x
|
|
1435
1373
|
# Note: Nexus can't follow a link if the data it points to is
|
|
1436
|
-
#
|
|
1437
|
-
#
|
|
1438
|
-
#
|
|
1439
|
-
#
|
|
1374
|
+
# too big. So get the data from the actual place, not from
|
|
1375
|
+
# nxentry.data
|
|
1376
|
+
# Also load one stack at a time to reduce risk of hitting Nexus
|
|
1377
|
+
# data access limit
|
|
1440
1378
|
t0 = time()
|
|
1441
1379
|
tomo_recon_combined = \
|
|
1442
1380
|
nxentry.reconstructed_data.data.reconstructed_data[0,:,:,:]
|
|
1381
|
+
# RV check this out more
|
|
1382
|
+
# tomo_recon_combined = np.concatenate(
|
|
1383
|
+
# [tomo_recon_combined]
|
|
1384
|
+
# + [nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
|
|
1385
|
+
# for i in range(1, num_tomo_stacks)])
|
|
1443
1386
|
tomo_recon_combined = np.concatenate(
|
|
1444
|
-
[
|
|
1445
|
-
|
|
1446
|
-
|
|
1387
|
+
[nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
|
|
1388
|
+
for i in range(num_tomo_stacks-1, 0, -1)]
|
|
1389
|
+
+ [tomo_recon_combined])
|
|
1447
1390
|
self._logger.info(
|
|
1448
1391
|
f'Combining the reconstructed stacks took {time()-t0:.2f} seconds')
|
|
1449
1392
|
tomo_shape = tomo_recon_combined.shape
|
|
1450
1393
|
|
|
1451
1394
|
# Resize the combined tomography data stacks
|
|
1452
|
-
#
|
|
1453
|
-
if self._interactive:
|
|
1395
|
+
# - combined axis data order: row/-z,y,x
|
|
1396
|
+
if self._interactive or self._save_figs:
|
|
1454
1397
|
x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
|
|
1455
1398
|
tomo_recon_combined, combine_data=True)
|
|
1456
1399
|
else:
|
|
1457
1400
|
x_bounds = tool_config.x_bounds
|
|
1458
1401
|
if x_bounds is None:
|
|
1459
1402
|
self._logger.warning(
|
|
1460
|
-
'x_bounds unspecified,
|
|
1403
|
+
'x_bounds unspecified, combine data for full x-range')
|
|
1461
1404
|
elif not is_int_pair(
|
|
1462
1405
|
x_bounds, ge=0, le=tomo_shape[2]):
|
|
1463
1406
|
raise ValueError(f'Invalid parameter x_bounds ({x_bounds})')
|
|
1464
1407
|
y_bounds = tool_config.y_bounds
|
|
1465
1408
|
if y_bounds is None:
|
|
1466
1409
|
self._logger.warning(
|
|
1467
|
-
'y_bounds unspecified,
|
|
1410
|
+
'y_bounds unspecified, combine data for full y-range')
|
|
1468
1411
|
elif not is_int_pair(
|
|
1469
1412
|
y_bounds, ge=0, le=tomo_shape[1]):
|
|
1470
1413
|
raise ValueError(f'Invalid parameter y_bounds ({y_bounds})')
|
|
1471
1414
|
z_bounds = tool_config.z_bounds
|
|
1472
1415
|
if z_bounds is None:
|
|
1473
1416
|
self._logger.warning(
|
|
1474
|
-
'z_bounds unspecified,
|
|
1417
|
+
'z_bounds unspecified, combine data for full z-range')
|
|
1475
1418
|
elif not is_int_pair(
|
|
1476
1419
|
z_bounds, ge=0, le=tomo_shape[0]):
|
|
1477
1420
|
raise ValueError(f'Invalid parameter z_bounds ({z_bounds})')
|
|
1478
1421
|
if x_bounds is None:
|
|
1479
1422
|
x_range = (0, tomo_shape[2])
|
|
1480
|
-
x_slice =
|
|
1423
|
+
x_slice = x_range[1]//2
|
|
1481
1424
|
else:
|
|
1482
1425
|
x_range = (min(x_bounds), max(x_bounds))
|
|
1483
|
-
x_slice =
|
|
1426
|
+
x_slice = (x_bounds[0]+x_bounds[1])//2
|
|
1484
1427
|
if y_bounds is None:
|
|
1485
1428
|
y_range = (0, tomo_shape[1])
|
|
1486
|
-
y_slice =
|
|
1429
|
+
y_slice = y_range[1]//2
|
|
1487
1430
|
else:
|
|
1488
1431
|
y_range = (min(y_bounds), max(y_bounds))
|
|
1489
|
-
y_slice =
|
|
1432
|
+
y_slice = (y_bounds[0]+y_bounds[1])//2
|
|
1490
1433
|
if z_bounds is None:
|
|
1491
1434
|
z_range = (0, tomo_shape[0])
|
|
1492
|
-
z_slice =
|
|
1435
|
+
z_slice = z_range[1]//2
|
|
1493
1436
|
else:
|
|
1494
1437
|
z_range = (min(z_bounds), max(z_bounds))
|
|
1495
|
-
z_slice =
|
|
1438
|
+
z_slice = (z_bounds[0]+z_bounds[1])//2
|
|
1496
1439
|
z_dim_org = tomo_shape[0]
|
|
1497
1440
|
tomo_recon_combined = tomo_recon_combined[
|
|
1498
1441
|
z_range[0]:z_range[1],y_range[0]:y_range[1],x_range[0]:x_range[1]]
|
|
1499
1442
|
|
|
1500
1443
|
# Convert the reconstructed tomography data from internal
|
|
1501
|
-
#
|
|
1502
|
-
#
|
|
1503
|
-
#
|
|
1504
|
-
#
|
|
1444
|
+
# coordinate frame row/-z,y,x with the origin on the
|
|
1445
|
+
# near-left-top corner to an z,y,x coordinate frame.
|
|
1446
|
+
# Here x is to the right, y along the beam direction and
|
|
1447
|
+
# z upwards in the lab frame of reference
|
|
1505
1448
|
tomo_recon_combined = np.flip(tomo_recon_combined, 0)
|
|
1506
1449
|
tomo_shape = tomo_recon_combined.shape
|
|
1507
1450
|
z_range = (z_dim_org-z_range[1], z_dim_org-z_range[0])
|
|
1508
1451
|
|
|
1509
1452
|
# Get coordinate axes
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
column_pixel_size = float(
|
|
1513
|
-
nxentry.instrument.detector.column_pixel_size)
|
|
1453
|
+
detector = nxentry.instrument.detector
|
|
1454
|
+
row_pixel_size = float(detector.row_pixel_size)
|
|
1455
|
+
column_pixel_size = float(detector.column_pixel_size)
|
|
1514
1456
|
x = column_pixel_size * (
|
|
1515
1457
|
np.linspace(x_range[0], x_range[1], x_range[1]-x_range[0], False)
|
|
1516
|
-
- 0.5*
|
|
1517
|
-
+ 0.5)
|
|
1458
|
+
- 0.5*detector.columns + 0.5)
|
|
1518
1459
|
if nxentry.reconstructed_data.get('x_bounds', None) is not None:
|
|
1519
1460
|
x += column_pixel_size*nxentry.reconstructed_data.x_bounds[0]
|
|
1520
1461
|
x = np.asarray(x + nxentry.reduced_data.x_translation[0])
|
|
1521
1462
|
y = column_pixel_size * (
|
|
1522
1463
|
np.linspace(y_range[0], y_range[1], y_range[1]-y_range[0], False)
|
|
1523
|
-
- 0.5*
|
|
1524
|
-
+ 0.5)
|
|
1464
|
+
- 0.5*detector.columns + 0.5)
|
|
1525
1465
|
if nxentry.reconstructed_data.get('y_bounds', None) is not None:
|
|
1526
1466
|
y += column_pixel_size*nxentry.reconstructed_data.y_bounds[0]
|
|
1527
1467
|
y = np.asarray(y)
|
|
1528
1468
|
z = row_pixel_size*(
|
|
1529
1469
|
np.linspace(z_range[0], z_range[1], z_range[1]-z_range[0], False)
|
|
1530
1470
|
- int(nxentry.reduced_data.img_row_bounds[0])
|
|
1531
|
-
+ 0.5*
|
|
1532
|
-
-0.5)
|
|
1471
|
+
+ 0.5*detector.rows - 0.5)
|
|
1533
1472
|
z = np.asarray(z + nxentry.reduced_data.z_translation[0])
|
|
1534
1473
|
|
|
1535
1474
|
# Plot a few combined image slices
|
|
@@ -1539,39 +1478,37 @@ class Tomo:
|
|
|
1539
1478
|
y[-1],
|
|
1540
1479
|
z[0],
|
|
1541
1480
|
z[-1])
|
|
1542
|
-
x_slice =
|
|
1481
|
+
x_slice = tomo_shape[2]//2
|
|
1543
1482
|
quick_imshow(
|
|
1544
1483
|
tomo_recon_combined[:,:,x_slice],
|
|
1545
1484
|
title=f'recon combined x={x[x_slice]:.4f}', origin='lower',
|
|
1546
|
-
extent=extent, path=self.
|
|
1485
|
+
extent=extent, path=self._outputdir, save_fig=True,
|
|
1547
1486
|
save_only=True)
|
|
1548
1487
|
extent = (
|
|
1549
1488
|
x[0],
|
|
1550
1489
|
x[-1],
|
|
1551
1490
|
z[0],
|
|
1552
1491
|
z[-1])
|
|
1553
|
-
y_slice =
|
|
1492
|
+
y_slice = tomo_shape[1]//2
|
|
1554
1493
|
quick_imshow(
|
|
1555
1494
|
tomo_recon_combined[:,y_slice,:],
|
|
1556
1495
|
title=f'recon combined y={y[y_slice]:.4f}', origin='lower',
|
|
1557
|
-
extent=extent, path=self.
|
|
1496
|
+
extent=extent, path=self._outputdir, save_fig=True,
|
|
1558
1497
|
save_only=True)
|
|
1559
1498
|
extent = (
|
|
1560
1499
|
x[0],
|
|
1561
1500
|
x[-1],
|
|
1562
1501
|
y[0],
|
|
1563
1502
|
y[-1])
|
|
1564
|
-
z_slice =
|
|
1503
|
+
z_slice = tomo_shape[0]//2
|
|
1565
1504
|
quick_imshow(
|
|
1566
1505
|
tomo_recon_combined[z_slice,:,:],
|
|
1567
1506
|
title=f'recon combined z={z[z_slice]:.4f}', origin='lower',
|
|
1568
|
-
extent=extent, path=self.
|
|
1507
|
+
extent=extent, path=self._outputdir, save_fig=True,
|
|
1569
1508
|
save_only=True)
|
|
1570
1509
|
|
|
1571
1510
|
# Add image reconstruction to reconstructed data NXprocess
|
|
1572
|
-
#
|
|
1573
|
-
nxprocess.data = NXdata()
|
|
1574
|
-
nxprocess.attrs['default'] = 'data'
|
|
1511
|
+
# - combined axis data order: z,y,x
|
|
1575
1512
|
if x_bounds is not None and x_bounds != (0, tomo_shape[2]):
|
|
1576
1513
|
nxprocess.x_bounds = x_bounds
|
|
1577
1514
|
nxprocess.x_bounds.units = 'pixels'
|
|
@@ -1587,48 +1524,36 @@ class Tomo:
|
|
|
1587
1524
|
nxprocess.z_bounds.units = 'pixels'
|
|
1588
1525
|
nxprocess.z_bounds.attrs['long_name'] = \
|
|
1589
1526
|
'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
|
|
1527
|
+
nxprocess.data = NXdata(
|
|
1528
|
+
NXfield(tomo_recon_combined, 'combined_data'),
|
|
1529
|
+
(NXfield(z, 'z', attrs={'units': detector.row_pixel_size.units}),
|
|
1530
|
+
NXfield(
|
|
1531
|
+
y, 'y', attrs={'units': detector.column_pixel_size.units}),
|
|
1532
|
+
NXfield(
|
|
1533
|
+
x, 'x', attrs={'units': detector.column_pixel_size.units}),))
|
|
1605
1534
|
|
|
1606
1535
|
# Create a copy of the input Nexus object and remove
|
|
1607
|
-
#
|
|
1536
|
+
# reconstructed data
|
|
1608
1537
|
exclude_items = [
|
|
1609
1538
|
f'{nxentry.nxname}/reconstructed_data/data',
|
|
1610
1539
|
f'{nxentry.nxname}/data/reconstructed_data',
|
|
1611
1540
|
]
|
|
1612
|
-
|
|
1541
|
+
nxroot = nxcopy(nxroot, exclude_nxpaths=exclude_items)
|
|
1613
1542
|
|
|
1614
1543
|
# 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
|
|
1544
|
+
nxentry = nxroot[nxroot.default]
|
|
1545
|
+
nxentry.combined_data = nxprocess
|
|
1546
|
+
if 'data' not in nxentry:
|
|
1547
|
+
nxentry.data = NXdata()
|
|
1548
|
+
nxentry.data.set_default()
|
|
1549
|
+
nxentry.data.makelink(nxprocess.data.combined_data)
|
|
1550
|
+
nxentry.data.attrs['axes'] = ['z', 'y', 'x']
|
|
1551
|
+
nxentry.data.makelink(nxprocess.data.x)
|
|
1552
|
+
nxentry.data.makelink(nxprocess.data.y)
|
|
1553
|
+
nxentry.data.makelink(nxprocess.data.z)
|
|
1554
|
+
nxentry.data.attrs['signal'] = 'combined_data'
|
|
1555
|
+
|
|
1556
|
+
return nxroot
|
|
1632
1557
|
|
|
1633
1558
|
def _gen_dark(self, nxentry, reduced_data, image_key):
|
|
1634
1559
|
"""Generate dark field."""
|
|
@@ -1639,8 +1564,7 @@ class Tomo:
|
|
|
1639
1564
|
field_indices = [
|
|
1640
1565
|
index for index, key in enumerate(image_key) if key == 2]
|
|
1641
1566
|
if field_indices:
|
|
1642
|
-
tdf_stack =
|
|
1643
|
-
nxentry.instrument.detector.data[field_indices,:,:])
|
|
1567
|
+
tdf_stack = nxentry.instrument.detector.data[field_indices,:,:]
|
|
1644
1568
|
else:
|
|
1645
1569
|
self._logger.warning('Dark field unavailable')
|
|
1646
1570
|
return reduced_data
|
|
@@ -1655,7 +1579,6 @@ class Tomo:
|
|
|
1655
1579
|
raise RuntimeError(f'Invalid tdf_stack shape ({tdf_stack.shape})')
|
|
1656
1580
|
|
|
1657
1581
|
# Remove dark field intensities above the cutoff
|
|
1658
|
-
# tdf_cutoff = None
|
|
1659
1582
|
tdf_cutoff = tdf.min() + 2 * (np.median(tdf)-tdf.min())
|
|
1660
1583
|
self._logger.debug(f'tdf_cutoff = {tdf_cutoff}')
|
|
1661
1584
|
if tdf_cutoff is not None:
|
|
@@ -1676,7 +1599,7 @@ class Tomo:
|
|
|
1676
1599
|
if self._save_figs:
|
|
1677
1600
|
quick_imshow(
|
|
1678
1601
|
tdf, title='Dark field', name='dark_field',
|
|
1679
|
-
path=self.
|
|
1602
|
+
path=self._outputdir, save_fig=True, save_only=True)
|
|
1680
1603
|
|
|
1681
1604
|
# Add dark field to reduced data NXprocess
|
|
1682
1605
|
reduced_data.data = NXdata()
|
|
@@ -1693,8 +1616,7 @@ class Tomo:
|
|
|
1693
1616
|
field_indices = [
|
|
1694
1617
|
index for index, key in enumerate(image_key) if key == 1]
|
|
1695
1618
|
if field_indices:
|
|
1696
|
-
tbf_stack =
|
|
1697
|
-
nxentry.instrument.detector.data[field_indices,:,:])
|
|
1619
|
+
tbf_stack = nxentry.instrument.detector.data[field_indices,:,:]
|
|
1698
1620
|
else:
|
|
1699
1621
|
raise ValueError('Bright field unavailable')
|
|
1700
1622
|
|
|
@@ -1727,7 +1649,7 @@ class Tomo:
|
|
|
1727
1649
|
if self._save_figs:
|
|
1728
1650
|
quick_imshow(
|
|
1729
1651
|
tbf, title='Bright field', name='bright_field',
|
|
1730
|
-
path=self.
|
|
1652
|
+
path=self._outputdir, save_fig=True, save_only=True)
|
|
1731
1653
|
|
|
1732
1654
|
# Add bright field to reduced data NXprocess
|
|
1733
1655
|
if 'data' not in reduced_data:
|
|
@@ -1754,48 +1676,74 @@ class Tomo:
|
|
|
1754
1676
|
if image_mask is None:
|
|
1755
1677
|
first_image_index = 0
|
|
1756
1678
|
else:
|
|
1757
|
-
image_mask = np.asarray(image_mask)
|
|
1758
1679
|
first_image_index = int(np.argmax(image_mask))
|
|
1759
1680
|
field_indices_all = [
|
|
1760
1681
|
index for index, key in enumerate(image_key) if key == 0]
|
|
1761
1682
|
if not field_indices_all:
|
|
1762
1683
|
raise ValueError('Tomography field(s) unavailable')
|
|
1763
|
-
z_translation_all =
|
|
1764
|
-
nxentry.sample.z_translation)[field_indices_all]
|
|
1684
|
+
z_translation_all = nxentry.sample.z_translation[field_indices_all]
|
|
1765
1685
|
z_translation_levels = sorted(list(set(z_translation_all)))
|
|
1766
1686
|
num_tomo_stacks = len(z_translation_levels)
|
|
1767
|
-
center_stack_index =
|
|
1687
|
+
center_stack_index = num_tomo_stacks//2
|
|
1768
1688
|
z_translation = z_translation_levels[center_stack_index]
|
|
1769
1689
|
try:
|
|
1770
1690
|
field_indices = [
|
|
1771
1691
|
field_indices_all[index]
|
|
1772
1692
|
for index, z in enumerate(z_translation_all)
|
|
1773
1693
|
if z == z_translation]
|
|
1774
|
-
first_image =
|
|
1775
|
-
field_indices[first_image_index]]
|
|
1694
|
+
first_image = nxentry.instrument.detector.data[
|
|
1695
|
+
field_indices[first_image_index]]
|
|
1776
1696
|
except:
|
|
1777
1697
|
raise RuntimeError('Unable to load the tomography images '
|
|
1778
1698
|
f'for stack {i}')
|
|
1779
1699
|
|
|
1780
1700
|
# Set initial image bounds or rotation calibration rows
|
|
1781
|
-
tbf =
|
|
1701
|
+
tbf = reduced_data.data.bright_field.nxdata
|
|
1782
1702
|
if (not isinstance(calibrate_center_rows, bool)
|
|
1783
1703
|
and is_int_pair(calibrate_center_rows)):
|
|
1784
1704
|
img_row_bounds = calibrate_center_rows
|
|
1785
1705
|
else:
|
|
1786
1706
|
if nxentry.instrument.source.attrs['station'] in ('id1a3', 'id3a'):
|
|
1707
|
+
# System modules
|
|
1708
|
+
from sys import float_info
|
|
1709
|
+
|
|
1710
|
+
# Third party modules
|
|
1711
|
+
from nexusformat.nexus import (
|
|
1712
|
+
NXdata,
|
|
1713
|
+
NXfield,
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
# Local modules
|
|
1717
|
+
from CHAP.utils.fit import FitProcessor
|
|
1718
|
+
|
|
1787
1719
|
pixel_size = float(nxentry.instrument.detector.row_pixel_size)
|
|
1788
1720
|
# Try to get a fit from the bright field
|
|
1789
1721
|
row_sum = np.sum(tbf, 1)
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1722
|
+
num = len(row_sum)
|
|
1723
|
+
fit = FitProcessor()
|
|
1724
|
+
model = {'model': 'rectangle',
|
|
1725
|
+
'parameters': [
|
|
1726
|
+
{'name': 'amplitude',
|
|
1727
|
+
'value': row_sum.max()-row_sum.min(),
|
|
1728
|
+
'min': 0.0},
|
|
1729
|
+
{'name': 'center1', 'value': 0.25*num,
|
|
1730
|
+
'min': 0.0, 'max': num},
|
|
1731
|
+
{'name': 'sigma1', 'value': num/7.0,
|
|
1732
|
+
'min': float_info.min},
|
|
1733
|
+
{'name': 'center2', 'value': 0.75*num,
|
|
1734
|
+
'min': 0.0, 'max': num},
|
|
1735
|
+
{'name': 'sigma2', 'value': num/7.0,
|
|
1736
|
+
'min': float_info.min}]}
|
|
1737
|
+
bounds_fit = fit.process(
|
|
1738
|
+
NXdata(NXfield(row_sum, 'y'),
|
|
1739
|
+
NXfield(np.array(range(num)), 'x')),
|
|
1740
|
+
{'models': [model], 'method': 'trf'})
|
|
1741
|
+
parameters = bounds_fit.best_values
|
|
1794
1742
|
row_low_fit = parameters.get('center1', None)
|
|
1795
1743
|
row_upp_fit = parameters.get('center2', None)
|
|
1796
1744
|
sig_low = parameters.get('sigma1', None)
|
|
1797
1745
|
sig_upp = parameters.get('sigma2', None)
|
|
1798
|
-
have_fit = (
|
|
1746
|
+
have_fit = (bounds_fit.success and row_low_fit is not None
|
|
1799
1747
|
and row_upp_fit is not None and sig_low is not None
|
|
1800
1748
|
and sig_upp is not None
|
|
1801
1749
|
and 0 <= row_low_fit < row_upp_fit <= row_sum.size
|
|
@@ -1829,12 +1777,12 @@ class Tomo:
|
|
|
1829
1777
|
self._logger.debug(f'num_row_min = {num_row_min}')
|
|
1830
1778
|
if have_fit:
|
|
1831
1779
|
# Center the default range relative to the fitted
|
|
1832
|
-
#
|
|
1833
|
-
row_low = int((row_low_fit+row_upp_fit-num_row_min)
|
|
1780
|
+
# window
|
|
1781
|
+
row_low = int((row_low_fit+row_upp_fit-num_row_min)/2)
|
|
1834
1782
|
row_upp = row_low+num_row_min
|
|
1835
1783
|
else:
|
|
1836
1784
|
# Center the default range
|
|
1837
|
-
row_low = int((tbf.shape[0]-num_row_min)
|
|
1785
|
+
row_low = int((tbf.shape[0]-num_row_min)/2)
|
|
1838
1786
|
row_upp = row_low+num_row_min
|
|
1839
1787
|
img_row_bounds = (row_low, row_upp)
|
|
1840
1788
|
if calibrate_center_rows:
|
|
@@ -1885,21 +1833,20 @@ class Tomo:
|
|
|
1885
1833
|
if self._save_figs:
|
|
1886
1834
|
if calibrate_center_rows:
|
|
1887
1835
|
fig.savefig(os_path.join(
|
|
1888
|
-
self.
|
|
1836
|
+
self._outputdir, 'rotation_calibration_rows.png'))
|
|
1889
1837
|
else:
|
|
1890
1838
|
fig.savefig(os_path.join(
|
|
1891
|
-
self.
|
|
1839
|
+
self._outputdir, 'detector_image_bounds.png'))
|
|
1892
1840
|
plt.close()
|
|
1893
1841
|
|
|
1894
1842
|
return img_row_bounds
|
|
1895
1843
|
|
|
1896
1844
|
def _gen_thetas(self, nxentry, image_key):
|
|
1897
1845
|
"""Get the rotation angles for the image stacks."""
|
|
1898
|
-
# Get the rotation angles
|
|
1846
|
+
# Get the rotation angles (in degrees)
|
|
1899
1847
|
field_indices_all = [
|
|
1900
1848
|
index for index, key in enumerate(image_key) if key == 0]
|
|
1901
|
-
z_translation_all =
|
|
1902
|
-
nxentry.sample.z_translation)[field_indices_all]
|
|
1849
|
+
z_translation_all = nxentry.sample.z_translation[field_indices_all]
|
|
1903
1850
|
z_translation_levels = sorted(list(set(z_translation_all)))
|
|
1904
1851
|
thetas = None
|
|
1905
1852
|
for i, z_translation in enumerate(z_translation_levels):
|
|
@@ -1907,22 +1854,20 @@ class Tomo:
|
|
|
1907
1854
|
field_indices_all[index]
|
|
1908
1855
|
for index, z in enumerate(z_translation_all)
|
|
1909
1856
|
if z == z_translation]
|
|
1910
|
-
sequence_numbers =
|
|
1911
|
-
nxentry.instrument.detector.sequence_number
|
|
1857
|
+
sequence_numbers = \
|
|
1858
|
+
nxentry.instrument.detector.sequence_number[field_indices]
|
|
1912
1859
|
assert (list(sequence_numbers)
|
|
1913
1860
|
== list(range((len(sequence_numbers)))))
|
|
1914
1861
|
if thetas is None:
|
|
1915
|
-
thetas =
|
|
1916
|
-
|
|
1917
|
-
field_indices][sequence_numbers]
|
|
1862
|
+
thetas = nxentry.sample.rotation_angle[
|
|
1863
|
+
field_indices][sequence_numbers]
|
|
1918
1864
|
else:
|
|
1919
1865
|
assert all(
|
|
1920
|
-
thetas[i] ==
|
|
1921
|
-
|
|
1922
|
-
field_indices[index]]
|
|
1866
|
+
thetas[i] == nxentry.sample.rotation_angle[
|
|
1867
|
+
field_indices[index]]
|
|
1923
1868
|
for i, index in enumerate(sequence_numbers))
|
|
1924
1869
|
|
|
1925
|
-
return thetas
|
|
1870
|
+
return np.asarray(thetas)
|
|
1926
1871
|
|
|
1927
1872
|
def _set_zoom_or_delta_theta(self, thetas, delta_theta=None):
|
|
1928
1873
|
"""
|
|
@@ -1949,7 +1894,7 @@ class Tomo:
|
|
|
1949
1894
|
if self._interactive:
|
|
1950
1895
|
if delta_theta is None:
|
|
1951
1896
|
delta_theta = thetas[1]-thetas[0]
|
|
1952
|
-
print(f'
|
|
1897
|
+
print(f'\nAvailable \u03b8 range: [{thetas[0]}, {thetas[-1]}]')
|
|
1953
1898
|
print(f'Current \u03b8 interval: {delta_theta}')
|
|
1954
1899
|
if input_yesno(
|
|
1955
1900
|
'Do you want to change the \u03b8 interval to reduce the '
|
|
@@ -1973,13 +1918,13 @@ class Tomo:
|
|
|
1973
1918
|
|
|
1974
1919
|
# Get dark field
|
|
1975
1920
|
if 'dark_field' in reduced_data.data:
|
|
1976
|
-
tdf =
|
|
1921
|
+
tdf = reduced_data.data.dark_field.nxdata
|
|
1977
1922
|
else:
|
|
1978
1923
|
self._logger.warning('Dark field unavailable')
|
|
1979
1924
|
tdf = None
|
|
1980
1925
|
|
|
1981
1926
|
# Get bright field
|
|
1982
|
-
tbf =
|
|
1927
|
+
tbf = reduced_data.data.bright_field.nxdata
|
|
1983
1928
|
tbf_shape = tbf.shape
|
|
1984
1929
|
|
|
1985
1930
|
# Subtract dark field
|
|
@@ -2018,26 +1963,26 @@ class Tomo:
|
|
|
2018
1963
|
img_column_bounds[0]:img_column_bounds[1]]
|
|
2019
1964
|
|
|
2020
1965
|
# Get thetas (in degrees)
|
|
2021
|
-
thetas =
|
|
1966
|
+
thetas = reduced_data.rotation_angle.nxdata
|
|
2022
1967
|
|
|
2023
1968
|
# Get or create image mask
|
|
2024
1969
|
image_mask = reduced_data.get('image_mask')
|
|
2025
1970
|
if image_mask is None:
|
|
2026
|
-
image_mask =
|
|
1971
|
+
image_mask = [True]*len(thetas)
|
|
2027
1972
|
else:
|
|
2028
|
-
image_mask =
|
|
1973
|
+
image_mask = list(image_mask)
|
|
2029
1974
|
|
|
2030
1975
|
# Get the tomography images
|
|
2031
1976
|
field_indices_all = [
|
|
2032
1977
|
index for index, key in enumerate(image_key) if key == 0]
|
|
2033
1978
|
if not field_indices_all:
|
|
2034
1979
|
raise ValueError('Tomography field(s) unavailable')
|
|
2035
|
-
z_translation_all =
|
|
2036
|
-
|
|
1980
|
+
z_translation_all = nxentry.sample.z_translation[
|
|
1981
|
+
field_indices_all]
|
|
2037
1982
|
z_translation_levels = sorted(list(set(z_translation_all)))
|
|
2038
1983
|
num_tomo_stacks = len(z_translation_levels)
|
|
2039
1984
|
if calibrate_center_rows:
|
|
2040
|
-
center_stack_index =
|
|
1985
|
+
center_stack_index = num_tomo_stacks//2
|
|
2041
1986
|
tomo_stacks = num_tomo_stacks*[np.array([])]
|
|
2042
1987
|
horizontal_shifts = []
|
|
2043
1988
|
vertical_shifts = []
|
|
@@ -2046,25 +1991,25 @@ class Tomo:
|
|
|
2046
1991
|
continue
|
|
2047
1992
|
try:
|
|
2048
1993
|
field_indices = [
|
|
2049
|
-
field_indices_all[
|
|
2050
|
-
for
|
|
1994
|
+
field_indices_all[i]
|
|
1995
|
+
for i, z in enumerate(z_translation_all)
|
|
2051
1996
|
if z == z_translation]
|
|
2052
|
-
field_indices_masked =
|
|
2053
|
-
|
|
2054
|
-
|
|
1997
|
+
field_indices_masked = [
|
|
1998
|
+
v for i, v in enumerate(field_indices) if image_mask[i]]
|
|
1999
|
+
horizontal_shift = list(
|
|
2000
|
+
set(nxentry.sample.x_translation[field_indices_masked]))
|
|
2055
2001
|
assert len(horizontal_shift) == 1
|
|
2056
2002
|
horizontal_shifts += horizontal_shift
|
|
2057
|
-
vertical_shift = list(
|
|
2058
|
-
nxentry.sample.z_translation
|
|
2003
|
+
vertical_shift = list(
|
|
2004
|
+
set(nxentry.sample.z_translation[field_indices_masked]))
|
|
2059
2005
|
assert len(vertical_shift) == 1
|
|
2060
2006
|
vertical_shifts += vertical_shift
|
|
2061
|
-
sequence_numbers =
|
|
2062
|
-
nxentry.instrument.detector.sequence_number
|
|
2063
|
-
field_indices]
|
|
2007
|
+
sequence_numbers = \
|
|
2008
|
+
nxentry.instrument.detector.sequence_number[field_indices]
|
|
2064
2009
|
assert (list(sequence_numbers)
|
|
2065
2010
|
== list(range((len(sequence_numbers)))))
|
|
2066
|
-
tomo_stack =
|
|
2067
|
-
|
|
2011
|
+
tomo_stack = nxentry.instrument.detector.data[
|
|
2012
|
+
field_indices_masked]
|
|
2068
2013
|
except:
|
|
2069
2014
|
raise RuntimeError('Unable to load the tomography images '
|
|
2070
2015
|
f'for stack {i}')
|
|
@@ -2147,7 +2092,7 @@ class Tomo:
|
|
|
2147
2092
|
name = f'reduced_data_stack_{i}_theta_{theta}'
|
|
2148
2093
|
quick_imshow(
|
|
2149
2094
|
tomo_stack[0,:,:], title=title, name=name,
|
|
2150
|
-
path=self.
|
|
2095
|
+
path=self._outputdir, save_fig=self._save_figs,
|
|
2151
2096
|
save_only=self._save_only, block=self._block)
|
|
2152
2097
|
zoom_perc = 100
|
|
2153
2098
|
if zoom_perc != 100:
|
|
@@ -2163,7 +2108,7 @@ class Tomo:
|
|
|
2163
2108
|
f'{round(thetas[0], 2)+0}'
|
|
2164
2109
|
quick_imshow(
|
|
2165
2110
|
tomo_stack[0,:,:], title=title,
|
|
2166
|
-
path=self.
|
|
2111
|
+
path=self._outputdir, save_fig=self._save_figs,
|
|
2167
2112
|
save_only=self._save_only, block=self._block)
|
|
2168
2113
|
del tomo_zoom_list
|
|
2169
2114
|
|
|
@@ -2179,11 +2124,11 @@ class Tomo:
|
|
|
2179
2124
|
reduced_tomo_stacks[i] = np.zeros(tomo_stack_shape)
|
|
2180
2125
|
|
|
2181
2126
|
# Add tomo field info to reduced data NXprocess
|
|
2182
|
-
reduced_data.x_translation =
|
|
2127
|
+
reduced_data.x_translation = horizontal_shifts
|
|
2183
2128
|
reduced_data.x_translation.units = 'mm'
|
|
2184
|
-
reduced_data.z_translation =
|
|
2129
|
+
reduced_data.z_translation = vertical_shifts
|
|
2185
2130
|
reduced_data.z_translation.units = 'mm'
|
|
2186
|
-
reduced_data.data.tomo_fields =
|
|
2131
|
+
reduced_data.data.tomo_fields = reduced_tomo_stacks
|
|
2187
2132
|
reduced_data.data.attrs['signal'] = 'tomo_fields'
|
|
2188
2133
|
|
|
2189
2134
|
if tdf is not None:
|
|
@@ -2193,22 +2138,35 @@ class Tomo:
|
|
|
2193
2138
|
return reduced_data
|
|
2194
2139
|
|
|
2195
2140
|
def _find_center_one_plane(
|
|
2196
|
-
self,
|
|
2197
|
-
path=None, num_core=1,
|
|
2198
|
-
|
|
2199
|
-
|
|
2141
|
+
self, tomo_stacks, stack_index, row, offset_row, thetas,
|
|
2142
|
+
eff_pixel_size, cross_sectional_dim, path=None, num_core=1,
|
|
2143
|
+
center_offset_min=-50, center_offset_max=50,
|
|
2144
|
+
center_search_range=None, gaussian_sigma=None, ring_width=None,
|
|
2145
|
+
prev_center_offset=None):
|
|
2146
|
+
"""
|
|
2147
|
+
Find center for a single tomography plane.
|
|
2148
|
+
|
|
2149
|
+
tomo_stacks data axes order: stack,theta,row,column
|
|
2150
|
+
thetas in radians
|
|
2151
|
+
"""
|
|
2200
2152
|
# Third party modules
|
|
2201
2153
|
import matplotlib.pyplot as plt
|
|
2202
|
-
from tomopy import
|
|
2154
|
+
from tomopy import (
|
|
2155
|
+
# find_center,
|
|
2156
|
+
find_center_vo,
|
|
2157
|
+
find_center_pc,
|
|
2158
|
+
)
|
|
2203
2159
|
|
|
2204
|
-
# sinogram index order: theta,column
|
|
2205
|
-
sinogram = np.asarray(sinogram)
|
|
2206
2160
|
if not gaussian_sigma:
|
|
2207
2161
|
gaussian_sigma = None
|
|
2208
2162
|
if not ring_width:
|
|
2209
2163
|
ring_width = None
|
|
2210
2164
|
|
|
2211
|
-
#
|
|
2165
|
+
# Get the sinogram for the selected plane
|
|
2166
|
+
sinogram = tomo_stacks[stack_index,:,offset_row,:]
|
|
2167
|
+
center_offset_range = sinogram.shape[1]/2
|
|
2168
|
+
|
|
2169
|
+
# Try Nghia Vo's method to find the center
|
|
2212
2170
|
t0 = time()
|
|
2213
2171
|
if center_offset_min is None:
|
|
2214
2172
|
center_offset_min = -50
|
|
@@ -2228,298 +2186,420 @@ class Tomo:
|
|
|
2228
2186
|
self._logger.info(
|
|
2229
2187
|
f'Finding center using Nghia Vo\'s method took {time()-t0:.2f} '
|
|
2230
2188
|
'seconds')
|
|
2231
|
-
center_offset_range = sinogram.shape[1]/2
|
|
2232
2189
|
center_offset_vo = float(tomo_center-center_offset_range)
|
|
2233
2190
|
self._logger.info(
|
|
2234
2191
|
f'Center at row {row} using Nghia Vo\'s method = '
|
|
2235
2192
|
f'{center_offset_vo:.2f}')
|
|
2236
2193
|
|
|
2237
|
-
|
|
2194
|
+
selected_center_offset = center_offset_vo
|
|
2238
2195
|
if self._interactive or self._save_figs:
|
|
2239
2196
|
|
|
2240
|
-
#
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
# Reconstruct the plane for the Nghia Vo's center offset
|
|
2197
|
+
# Try Guizar-Sicairos's phase correlation method to find
|
|
2198
|
+
# the center
|
|
2244
2199
|
t0 = time()
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2200
|
+
tomo_center = find_center_pc(
|
|
2201
|
+
tomo_stacks[stack_index,0,:,:],
|
|
2202
|
+
tomo_stacks[stack_index,-1,:,:])
|
|
2203
|
+
self._logger.info(
|
|
2204
|
+
'Finding center using Guizar-Sicairos\'s phase correlation '
|
|
2205
|
+
f'method took {time()-t0:.2f} seconds')
|
|
2206
|
+
center_offset_pc = float(tomo_center-center_offset_range)
|
|
2207
|
+
self._logger.info(
|
|
2208
|
+
f'Center at row {row} using Guizar-Sicairos\'s image entropy '
|
|
2209
|
+
f'method = {center_offset_pc:.2f}')
|
|
2210
|
+
|
|
2211
|
+
# Try Donath's image entropy method to find the center
|
|
2212
|
+
# Skip this method, it seems flawed somehow or I'm doing something wrong
|
|
2213
|
+
# t0 = time()
|
|
2214
|
+
# tomo_center = find_center(
|
|
2215
|
+
# tomo_stacks[stack_index,:,:,:], thetas,
|
|
2216
|
+
# ind=offset_row)
|
|
2217
|
+
# self._logger.info(
|
|
2218
|
+
# 'Finding center using Donath\'s image entropy method took '
|
|
2219
|
+
# f'{time()-t0:.2f} seconds')
|
|
2220
|
+
# center_offset_ie = float(tomo_center-center_offset_range)
|
|
2221
|
+
# self._logger.info(
|
|
2222
|
+
# f'Center at row {row} using Donath\'s image entropy method = '
|
|
2223
|
+
# f'{center_offset_ie:.2f}')
|
|
2224
|
+
|
|
2225
|
+
# Reconstruct the plane for the Nghia Vo's center
|
|
2226
|
+
t0 = time()
|
|
2227
|
+
center_offsets = [center_offset_vo]
|
|
2228
|
+
fig_titles = [f'Vo\'s method: center offset = '
|
|
2229
|
+
f'{center_offset_vo:.2f}']
|
|
2230
|
+
recon_planes = [self._reconstruct_planes(
|
|
2231
|
+
sinogram, center_offset_vo, thetas, num_core=num_core,
|
|
2232
|
+
gaussian_sigma=gaussian_sigma, ring_width=ring_width)]
|
|
2249
2233
|
self._logger.info(
|
|
2250
2234
|
f'Reconstructing row {row} with center at '
|
|
2251
2235
|
f'{center_offset_vo} took {time()-t0:.2f} seconds')
|
|
2252
2236
|
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2237
|
+
# Reconstruct the plane for the Guizar-Sicairos's center
|
|
2238
|
+
t0 = time()
|
|
2239
|
+
center_offsets.append(center_offset_pc)
|
|
2240
|
+
fig_titles.append(f'Guizar-Sicairos\'s method: center offset = '
|
|
2241
|
+
f'{center_offset_pc:.2f}')
|
|
2242
|
+
recon_planes.append(self._reconstruct_planes(
|
|
2243
|
+
sinogram, center_offset_pc, thetas, num_core=num_core,
|
|
2244
|
+
gaussian_sigma=gaussian_sigma, ring_width=ring_width))
|
|
2245
|
+
self._logger.info(
|
|
2246
|
+
f'Reconstructing row {row} with center at '
|
|
2247
|
+
f'{center_offset_pc} took {time()-t0:.2f} seconds')
|
|
2248
|
+
|
|
2249
|
+
# Reconstruct the plane for the Donath's center
|
|
2250
|
+
# t0 = time()
|
|
2251
|
+
# center_offsets.append(center_offset_ie)
|
|
2252
|
+
# fig_titles.append(f'Donath\'s method: center offset = '
|
|
2253
|
+
# f'{center_offset_ie:.2f}')
|
|
2254
|
+
# recon_planes.append(self._reconstruct_planes(
|
|
2255
|
+
# sinogram, center_offset_ie, thetas, num_core=num_core,
|
|
2256
|
+
# gaussian_sigma=gaussian_sigma, ring_width=ring_width))
|
|
2257
|
+
# self._logger.info(
|
|
2258
|
+
# f'Reconstructing row {row} with center at '
|
|
2259
|
+
# f'{center_offset_ie} took {time()-t0:.2f} seconds')
|
|
2260
|
+
|
|
2261
|
+
# Reconstruct the plane at the previous row's center
|
|
2262
|
+
if (prev_center_offset is not None
|
|
2263
|
+
and prev_center_offset not in center_offsets):
|
|
2271
2264
|
t0 = time()
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2265
|
+
center_offsets.append(prev_center_offset)
|
|
2266
|
+
fig_titles.append(f'Previous row\'s: center offset = '
|
|
2267
|
+
f'{prev_center_offset:.2f}')
|
|
2268
|
+
recon_planes.append(self._reconstruct_planes(
|
|
2269
|
+
sinogram, prev_center_offset, thetas, num_core=num_core,
|
|
2270
|
+
gaussian_sigma=gaussian_sigma, ring_width=ring_width))
|
|
2276
2271
|
self._logger.info(
|
|
2277
2272
|
f'Reconstructing row {row} with center at '
|
|
2278
2273
|
f'{prev_center_offset} took {time()-t0:.2f} seconds')
|
|
2279
2274
|
|
|
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()
|
|
2275
|
+
# t0 = time()
|
|
2276
|
+
# recon_edges = []
|
|
2277
|
+
# for recon_plane in recon_planes:
|
|
2278
|
+
# recon_edges.append(self._get_edges_one_plane(recon_plane))
|
|
2279
|
+
# print(f'\nGetting edges for row {row} with centers at '
|
|
2280
|
+
# f'{center_offsets} took {time()-t0:.2f} seconds\n')
|
|
2294
2281
|
|
|
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
|
|
2282
|
+
# Select the best center
|
|
2283
|
+
fig, accept, selected_center_offset = \
|
|
2284
|
+
self._select_center_offset(
|
|
2285
|
+
recon_planes, row, center_offsets, default_offset_index=0,
|
|
2286
|
+
fig_titles=fig_titles, search_button=False,
|
|
2287
|
+
include_all_bad=True)
|
|
2321
2288
|
|
|
2322
|
-
#
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2289
|
+
# Plot results
|
|
2290
|
+
if self._save_figs:
|
|
2291
|
+
fig.savefig(
|
|
2292
|
+
os_path.join(
|
|
2293
|
+
self._outputdir,
|
|
2294
|
+
f'recon_row_{row}_default_centers.png'))
|
|
2295
|
+
plt.close()
|
|
2296
|
+
|
|
2297
|
+
# Create reconstructions for a specified search range
|
|
2298
|
+
if self._interactive:
|
|
2299
|
+
if (center_search_range is None
|
|
2300
|
+
and input_yesno('\nDo you want to reconstruct images '
|
|
2301
|
+
'for a range of rotation centers', 'n')):
|
|
2302
|
+
center_search_range = input_num_list(
|
|
2303
|
+
'Enter up to 3 numbers (start, end, step), '
|
|
2304
|
+
'(range, step), or range', remove_duplicates=False,
|
|
2305
|
+
sort=False)
|
|
2306
|
+
if center_search_range is not None:
|
|
2307
|
+
if len(center_search_range) != 3:
|
|
2308
|
+
search_range = center_search_range[0]
|
|
2309
|
+
if len(center_search_range) == 1:
|
|
2310
|
+
step = search_range
|
|
2311
|
+
else:
|
|
2312
|
+
step = center_search_range[1]
|
|
2341
2313
|
if selected_center_offset == 'all bad':
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
selected_center_offset-step_size,
|
|
2345
|
-
selected_center_offset+step_size)
|
|
2314
|
+
center_search_range = [
|
|
2315
|
+
- search_range/2, search_range/2, step]
|
|
2346
2316
|
else:
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
selected_center_offset
|
|
2350
|
-
|
|
2351
|
-
|
|
2317
|
+
center_search_range = [
|
|
2318
|
+
selected_center_offset - search_range/2,
|
|
2319
|
+
selected_center_offset + search_range/2,
|
|
2320
|
+
step]
|
|
2321
|
+
center_search_range[1] += 1 # Make upper bound inclusive
|
|
2322
|
+
search_center_offsets = list(np.arange(*center_search_range))
|
|
2323
|
+
search_recon_planes = self._reconstruct_planes(
|
|
2324
|
+
sinogram, search_center_offsets, thetas, num_core=num_core,
|
|
2325
|
+
gaussian_sigma=gaussian_sigma, ring_width=ring_width)
|
|
2326
|
+
for i, center in enumerate(search_center_offsets):
|
|
2327
|
+
title = f'Reconstruction for row {row}, center offset: ' \
|
|
2328
|
+
f'{center:.2f}'
|
|
2329
|
+
name = f'recon_row_{row}_center_{center:.2f}.png'
|
|
2330
|
+
if self._interactive:
|
|
2331
|
+
save_only = False
|
|
2332
|
+
block = True
|
|
2333
|
+
else:
|
|
2334
|
+
save_only = True
|
|
2335
|
+
block = False
|
|
2336
|
+
quick_imshow(
|
|
2337
|
+
search_recon_planes[i], title=title, row_label='y',
|
|
2338
|
+
column_label='x', path=self._outputdir, name=name,
|
|
2339
|
+
save_only=save_only, save_fig=True, block=block)
|
|
2340
|
+
center_offsets.append(center)
|
|
2341
|
+
recon_planes.append(search_recon_planes[i])
|
|
2342
|
+
|
|
2343
|
+
# Perform an interactive center finding search
|
|
2344
|
+
calibrate_interactively = False
|
|
2345
|
+
if self._interactive:
|
|
2346
|
+
if selected_center_offset == 'all bad':
|
|
2347
|
+
calibrate_interactively = input_yesno(
|
|
2348
|
+
'\nDo you want to perform an interactive search to '
|
|
2349
|
+
'calibrate the rotation center (y/n)?', 'n')
|
|
2350
|
+
else:
|
|
2351
|
+
calibrate_interactively = input_yesno(
|
|
2352
|
+
'\nDo you want to perform an interactive search to '
|
|
2353
|
+
'calibrate the rotation center around the selected value '
|
|
2354
|
+
f'of {selected_center_offset} (y/n)?', 'n')
|
|
2355
|
+
if calibrate_interactively:
|
|
2356
|
+
include_all_bad = True
|
|
2357
|
+
low = None
|
|
2358
|
+
upp = None
|
|
2359
|
+
if selected_center_offset == 'all bad':
|
|
2360
|
+
selected_center_offset = None
|
|
2361
|
+
selected_center_offset = input_num(
|
|
2362
|
+
'\nEnter the initial center offset in the center calibration '
|
|
2363
|
+
'search', ge=-center_offset_range, le=center_offset_range,
|
|
2364
|
+
default=selected_center_offset)
|
|
2365
|
+
max_step_size = min(
|
|
2366
|
+
center_offset_range+selected_center_offset,
|
|
2367
|
+
center_offset_range-selected_center_offset-1)
|
|
2368
|
+
max_step_size = 1 << int(np.log2(max_step_size))-1
|
|
2369
|
+
step_size = input_int(
|
|
2370
|
+
'\nEnter the intial step size in the center calibration '
|
|
2371
|
+
'search (will be truncated to the nearest lower power of 2)',
|
|
2372
|
+
ge=2, le=max_step_size, default=4)
|
|
2373
|
+
step_size = 1 << int(np.log2(step_size))
|
|
2374
|
+
selected_center_offset_prev = round(selected_center_offset)
|
|
2375
|
+
while step_size:
|
|
2376
|
+
preselected_offsets = (
|
|
2377
|
+
selected_center_offset_prev-step_size,
|
|
2378
|
+
selected_center_offset_prev,
|
|
2379
|
+
selected_center_offset_prev+step_size)
|
|
2352
2380
|
indices = []
|
|
2353
2381
|
for i, preselected_offset in enumerate(preselected_offsets):
|
|
2354
2382
|
if preselected_offset in center_offsets:
|
|
2355
2383
|
indices.append(
|
|
2356
2384
|
center_offsets.index(preselected_offset))
|
|
2357
2385
|
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
2386
|
indices.append(len(center_offsets))
|
|
2363
2387
|
center_offsets.append(preselected_offset)
|
|
2364
|
-
|
|
2365
|
-
|
|
2388
|
+
recon_planes.append(self._reconstruct_planes(
|
|
2389
|
+
sinogram, preselected_offset, thetas,
|
|
2390
|
+
num_core=num_core, gaussian_sigma=gaussian_sigma,
|
|
2391
|
+
ring_width=ring_width))
|
|
2366
2392
|
fig, accept, selected_center_offset = \
|
|
2367
2393
|
self._select_center_offset(
|
|
2368
|
-
[
|
|
2369
|
-
row, preselected_offsets,
|
|
2394
|
+
[recon_planes[i] for i in indices],
|
|
2395
|
+
row, preselected_offsets, default_offset_index=1,
|
|
2370
2396
|
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
2397
|
# Plot results
|
|
2387
2398
|
if self._save_figs:
|
|
2388
2399
|
fig.savefig(
|
|
2389
2400
|
os_path.join(
|
|
2390
|
-
self.
|
|
2391
|
-
f'
|
|
2401
|
+
self._outputdir,
|
|
2402
|
+
f'recon_row_{row}_center_range_'
|
|
2403
|
+
f'{min(preselected_offsets)}_'\
|
|
2392
2404
|
f'{max(preselected_offsets)}.png'))
|
|
2393
2405
|
plt.close()
|
|
2394
|
-
|
|
2395
|
-
|
|
2406
|
+
if accept and input_yesno(
|
|
2407
|
+
f'Accept center offset {selected_center_offset} '
|
|
2408
|
+
f'for row {row}? (y/n)', 'y'):
|
|
2409
|
+
break
|
|
2410
|
+
if selected_center_offset == 'all bad':
|
|
2411
|
+
step_size *=2
|
|
2412
|
+
else:
|
|
2413
|
+
if selected_center_offset == preselected_offsets[0]:
|
|
2414
|
+
upp = preselected_offsets[1]
|
|
2415
|
+
elif selected_center_offset == preselected_offsets[1]:
|
|
2416
|
+
low = preselected_offsets[0]
|
|
2417
|
+
upp = preselected_offsets[2]
|
|
2418
|
+
else:
|
|
2419
|
+
low = preselected_offsets[1]
|
|
2420
|
+
if None in (low, upp):
|
|
2421
|
+
step_size *= 2
|
|
2422
|
+
else:
|
|
2423
|
+
step_size = step_size//2
|
|
2424
|
+
include_all_bad = False
|
|
2425
|
+
selected_center_offset_prev = round(selected_center_offset)
|
|
2426
|
+
if step_size > max_step_size:
|
|
2427
|
+
self._logger.warning(
|
|
2428
|
+
'Exceeding maximum step size of {max_step_size}')
|
|
2429
|
+
step_size = max_step_size
|
|
2396
2430
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2431
|
+
# Collect info for the currently selected center
|
|
2432
|
+
recon_planes = [recon_planes[
|
|
2433
|
+
center_offsets.index(selected_center_offset)]]
|
|
2434
|
+
center_offsets = [selected_center_offset]
|
|
2435
|
+
fig_titles = [f'Reconstruction for center offset = '
|
|
2436
|
+
f'{selected_center_offset:.2f}']
|
|
2437
|
+
|
|
2438
|
+
# Try Nghia Vo's method with the selected center
|
|
2439
|
+
step_size = min(step_size, 10)
|
|
2440
|
+
center_offset_min = selected_center_offset-step_size
|
|
2441
|
+
center_offset_max = selected_center_offset+step_size
|
|
2442
|
+
if num_core > NUM_CORE_TOMOPY_LIMIT:
|
|
2443
|
+
self._logger.debug(
|
|
2444
|
+
f'Running find_center_vo on {NUM_CORE_TOMOPY_LIMIT} '
|
|
2445
|
+
'cores ...')
|
|
2446
|
+
tomo_center = find_center_vo(
|
|
2447
|
+
sinogram, ncore=NUM_CORE_TOMOPY_LIMIT,
|
|
2448
|
+
smin=center_offset_min, smax=center_offset_max)
|
|
2449
|
+
else:
|
|
2450
|
+
tomo_center = find_center_vo(
|
|
2451
|
+
sinogram, ncore=num_core, smin=center_offset_min,
|
|
2452
|
+
smax=center_offset_max)
|
|
2453
|
+
center_offset_vo = float(tomo_center-center_offset_range)
|
|
2454
|
+
self._logger.info(
|
|
2455
|
+
f'Center at row {row} using Nghia Vo\'s method = '
|
|
2456
|
+
f'{center_offset_vo:.2f}')
|
|
2457
|
+
|
|
2458
|
+
# Reconstruct the plane for the Nghia Vo's center
|
|
2459
|
+
center_offsets.append(center_offset_vo)
|
|
2460
|
+
fig_titles.append(f'Vo\'s method: center offset = '
|
|
2461
|
+
f'{center_offset_vo:.2f}')
|
|
2462
|
+
recon_planes.append(self._reconstruct_planes(
|
|
2463
|
+
sinogram, center_offset_vo, thetas, num_core=num_core,
|
|
2464
|
+
gaussian_sigma=gaussian_sigma, ring_width=ring_width))
|
|
2465
|
+
|
|
2466
|
+
# Select the best center
|
|
2467
|
+
fig, accept, selected_center_offset = \
|
|
2468
|
+
self._select_center_offset(
|
|
2469
|
+
recon_planes, row, center_offsets, default_offset_index=0,
|
|
2470
|
+
fig_titles=fig_titles, search_button=False)
|
|
2402
2471
|
|
|
2403
|
-
|
|
2472
|
+
# Plot results
|
|
2473
|
+
if self._save_figs:
|
|
2474
|
+
fig.savefig(
|
|
2475
|
+
os_path.join(
|
|
2476
|
+
self._outputdir,
|
|
2477
|
+
f'recon_row_{row}_center_'
|
|
2478
|
+
f'{selected_center_offset:.2f}.png'))
|
|
2479
|
+
plt.close()
|
|
2480
|
+
|
|
2481
|
+
del recon_planes
|
|
2482
|
+
|
|
2483
|
+
del sinogram
|
|
2404
2484
|
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2485
|
+
# Return the center location
|
|
2486
|
+
if self._interactive:
|
|
2487
|
+
if selected_center_offset == 'all bad':
|
|
2488
|
+
print('\nUnable to successfully calibrate center axis')
|
|
2489
|
+
selected_center_offset = input_num(
|
|
2490
|
+
'Enter the center offset for row {row}',
|
|
2491
|
+
ge=-center_offset_range, le=center_offset_range)
|
|
2492
|
+
return float(selected_center_offset)
|
|
2493
|
+
return float(center_offset_vo)
|
|
2494
|
+
|
|
2495
|
+
def _reconstruct_planes(
|
|
2496
|
+
self, tomo_planes, center_offset, thetas, num_core=1,
|
|
2408
2497
|
gaussian_sigma=None, ring_width=None):
|
|
2409
|
-
"""Invert the sinogram for a single tomography
|
|
2498
|
+
"""Invert the sinogram for a single or multiple tomography
|
|
2499
|
+
planes using tomopy's recon routine."""
|
|
2410
2500
|
# Third party modules
|
|
2411
2501
|
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')
|
|
2502
|
+
from tomopy import (
|
|
2503
|
+
misc,
|
|
2504
|
+
recon,
|
|
2505
|
+
)
|
|
2442
2506
|
|
|
2443
|
-
#
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2507
|
+
# Reconstruct the planes
|
|
2508
|
+
# tomo_planes axis data order: (row,)theta,column
|
|
2509
|
+
# thetas in radians
|
|
2510
|
+
if isinstance(center_offset, (int, float)):
|
|
2511
|
+
tomo_planes = np.expand_dims(tomo_planes, 0)
|
|
2512
|
+
center_offset = center_offset + tomo_planes.shape[2]/2
|
|
2513
|
+
elif is_num_series(center_offset):
|
|
2514
|
+
tomo_planes = np.array([tomo_planes]*len(center_offset))
|
|
2515
|
+
center_offset = np.asarray(center_offset) + tomo_planes.shape[2]/2
|
|
2516
|
+
else:
|
|
2517
|
+
raise ValueError(
|
|
2518
|
+
f'Invalid parameter center_offset ({center_offset})')
|
|
2519
|
+
recon_planes = recon(
|
|
2520
|
+
tomo_planes, thetas, center=center_offset, sinogram_order=True,
|
|
2521
|
+
algorithm='gridrec', ncore=num_core)
|
|
2448
2522
|
|
|
2449
2523
|
# Performing Gaussian filtering and removing ring artifacts
|
|
2450
2524
|
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
|
|
2525
|
+
recon_planes = gaussian_filter(
|
|
2526
|
+
recon_planes, gaussian_sigma, mode='nearest')
|
|
2455
2527
|
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
|
-
|
|
2528
|
+
recon_planes = misc.corr.remove_ring(
|
|
2529
|
+
recon_planes, rwidth=ring_width, ncore=num_core)
|
|
2530
|
+
|
|
2531
|
+
# Apply a circular mask
|
|
2532
|
+
recon_planes = misc.corr.circ_mask(recon_planes, axis=0)
|
|
2533
|
+
|
|
2534
|
+
return np.squeeze(recon_planes)
|
|
2535
|
+
|
|
2536
|
+
# def _get_edges_one_plane(self, recon_plane):
|
|
2537
|
+
# """
|
|
2538
|
+
# Create an "edges plot" image for a single reconstructed
|
|
2539
|
+
# tomography data plane.
|
|
2540
|
+
# """
|
|
2541
|
+
# # Third party modules
|
|
2542
|
+
# from skimage.restoration import denoise_tv_chambolle
|
|
2543
|
+
#
|
|
2544
|
+
# vis_parameters = None # RV self._config.get('vis_parameters')
|
|
2545
|
+
# if vis_parameters is None:
|
|
2546
|
+
# weight = 0.1
|
|
2547
|
+
# else:
|
|
2548
|
+
# weight = vis_parameters.get('denoise_weight', 0.1)
|
|
2549
|
+
# if not is_num(weight, ge=0.):
|
|
2550
|
+
# self._logger.warning(
|
|
2551
|
+
# f'Invalid weight ({weight}) in _get_edges_one_plane, '
|
|
2552
|
+
# 'set to a default of 0.1')
|
|
2553
|
+
# weight = 0.1
|
|
2554
|
+
# return denoise_tv_chambolle(recon_plane, weight=weight)
|
|
2480
2555
|
|
|
2481
2556
|
def _select_center_offset(
|
|
2482
|
-
self,
|
|
2557
|
+
self, recon_planes, row, preselected_offsets,
|
|
2558
|
+
default_offset_index=0, fig_titles=None, search_button=True,
|
|
2483
2559
|
include_all_bad=False):
|
|
2484
|
-
"""Select a center offset value from
|
|
2560
|
+
"""Select a center offset value from reconstructed images
|
|
2485
2561
|
for a single reconstructed tomography data plane."""
|
|
2486
2562
|
# Third party modules
|
|
2487
2563
|
import matplotlib.pyplot as plt
|
|
2488
2564
|
from matplotlib.widgets import RadioButtons, Button
|
|
2489
|
-
from matplotlib.widgets import Button
|
|
2490
2565
|
|
|
2491
2566
|
def select_offset(offset):
|
|
2492
2567
|
"""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()
|
|
2568
|
+
pass
|
|
2501
2569
|
|
|
2502
|
-
def
|
|
2503
|
-
"""Callback function for the "
|
|
2504
|
-
|
|
2570
|
+
def search(event):
|
|
2571
|
+
"""Callback function for the "Search" button."""
|
|
2572
|
+
if num_plots == 1:
|
|
2573
|
+
selected_offset.append(
|
|
2574
|
+
(False, preselected_offsets[default_offset_index]))
|
|
2575
|
+
else:
|
|
2576
|
+
offset = radio_btn.value_selected
|
|
2577
|
+
if offset in ('both bad', 'all bad'):
|
|
2578
|
+
selected_offset.append((False, 'all bad'))
|
|
2579
|
+
else:
|
|
2580
|
+
selected_offset.append((False, float(offset)))
|
|
2505
2581
|
plt.close()
|
|
2506
2582
|
|
|
2507
2583
|
def accept(event):
|
|
2508
2584
|
"""Callback function for the "Accept" button."""
|
|
2509
2585
|
if num_plots == 1:
|
|
2510
|
-
selected_offset.append((True, preselected_offsets[0]))
|
|
2511
|
-
else:
|
|
2512
2586
|
selected_offset.append(
|
|
2513
|
-
(
|
|
2514
|
-
|
|
2515
|
-
|
|
2587
|
+
(True, preselected_offsets[default_offset_index]))
|
|
2588
|
+
else:
|
|
2589
|
+
offset = radio_btn.value_selected
|
|
2590
|
+
if offset in ('both bad', 'all bad'):
|
|
2591
|
+
selected_offset.append((False, 'all bad'))
|
|
2592
|
+
else:
|
|
2593
|
+
selected_offset.append((True, float(offset)))
|
|
2516
2594
|
plt.close()
|
|
2517
2595
|
|
|
2518
|
-
if not isinstance(
|
|
2519
|
-
|
|
2596
|
+
if not isinstance(recon_planes, (tuple, list)):
|
|
2597
|
+
recon_planes = [recon_planes]
|
|
2520
2598
|
if not isinstance(preselected_offsets, (tuple, list)):
|
|
2521
2599
|
preselected_offsets = [preselected_offsets]
|
|
2522
|
-
assert len(
|
|
2600
|
+
assert len(recon_planes) == len(preselected_offsets)
|
|
2601
|
+
if fig_titles is not None:
|
|
2602
|
+
assert len(fig_titles) == len(preselected_offsets)
|
|
2523
2603
|
|
|
2524
2604
|
selected_offset = []
|
|
2525
2605
|
|
|
@@ -2531,68 +2611,58 @@ class Tomo:
|
|
|
2531
2611
|
'horizontalalignment': 'center',
|
|
2532
2612
|
'verticalalignment': 'bottom'}
|
|
2533
2613
|
|
|
2534
|
-
num_plots = len(
|
|
2614
|
+
num_plots = len(recon_planes)
|
|
2535
2615
|
if num_plots == 1:
|
|
2536
2616
|
fig, axs = plt.subplots(figsize=(11, 8.5))
|
|
2537
2617
|
axs = [axs]
|
|
2538
|
-
vmax = np.max(
|
|
2618
|
+
vmax = np.max(recon_planes[0][:,:])
|
|
2539
2619
|
else:
|
|
2540
2620
|
fig, axs = plt.subplots(ncols=num_plots, figsize=(17, 8.5))
|
|
2541
2621
|
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
|
-
|
|
2622
|
+
vmax = np.max(recon_planes[1][:,:])
|
|
2623
|
+
for i, (ax, recon_plane, preselected_offset) in enumerate(zip(
|
|
2624
|
+
axs, recon_planes, preselected_offsets)):
|
|
2625
|
+
ax.imshow(recon_plane, vmin=-vmax, vmax=vmax, cmap='gray')
|
|
2626
|
+
if fig_titles is None:
|
|
2627
|
+
if num_plots == 1:
|
|
2628
|
+
ax.set_title(
|
|
2629
|
+
f'Reconstruction for row {row}, center offset: ' \
|
|
2630
|
+
f'{preselected_offset:.2f}', fontsize='x-large')
|
|
2631
|
+
else:
|
|
2632
|
+
ax.set_title(
|
|
2633
|
+
f'Center offset: {preselected_offset}',
|
|
2634
|
+
fontsize='x-large')
|
|
2554
2635
|
ax.set_xlabel('x', fontsize='x-large')
|
|
2555
2636
|
if not i:
|
|
2556
2637
|
ax.set_ylabel('y', fontsize='x-large')
|
|
2638
|
+
if fig_titles is not None:
|
|
2639
|
+
for (ax, fig_title) in zip(axs, fig_titles):
|
|
2640
|
+
ax.set_title(fig_title, fontsize='x-large')
|
|
2557
2641
|
|
|
2642
|
+
fig_title = plt.figtext(
|
|
2643
|
+
*title_pos, f'Reconstruction for row {row}', **title_props)
|
|
2558
2644
|
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
2645
|
fig_subtitle = plt.figtext(
|
|
2572
2646
|
*subtitle_pos,
|
|
2573
|
-
'Press "Accept" to accept this value or "Reject"
|
|
2574
|
-
'center calibration search',
|
|
2647
|
+
'Press "Accept" to accept this value or "Reject" if not',
|
|
2575
2648
|
**subtitle_props)
|
|
2576
2649
|
else:
|
|
2577
|
-
if
|
|
2578
|
-
|
|
2579
|
-
*
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
**title_props)
|
|
2650
|
+
if search_button:
|
|
2651
|
+
fig_subtitle = plt.figtext(
|
|
2652
|
+
*subtitle_pos,
|
|
2653
|
+
'Select the best offset and press "Accept" to accept or '
|
|
2654
|
+
'"Search" to continue the search',
|
|
2655
|
+
**subtitle_props)
|
|
2584
2656
|
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)
|
|
2657
|
+
fig_subtitle = plt.figtext(
|
|
2658
|
+
*subtitle_pos,
|
|
2659
|
+
'Select the best offset and press "Accept" to accept',
|
|
2660
|
+
**subtitle_props)
|
|
2592
2661
|
|
|
2593
2662
|
if not self._interactive:
|
|
2594
2663
|
|
|
2595
|
-
selected_offset.append(
|
|
2664
|
+
selected_offset.append(
|
|
2665
|
+
(True, preselected_offsets[default_offset_index]))
|
|
2596
2666
|
|
|
2597
2667
|
else:
|
|
2598
2668
|
|
|
@@ -2605,7 +2675,6 @@ class Tomo:
|
|
|
2605
2675
|
plt.axes([0.15, 0.05, 0.15, 0.075]), 'Reject')
|
|
2606
2676
|
reject_cid = reject_btn.on_clicked(reject)
|
|
2607
2677
|
|
|
2608
|
-
|
|
2609
2678
|
else:
|
|
2610
2679
|
|
|
2611
2680
|
# Setup RadioButtons
|
|
@@ -2621,9 +2690,15 @@ class Tomo:
|
|
|
2621
2690
|
labels = preselected_offsets
|
|
2622
2691
|
radio_btn = RadioButtons(
|
|
2623
2692
|
plt.axes([0.175, 0.05, 0.1, 0.1]),
|
|
2624
|
-
labels = labels, active=
|
|
2693
|
+
labels = labels, active=default_offset_index)
|
|
2625
2694
|
radio_cid = radio_btn.on_clicked(select_offset)
|
|
2626
2695
|
|
|
2696
|
+
# Setup "Search" button
|
|
2697
|
+
if search_button:
|
|
2698
|
+
search_btn = Button(
|
|
2699
|
+
plt.axes([0.4125, 0.05, 0.15, 0.075]), 'Search')
|
|
2700
|
+
search_cid = search_btn.on_clicked(search)
|
|
2701
|
+
|
|
2627
2702
|
# Setup "Accept" button
|
|
2628
2703
|
accept_btn = Button(
|
|
2629
2704
|
plt.axes([0.7, 0.05, 0.15, 0.075]), 'Accept')
|
|
@@ -2639,6 +2714,9 @@ class Tomo:
|
|
|
2639
2714
|
else:
|
|
2640
2715
|
radio_btn.disconnect(radio_cid)
|
|
2641
2716
|
radio_btn.ax.remove()
|
|
2717
|
+
if search_button:
|
|
2718
|
+
search_btn.disconnect(search_cid)
|
|
2719
|
+
search_btn.ax.remove()
|
|
2642
2720
|
accept_btn.disconnect(accept_cid)
|
|
2643
2721
|
accept_btn.ax.remove()
|
|
2644
2722
|
|
|
@@ -2646,18 +2724,20 @@ class Tomo:
|
|
|
2646
2724
|
fig_title.remove()
|
|
2647
2725
|
else:
|
|
2648
2726
|
fig_title.set_in_layout(True)
|
|
2649
|
-
|
|
2727
|
+
if self._interactive:
|
|
2728
|
+
select_text.remove()
|
|
2650
2729
|
fig_subtitle.remove()
|
|
2651
2730
|
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
2652
|
-
if not selected_offset and num_plots == 1:
|
|
2653
|
-
selected_offset.append(
|
|
2731
|
+
if not selected_offset:# and num_plots == 1:
|
|
2732
|
+
selected_offset.append(
|
|
2733
|
+
(True, preselected_offsets[default_offset_index]))
|
|
2654
2734
|
|
|
2655
2735
|
return fig, *selected_offset[0]
|
|
2656
2736
|
|
|
2657
2737
|
def _reconstruct_one_tomo_stack(
|
|
2658
2738
|
self, tomo_stack, thetas, center_offsets=None, num_core=1,
|
|
2659
|
-
algorithm='gridrec', secondary_iters=0,
|
|
2660
|
-
ring_width=None):
|
|
2739
|
+
algorithm='gridrec', secondary_iters=0, gaussian_sigma=None,
|
|
2740
|
+
remove_stripe_sigma=None, ring_width=None):
|
|
2661
2741
|
"""Reconstruct a single tomography stack."""
|
|
2662
2742
|
# Third party modules
|
|
2663
2743
|
from tomopy import (
|
|
@@ -2667,16 +2747,10 @@ class Tomo:
|
|
|
2667
2747
|
recon,
|
|
2668
2748
|
)
|
|
2669
2749
|
|
|
2670
|
-
# tomo_stack order: row,theta,column
|
|
2671
|
-
#
|
|
2750
|
+
# tomo_stack axis data order: row,theta,column
|
|
2751
|
+
# thetas in radians
|
|
2672
2752
|
# 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?
|
|
2753
|
+
# to column center
|
|
2680
2754
|
if center_offsets is None:
|
|
2681
2755
|
centers = np.zeros((tomo_stack.shape[0]))
|
|
2682
2756
|
elif len(center_offsets) == 2:
|
|
@@ -2690,24 +2764,9 @@ class Tomo:
|
|
|
2690
2764
|
centers = center_offsets
|
|
2691
2765
|
centers += tomo_stack.shape[2]/2
|
|
2692
2766
|
|
|
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
2767
|
# Remove horizontal stripe
|
|
2709
2768
|
# RV prep.stripe.remove_stripe_fw seems flawed for hollow brick
|
|
2710
|
-
#
|
|
2769
|
+
# accross multiple stacks
|
|
2711
2770
|
if remove_stripe_sigma is not None and remove_stripe_sigma:
|
|
2712
2771
|
if num_core > NUM_CORE_TOMOPY_LIMIT:
|
|
2713
2772
|
tomo_stack = prep.stripe.remove_stripe_fw(
|
|
@@ -2721,7 +2780,7 @@ class Tomo:
|
|
|
2721
2780
|
self._logger.debug('Performing initial image reconstruction')
|
|
2722
2781
|
t0 = time()
|
|
2723
2782
|
tomo_recon_stack = recon(
|
|
2724
|
-
tomo_stack,
|
|
2783
|
+
tomo_stack, thetas, centers, sinogram_order=True,
|
|
2725
2784
|
algorithm=algorithm, ncore=num_core)
|
|
2726
2785
|
self._logger.info(
|
|
2727
2786
|
f'Performing initial image reconstruction took {time()-t0:.2f} '
|
|
@@ -2759,9 +2818,9 @@ class Tomo:
|
|
|
2759
2818
|
}
|
|
2760
2819
|
t0 = time()
|
|
2761
2820
|
tomo_recon_stack = recon(
|
|
2762
|
-
tomo_stack,
|
|
2763
|
-
|
|
2764
|
-
|
|
2821
|
+
tomo_stack, thetas, centers, init_recon=tomo_recon_stack,
|
|
2822
|
+
options=options, sinogram_order=True, algorithm=astra,
|
|
2823
|
+
ncore=num_core)
|
|
2765
2824
|
self._logger.info(
|
|
2766
2825
|
f'Performing secondary iterations took {time()-t0:.2f} '
|
|
2767
2826
|
'seconds')
|
|
@@ -2772,6 +2831,11 @@ class Tomo:
|
|
|
2772
2831
|
tomo_recon_stack, rwidth=ring_width, out=tomo_recon_stack,
|
|
2773
2832
|
ncore=num_core)
|
|
2774
2833
|
|
|
2834
|
+
# Performing Gaussian filtering
|
|
2835
|
+
if gaussian_sigma is not None and gaussian_sigma:
|
|
2836
|
+
tomo_recon_stack = misc.corr.gaussian_filter(
|
|
2837
|
+
tomo_recon_stack, sigma=gaussian_sigma, ncore=num_core)
|
|
2838
|
+
|
|
2775
2839
|
return tomo_recon_stack
|
|
2776
2840
|
|
|
2777
2841
|
def _resize_reconstructed_data(
|
|
@@ -2797,16 +2861,16 @@ class Tomo:
|
|
|
2797
2861
|
# Selecting x an y bounds (in z-plane)
|
|
2798
2862
|
if x_bounds is None:
|
|
2799
2863
|
if not self._interactive:
|
|
2800
|
-
self._logger.warning('x_bounds unspecified,
|
|
2801
|
-
'
|
|
2864
|
+
self._logger.warning('x_bounds unspecified, use data for '
|
|
2865
|
+
'full x-range')
|
|
2802
2866
|
x_bounds = (0, tomo_recon_stacks[0].shape[2])
|
|
2803
2867
|
elif not is_int_pair(
|
|
2804
2868
|
x_bounds, ge=0, le=tomo_recon_stacks[0].shape[2]):
|
|
2805
2869
|
raise ValueError(f'Invalid parameter x_bounds ({x_bounds})')
|
|
2806
2870
|
if y_bounds is None:
|
|
2807
2871
|
if not self._interactive:
|
|
2808
|
-
self._logger.warning('y_bounds unspecified,
|
|
2809
|
-
'
|
|
2872
|
+
self._logger.warning('y_bounds unspecified, use data for '
|
|
2873
|
+
'full y-range')
|
|
2810
2874
|
y_bounds = (0, tomo_recon_stacks[0].shape[1])
|
|
2811
2875
|
elif not is_int_pair(
|
|
2812
2876
|
y_bounds, ge=0, le=tomo_recon_stacks[0].shape[1]):
|
|
@@ -2828,11 +2892,20 @@ class Tomo:
|
|
|
2828
2892
|
tomosum = 0
|
|
2829
2893
|
for i in range(num_tomo_stacks):
|
|
2830
2894
|
tomosum = tomosum + np.sum(tomo_recon_stacks[i], axis=0)
|
|
2831
|
-
|
|
2895
|
+
if self._save_figs:
|
|
2896
|
+
if combine_data:
|
|
2897
|
+
filename = os_path.join(
|
|
2898
|
+
self._outputdir, 'combined_data_xy_roi.png')
|
|
2899
|
+
else:
|
|
2900
|
+
filename = os_path.join(
|
|
2901
|
+
self._outputdir, 'reconstructed_data_xy_roi.png')
|
|
2902
|
+
else:
|
|
2903
|
+
filename = None
|
|
2904
|
+
roi = select_roi_2d(
|
|
2832
2905
|
tomosum, preselected_roi=preselected_roi,
|
|
2833
2906
|
title_a='Reconstructed data summed over z',
|
|
2834
2907
|
row_label='y', column_label='x',
|
|
2835
|
-
interactive=self._interactive)
|
|
2908
|
+
interactive=self._interactive, filename=filename)
|
|
2836
2909
|
if roi is None:
|
|
2837
2910
|
x_bounds = (0, tomo_recon_stacks[0].shape[2])
|
|
2838
2911
|
y_bounds = (0, tomo_recon_stacks[0].shape[1])
|
|
@@ -2841,12 +2914,6 @@ class Tomo:
|
|
|
2841
2914
|
y_bounds = (int(roi[2]), int(roi[3]))
|
|
2842
2915
|
self._logger.debug(f'x_bounds = {x_bounds}')
|
|
2843
2916
|
self._logger.debug(f'y_bounds = {y_bounds}')
|
|
2844
|
-
# Plot results
|
|
2845
|
-
if self._save_figs:
|
|
2846
|
-
fig.savefig(
|
|
2847
|
-
os_path.join(
|
|
2848
|
-
self._output_folder, 'reconstructed_data_xy_roi.png'))
|
|
2849
|
-
plt.close()
|
|
2850
2917
|
|
|
2851
2918
|
# Selecting z bounds (in xy-plane)
|
|
2852
2919
|
# (only valid for a single image stack or when combining a stack)
|
|
@@ -2868,17 +2935,20 @@ class Tomo:
|
|
|
2868
2935
|
tomosum = 0
|
|
2869
2936
|
for i in range(num_tomo_stacks):
|
|
2870
2937
|
tomosum = tomosum + np.sum(tomo_recon_stacks[i], axis=(1,2))
|
|
2871
|
-
|
|
2938
|
+
if self._save_figs:
|
|
2939
|
+
if combine_data:
|
|
2940
|
+
filename = os_path.join(
|
|
2941
|
+
self._outputdir, 'combined_data_z_roi.png')
|
|
2942
|
+
else:
|
|
2943
|
+
filename = os_path.join(
|
|
2944
|
+
self._outputdir, 'reconstructed_data_z_roi.png')
|
|
2945
|
+
else:
|
|
2946
|
+
filename = None
|
|
2947
|
+
z_bounds = select_roi_1d(
|
|
2872
2948
|
tomosum, preselected_roi=z_bounds,
|
|
2873
2949
|
xlabel='z', ylabel='Reconstructed data summed over x and y',
|
|
2874
|
-
interactive=self._interactive)
|
|
2950
|
+
interactive=self._interactive, filename=filename)
|
|
2875
2951
|
self._logger.debug(f'z_bounds = {z_bounds}')
|
|
2876
|
-
# Plot results
|
|
2877
|
-
if self._save_figs:
|
|
2878
|
-
fig.savefig(
|
|
2879
|
-
os_path.join(
|
|
2880
|
-
self._output_folder, 'reconstructed_data_z_roi.png'))
|
|
2881
|
-
plt.close()
|
|
2882
2952
|
|
|
2883
2953
|
return x_bounds, y_bounds, z_bounds
|
|
2884
2954
|
|
|
@@ -2890,7 +2960,7 @@ class TomoSimFieldProcessor(Processor):
|
|
|
2890
2960
|
tomography detector images.
|
|
2891
2961
|
"""
|
|
2892
2962
|
|
|
2893
|
-
def process(self, data
|
|
2963
|
+
def process(self, data):
|
|
2894
2964
|
"""
|
|
2895
2965
|
Process the input configuration and return a
|
|
2896
2966
|
`nexusformat.nexus.NXroot` object with the simulated
|
|
@@ -2920,6 +2990,9 @@ class TomoSimFieldProcessor(Processor):
|
|
|
2920
2990
|
sample_size = config.sample_size
|
|
2921
2991
|
if len(sample_size) == 1:
|
|
2922
2992
|
sample_size = (sample_size[0], sample_size[0])
|
|
2993
|
+
if sample_type == 'hollow_pyramid' and len(sample_size) != 3:
|
|
2994
|
+
raise ValueError('Invalid combindation of sample_type '
|
|
2995
|
+
f'({sample_type}) and sample_size ({sample_size}')
|
|
2923
2996
|
wall_thickness = config.wall_thickness
|
|
2924
2997
|
mu = config.mu
|
|
2925
2998
|
theta_step = config.theta_step
|
|
@@ -2944,30 +3017,37 @@ class TomoSimFieldProcessor(Processor):
|
|
|
2944
3017
|
f'({detector_size[0]*pixel_size[0]})')
|
|
2945
3018
|
|
|
2946
3019
|
# Get the rotation angles (start at a arbitrarily choose angle
|
|
2947
|
-
#
|
|
3020
|
+
# and add thetas for a full 360 degrees rotation series)
|
|
2948
3021
|
if station in ('id1a3', 'id3a'):
|
|
2949
3022
|
theta_start = 0.
|
|
2950
3023
|
else:
|
|
2951
3024
|
theta_start = -17
|
|
2952
|
-
#RV theta_end = theta_start + 360.
|
|
3025
|
+
# RV theta_end = theta_start + 360.
|
|
2953
3026
|
theta_end = theta_start + 180.
|
|
2954
3027
|
thetas = list(
|
|
2955
3028
|
np.arange(theta_start, theta_end+0.5*theta_step, theta_step))
|
|
2956
3029
|
|
|
2957
3030
|
# Get the number of horizontal stacks bases on the diagonal
|
|
2958
|
-
#
|
|
2959
|
-
|
|
2960
|
-
|
|
3031
|
+
# of the square and for now don't allow more than one
|
|
3032
|
+
if (sample_size) == 3:
|
|
3033
|
+
num_tomo_stack = 1 + int(
|
|
3034
|
+
(max(sample_size[1:2])*np.sqrt(2)-pixel_size[1])
|
|
3035
|
+
/ (detector_size[1]*pixel_size[1]))
|
|
3036
|
+
else:
|
|
3037
|
+
num_tomo_stack = 1 + int((sample_size[1]*np.sqrt(2)-pixel_size[1])
|
|
3038
|
+
/ (detector_size[1]*pixel_size[1]))
|
|
2961
3039
|
if num_tomo_stack > 1:
|
|
2962
3040
|
raise ValueError('Sample is too wide for the detector')
|
|
2963
3041
|
|
|
2964
3042
|
# Create the x-ray path length through a solid square
|
|
2965
|
-
#
|
|
2966
|
-
path_lengths_solid =
|
|
2967
|
-
|
|
3043
|
+
# crosssection for a set of rotation angles.
|
|
3044
|
+
path_lengths_solid = None
|
|
3045
|
+
if sample_type != 'hollow_pyramid':
|
|
3046
|
+
path_lengths_solid = self._create_pathlength_solid_square(
|
|
3047
|
+
sample_size[1], thetas, pixel_size[1], detector_size[1])
|
|
2968
3048
|
|
|
2969
3049
|
# Create the x-ray path length through a hollow square
|
|
2970
|
-
#
|
|
3050
|
+
# crosssection for a set of rotation angles.
|
|
2971
3051
|
path_lengths_hollow = None
|
|
2972
3052
|
if sample_type in ('square_pipe', 'hollow_cube', 'hollow_brick'):
|
|
2973
3053
|
path_lengths_hollow = path_lengths_solid \
|
|
@@ -2990,7 +3070,12 @@ class TomoSimFieldProcessor(Processor):
|
|
|
2990
3070
|
num_theta = len(thetas)
|
|
2991
3071
|
vertical_shifts = []
|
|
2992
3072
|
tomo_fields_stack = []
|
|
2993
|
-
|
|
3073
|
+
len_img_y = (detector_size[1]+1)//2
|
|
3074
|
+
if len_img_y%2:
|
|
3075
|
+
len_img_y = 2*len_img_y - 1
|
|
3076
|
+
else:
|
|
3077
|
+
len_img_y = 2*len_img_y
|
|
3078
|
+
img_dim = (len(img_row_coords), len_img_y)
|
|
2994
3079
|
intensities_solid = None
|
|
2995
3080
|
intensities_hollow = None
|
|
2996
3081
|
for n in range(num_tomo_stack):
|
|
@@ -3007,6 +3092,37 @@ class TomoSimFieldProcessor(Processor):
|
|
|
3007
3092
|
beam_intensity * np.exp(-mu*path_lengths_hollow)
|
|
3008
3093
|
for n in range(num_theta):
|
|
3009
3094
|
tomo_field[n,:,:] = intensities_hollow[n]
|
|
3095
|
+
elif sample_type == 'hollow_pyramid':
|
|
3096
|
+
outer_indices = \
|
|
3097
|
+
np.where(abs(img_row_coords) <= sample_size[0]/2)[0]
|
|
3098
|
+
inner_indices = np.where(
|
|
3099
|
+
abs(img_row_coords) < sample_size[0]/2 - wall_thickness)[0]
|
|
3100
|
+
wall_indices = list(set(outer_indices)-set(inner_indices))
|
|
3101
|
+
ratio = abs(sample_size[1]-sample_size[2])/sample_size[0]
|
|
3102
|
+
baselength = max(sample_size[1:2])
|
|
3103
|
+
for i in wall_indices:
|
|
3104
|
+
path_lengths_solid = self._create_pathlength_solid_square(
|
|
3105
|
+
baselength - ratio*(
|
|
3106
|
+
img_row_coords[i] + 0.5*sample_size[0]),
|
|
3107
|
+
thetas, pixel_size[1], detector_size[1])
|
|
3108
|
+
intensities_solid = \
|
|
3109
|
+
beam_intensity * np.exp(-mu*path_lengths_solid)
|
|
3110
|
+
for n in range(num_theta):
|
|
3111
|
+
tomo_field[n,i] = intensities_solid[n]
|
|
3112
|
+
for i in inner_indices:
|
|
3113
|
+
path_lengths_hollow = (
|
|
3114
|
+
self._create_pathlength_solid_square(
|
|
3115
|
+
baselength - ratio*(
|
|
3116
|
+
img_row_coords[i] + 0.5*sample_size[0]),
|
|
3117
|
+
thetas, pixel_size[1], detector_size[1])
|
|
3118
|
+
- self._create_pathlength_solid_square(
|
|
3119
|
+
baselength - 2*wall_thickness - ratio*(
|
|
3120
|
+
img_row_coords[i] + 0.5*sample_size[0]),
|
|
3121
|
+
thetas, pixel_size[1], detector_size[1]))
|
|
3122
|
+
intensities_hollow = \
|
|
3123
|
+
beam_intensity * np.exp(-mu*path_lengths_hollow)
|
|
3124
|
+
for n in range(num_theta):
|
|
3125
|
+
tomo_field[n,i] = intensities_hollow[n]
|
|
3010
3126
|
else:
|
|
3011
3127
|
intensities_solid = \
|
|
3012
3128
|
beam_intensity * np.exp(-mu*path_lengths_solid)
|
|
@@ -3088,10 +3204,6 @@ class TomoSimFieldProcessor(Processor):
|
|
|
3088
3204
|
nxdetector.z_translation = vertical_shifts
|
|
3089
3205
|
nxdetector.starting_image_index = starting_image_index
|
|
3090
3206
|
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
3207
|
|
|
3096
3208
|
return nxroot
|
|
3097
3209
|
|
|
@@ -3103,7 +3215,7 @@ class TomoSimFieldProcessor(Processor):
|
|
|
3103
3215
|
"""
|
|
3104
3216
|
# Get the column coordinates
|
|
3105
3217
|
img_y_coords = pixel_size * (0.5 * (1 - detector_size%2)
|
|
3106
|
-
+ np.asarray(range(
|
|
3218
|
+
+ np.asarray(range((detector_size+1)//2)))
|
|
3107
3219
|
|
|
3108
3220
|
# Get the path lenghts for position column coordinates
|
|
3109
3221
|
lengths = np.zeros((len(thetas), len(img_y_coords)), dtype=np.float64)
|
|
@@ -3145,7 +3257,7 @@ class TomoDarkFieldProcessor(Processor):
|
|
|
3145
3257
|
tomography data set created by TomoSimProcessor.
|
|
3146
3258
|
"""
|
|
3147
3259
|
|
|
3148
|
-
def process(self, data, num_image=5
|
|
3260
|
+
def process(self, data, num_image=5):
|
|
3149
3261
|
"""
|
|
3150
3262
|
Process the input configuration and return a
|
|
3151
3263
|
`nexusformat.nexus.NXroot` object with the simulated
|
|
@@ -3218,7 +3330,7 @@ class TomoBrightFieldProcessor(Processor):
|
|
|
3218
3330
|
tomography data set created by TomoSimProcessor.
|
|
3219
3331
|
"""
|
|
3220
3332
|
|
|
3221
|
-
def process(self, data, num_image=5
|
|
3333
|
+
def process(self, data, num_image=5):
|
|
3222
3334
|
"""
|
|
3223
3335
|
Process the input configuration and return a
|
|
3224
3336
|
`nexusformat.nexus.NXroot` object with the simulated
|
|
@@ -3235,7 +3347,6 @@ class TomoBrightFieldProcessor(Processor):
|
|
|
3235
3347
|
"""
|
|
3236
3348
|
# Third party modules
|
|
3237
3349
|
from nexusformat.nexus import (
|
|
3238
|
-
NeXusError,
|
|
3239
3350
|
NXroot,
|
|
3240
3351
|
NXentry,
|
|
3241
3352
|
NXinstrument,
|
|
@@ -3272,7 +3383,7 @@ class TomoBrightFieldProcessor(Processor):
|
|
|
3272
3383
|
bright_field = np.concatenate((dummy_fields, bright_field))
|
|
3273
3384
|
num_image += num_dummy_start
|
|
3274
3385
|
# Add 20% to slit size to make the bright beam slightly taller
|
|
3275
|
-
#
|
|
3386
|
+
# than the vertical displacements between stacks
|
|
3276
3387
|
slit_size = 1.2*source.slit_size
|
|
3277
3388
|
if slit_size < float(detector.row_pixel_size*detector_size[0]):
|
|
3278
3389
|
img_row_coords = float(detector.row_pixel_size) \
|
|
@@ -3307,7 +3418,7 @@ class TomoSpecProcessor(Processor):
|
|
|
3307
3418
|
simulated tomography data set created by TomoSimProcessor.
|
|
3308
3419
|
"""
|
|
3309
3420
|
|
|
3310
|
-
def process(self, data, scan_numbers=[1]
|
|
3421
|
+
def process(self, data, scan_numbers=[1]):
|
|
3311
3422
|
"""
|
|
3312
3423
|
Process the input configuration and return a list of strings
|
|
3313
3424
|
representing a plain text SPEC file.
|
|
@@ -3325,17 +3436,14 @@ class TomoSpecProcessor(Processor):
|
|
|
3325
3436
|
from json import dumps
|
|
3326
3437
|
from datetime import datetime
|
|
3327
3438
|
|
|
3328
|
-
# Third party modules
|
|
3329
3439
|
from nexusformat.nexus import (
|
|
3330
|
-
NeXusError,
|
|
3331
|
-
NXcollection,
|
|
3332
3440
|
NXentry,
|
|
3333
3441
|
NXroot,
|
|
3334
3442
|
NXsubentry,
|
|
3335
3443
|
)
|
|
3336
3444
|
|
|
3337
3445
|
# Get and validate the TomoSimField, TomoDarkField, or
|
|
3338
|
-
#
|
|
3446
|
+
# TomoBrightField configuration object in data
|
|
3339
3447
|
configs = {}
|
|
3340
3448
|
nxroot = get_nxroot(data, 'tomo.models.TomoDarkField')
|
|
3341
3449
|
if nxroot is not None:
|
|
@@ -3364,7 +3472,7 @@ class TomoSpecProcessor(Processor):
|
|
|
3364
3472
|
raise ValueError('Inconsistent sample_type among scans')
|
|
3365
3473
|
detector = nxroot.entry.instrument.detector
|
|
3366
3474
|
if 'z_translation' in detector:
|
|
3367
|
-
num_stack =
|
|
3475
|
+
num_stack = detector.z_translation.size
|
|
3368
3476
|
else:
|
|
3369
3477
|
num_stack = 1
|
|
3370
3478
|
data_shape = detector.data.shape
|
|
@@ -3398,9 +3506,9 @@ class TomoSpecProcessor(Processor):
|
|
|
3398
3506
|
if station in ('id1a3', 'id3a'):
|
|
3399
3507
|
spec_file.append('#O0 ramsx ramsz')
|
|
3400
3508
|
else:
|
|
3401
|
-
#RV Fix main code to use independent dim info
|
|
3509
|
+
# RV Fix main code to use independent dim info
|
|
3402
3510
|
spec_file.append('#O0 GI_samx GI_samz GI_samphi')
|
|
3403
|
-
spec_file.append('#o0 samx samz samphi') #RV do I need this line?
|
|
3511
|
+
spec_file.append('#o0 samx samz samphi') # RV do I need this line?
|
|
3404
3512
|
spec_file.append('')
|
|
3405
3513
|
|
|
3406
3514
|
# Create the SPEC file scan info (and image and parfile data for SMB)
|
|
@@ -3412,10 +3520,10 @@ class TomoSpecProcessor(Processor):
|
|
|
3412
3520
|
for schema, nxroot in configs.items():
|
|
3413
3521
|
detector = nxroot.entry.instrument.detector
|
|
3414
3522
|
if 'z_translation' in detector:
|
|
3415
|
-
z_translations = list(
|
|
3523
|
+
z_translations = list(detector.z_translation.nxdata)
|
|
3416
3524
|
else:
|
|
3417
3525
|
z_translations = [0.]
|
|
3418
|
-
thetas =
|
|
3526
|
+
thetas = detector.thetas
|
|
3419
3527
|
num_theta = thetas.size
|
|
3420
3528
|
if schema == 'tomo.models.TomoDarkField':
|
|
3421
3529
|
if station in ('id1a3', 'id3a'):
|
|
@@ -3454,9 +3562,11 @@ class TomoSpecProcessor(Processor):
|
|
|
3454
3562
|
spec_file.append('#N 1')
|
|
3455
3563
|
spec_file.append('#L ome')
|
|
3456
3564
|
if scan_type == 'ts1':
|
|
3457
|
-
image_sets.append(
|
|
3565
|
+
#image_sets.append(detector.data.nxdata[n])
|
|
3566
|
+
image_sets.append(detector.data[n])
|
|
3458
3567
|
else:
|
|
3459
|
-
image_sets.append(
|
|
3568
|
+
#image_sets.append(detector.data.nxdata)
|
|
3569
|
+
image_sets.append(detector.data)
|
|
3460
3570
|
par_file.append(
|
|
3461
3571
|
f'{datetime.now().strftime("%Y%m%d")} '
|
|
3462
3572
|
f'{datetime.now().strftime("%H%M%S")} '
|
|
@@ -3546,7 +3656,7 @@ class TomoSpecProcessor(Processor):
|
|
|
3546
3656
|
|
|
3547
3657
|
nxroot = NXroot()
|
|
3548
3658
|
nxroot[sample_type] = nxentry
|
|
3549
|
-
nxroot
|
|
3659
|
+
nxroot[sample_type].set_default()
|
|
3550
3660
|
|
|
3551
3661
|
return nxroot
|
|
3552
3662
|
|