1
|
|
|
# Copyright 2014 Diamond Light Source Ltd. |
2
|
|
|
# |
3
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
4
|
|
|
# you may not use this file except in compliance with the License. |
5
|
|
|
# You may obtain a copy of the License at |
6
|
|
|
# |
7
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
8
|
|
|
# |
9
|
|
|
# Unless required by applicable law or agreed to in writing, software |
10
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
11
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12
|
|
|
# See the License for the specific language governing permissions and |
13
|
|
|
# limitations under the License. |
14
|
|
|
""" |
15
|
|
|
.. module:: vo_centering_iterative |
16
|
|
|
:platform: Unix |
17
|
|
|
:synopsis: A plugin to find the center of rotation per frame |
18
|
|
|
.. moduleauthor:: Mark Basham <[email protected]> |
19
|
|
|
""" |
20
|
|
|
|
21
|
|
|
import math |
22
|
|
|
import logging |
23
|
|
|
import numpy as np |
24
|
|
|
import scipy.ndimage as ndi |
25
|
|
|
import scipy.ndimage.filters as filter |
26
|
|
|
import pyfftw.interfaces.scipy_fftpack as fft |
27
|
|
|
|
28
|
|
|
from scipy import signal |
29
|
|
|
|
30
|
|
|
from savu.plugins.utils import register_plugin |
31
|
|
|
from savu.data.plugin_list import CitationInformation |
32
|
|
|
from savu.plugins.filters.base_filter import BaseFilter |
33
|
|
|
from savu.plugins.driver.iterative_plugin import IterativePlugin |
34
|
|
|
|
35
|
|
|
# :u*param search_area: Search area in pixels from horizontal approximate \ |
36
|
|
|
# centre of the image. Default: (-50, 50). |
37
|
|
|
# Deprecated !!! |
38
|
|
|
|
39
|
|
|
@register_plugin |
40
|
|
|
class VoCenteringIterative(BaseFilter, IterativePlugin): |
41
|
|
|
""" |
42
|
|
|
A plugin to calculate the centre of rotation using the Vo Method |
43
|
|
|
|
44
|
|
|
:param ratio: The ratio between the size of object and FOV of \ |
45
|
|
|
the camera. Default: 0.5. |
46
|
|
|
:param row_drop: Drop lines around vertical center of the \ |
47
|
|
|
mask. Default: 20. |
48
|
|
|
:param search_radius: Use for fine searching. Default: 6. |
49
|
|
|
:param step: Step of fine searching. Default: 0.5. |
50
|
|
|
:param expand_by: The number of pixels to expand the search region by \ |
51
|
|
|
on each iteration. Default: 5 |
52
|
|
|
:param boundary_distance: Accepted distance of minima from the boundary of\ |
53
|
|
|
the listshift in the coarse search. Default: 3. |
54
|
|
|
:u*param preview: A slice list of required frames (sinograms) to use in \ |
55
|
|
|
the calulation of the centre of rotation (this will not reduce the data \ |
56
|
|
|
size for subsequent plugins). Default: []. |
57
|
|
|
:param datasets_to_populate: A list of datasets which require this \ |
58
|
|
|
information. Default: []. |
59
|
|
|
:param out_datasets: The default \ |
60
|
|
|
names. Default: ['cor_raw','cor_fit', 'reliability']. |
61
|
|
|
:u*param start_pixel: The approximate centre. If value is None, take the \ |
62
|
|
|
value from .nxs file else set to image centre. Default: None. |
63
|
|
|
""" |
64
|
|
|
|
65
|
|
|
def __init__(self): |
66
|
|
|
super(VoCenteringIterative, self).__init__("VoCenteringIterative") |
67
|
|
|
self.search_area = (-20, 20) |
68
|
|
|
self.peak_height_min = 50000 # arbitrary |
69
|
|
|
self.min_dist = 3 # min distance deamed acceptible from boundary |
70
|
|
|
self.expand_by = 5 # expand the search region by this amount |
71
|
|
|
self.list_shift = None |
72
|
|
|
self.warning_level = 0 |
73
|
|
|
self.final = False |
74
|
|
|
self.at_boundary = False |
75
|
|
|
self.list_metric = [] |
76
|
|
|
self.expand_direction = None |
77
|
|
|
|
78
|
|
|
def _create_mask(self, Nrow, Ncol, obj_radius): |
79
|
|
|
du, dv = 1.0/Ncol, (Nrow-1.0)/(Nrow*2.0*math.pi) |
80
|
|
|
cen_row, cen_col = int(np.ceil(Nrow / 2.0)-1), int(np.ceil(Ncol / 2.0)-1) |
81
|
|
|
drop = self.parameters['row_drop'] |
82
|
|
|
mask = np.zeros((Nrow, Ncol), dtype=np.float32) |
83
|
|
|
for i in range(Nrow): |
84
|
|
|
num1 = np.round(((i-cen_row)*dv/obj_radius)/du) |
85
|
|
|
p1, p2 = (np.clip(np.sort((-num1+cen_col, num1+cen_col)), |
86
|
|
|
0, Ncol-1)).astype(int) |
87
|
|
|
mask[i, p1:p2+1] = np.ones(p2-p1+1, dtype=np.float32) |
88
|
|
|
|
89
|
|
|
if drop < cen_row: |
90
|
|
|
mask[cen_row-drop:cen_row+drop+1, :] = \ |
91
|
|
|
np.zeros((2*drop + 1, Ncol), dtype=np.float32) |
92
|
|
|
mask[:, cen_col-1:cen_col+2] = np.zeros((Nrow, 3), dtype=np.float32) |
93
|
|
|
return mask |
94
|
|
|
|
95
|
|
|
def _get_start_shift(self, centre): |
96
|
|
|
if self.parameters['start_pixel'] is not None: |
97
|
|
|
shift = centre - int(self.parameters['start_pixel']/self.downlevel) |
98
|
|
|
else: |
99
|
|
|
in_mData = self.get_in_meta_data()[0] |
100
|
|
|
shift = centre - in_mData['centre'] if 'centre' in \ |
101
|
|
|
list(in_mData.get_dictionary().keys()) else 0 |
102
|
|
|
return int(shift) |
103
|
|
|
|
104
|
|
|
def _coarse_search(self, sino, list_shift): |
105
|
|
|
# search minsearch to maxsearch in 1 pixel steps |
106
|
|
|
list_metric = np.zeros(len(list_shift), dtype=np.float32) |
107
|
|
|
(Nrow, Ncol) = sino.shape |
108
|
|
|
# check angles to determine if a sinogram should be chopped off. |
109
|
|
|
# Copy the sinogram and flip left right, to make a full [0:2Pi] sino |
110
|
|
|
sino2 = np.fliplr(sino[1:]) |
111
|
|
|
# This image is used for compensating the shift of sino2 |
112
|
|
|
compensateimage = np.zeros((Nrow-1, Ncol), dtype=np.float32) |
113
|
|
|
# Start coarse search in which the shift step is 1 |
114
|
|
|
compensateimage[:] = np.flipud(sino)[1:] |
115
|
|
|
mask = self._create_mask(2*Nrow-1, Ncol, |
116
|
|
|
0.5*self.parameters['ratio']*Ncol) |
117
|
|
|
count = 0 |
118
|
|
|
for i in list_shift: |
119
|
|
|
sino2a = np.roll(sino2, i, axis=1) |
120
|
|
|
if i >= 0: |
121
|
|
|
sino2a[:, 0:i] = compensateimage[:, 0:i] |
122
|
|
|
else: |
123
|
|
|
sino2a[:, i:] = compensateimage[:, i:] |
124
|
|
|
list_metric[count] = np.sum( |
125
|
|
|
np.abs(fft.fftshift(fft.fft2(np.vstack((sino, sino2a)))))*mask) |
126
|
|
|
count += 1 |
127
|
|
|
return list_metric |
128
|
|
|
|
129
|
|
|
def _fine_search(self, sino, raw_cor): |
130
|
|
|
(Nrow, Ncol) = sino.shape |
131
|
|
|
centerfliplr = (Ncol + 1.0) / 2.0 - 1.0 |
132
|
|
|
# Use to shift the sino2 to the raw CoR |
133
|
|
|
shiftsino = np.int16(2*(raw_cor-centerfliplr)) |
134
|
|
|
sino2 = np.roll(np.fliplr(sino[1:]), shiftsino, axis=1) |
135
|
|
|
lefttake = 0 |
136
|
|
|
righttake = Ncol-1 |
137
|
|
|
search_rad = self.parameters['search_radius'] |
138
|
|
|
|
139
|
|
|
if raw_cor <= centerfliplr: |
140
|
|
|
lefttake = np.int16(np.ceil(search_rad+1)) |
141
|
|
|
righttake = np.int16(np.floor(2*raw_cor-search_rad-1)) |
142
|
|
|
else: |
143
|
|
|
lefttake = np.int16(np.ceil(raw_cor-(Ncol-1-raw_cor)+search_rad+1)) |
144
|
|
|
righttake = np.int16(np.floor(Ncol-1-search_rad-1)) |
145
|
|
|
|
146
|
|
|
Ncol1 = righttake-lefttake + 1 |
147
|
|
|
mask = self._create_mask(2*Nrow-1, Ncol1, |
148
|
|
|
0.5*self.parameters['ratio']*Ncol) |
149
|
|
|
numshift = np.int16((2*search_rad)/self.parameters['step'])+1 |
150
|
|
|
listshift = np.linspace(-search_rad, search_rad, num=numshift) |
151
|
|
|
listmetric = np.zeros(len(listshift), dtype=np.float32) |
152
|
|
|
num1 = 0 |
153
|
|
|
factor1 = np.mean(sino[-1, lefttake:righttake]) |
154
|
|
|
for i in listshift: |
155
|
|
|
sino2a = ndi.interpolation.shift(sino2, (0, i), prefilter=False) |
156
|
|
|
factor2 = np.mean(sino2a[0, lefttake:righttake]) |
157
|
|
|
sino2a = sino2a*factor1/factor2 |
158
|
|
|
sinojoin = np.vstack((sino, sino2a)) |
159
|
|
|
listmetric[num1] = np.sum(np.abs(fft.fftshift( |
160
|
|
|
fft.fft2(sinojoin[:, lefttake:righttake + 1])))*mask) |
161
|
|
|
num1 = num1 + 1 |
162
|
|
|
minpos = np.argmin(listmetric) |
163
|
|
|
rotcenter = raw_cor + listshift[minpos] / 2.0 |
164
|
|
|
return rotcenter |
165
|
|
|
|
166
|
|
|
def _get_listshift(self): |
167
|
|
|
smin, smax = self.search_area if self.get_iteration() == 0 \ |
168
|
|
|
else self._expand_search() |
169
|
|
|
list_shift = np.arange(smin, smax+2, 2) - self.start_shift |
170
|
|
|
logging.debug('list shift is %s', list_shift) |
171
|
|
|
return list_shift |
172
|
|
|
|
173
|
|
|
def _expand_search(self): |
174
|
|
|
if self.expand_direction == 'left': |
175
|
|
|
return self._expand_left() |
176
|
|
|
elif self.expand_direction == 'right': |
177
|
|
|
return self._expand_right() |
178
|
|
|
else: |
179
|
|
|
raise Exception('Unknown expand direction.') |
180
|
|
|
|
181
|
|
|
def _expand_left(self): |
182
|
|
|
smax = self.list_shift[0] - 2 |
183
|
|
|
smin = smax - self.expand_by*2 |
184
|
|
|
|
185
|
|
|
if smin <= -self.boundary: |
186
|
|
|
smin = -self.boundary |
187
|
|
|
self.at_boundary = True |
188
|
|
|
return smin, smax |
189
|
|
|
|
190
|
|
|
def _expand_right(self): |
191
|
|
|
smin = self.list_shift[-1] + 2 |
192
|
|
|
smax = self.list_shift[-1] + self.expand_by*2 |
193
|
|
|
|
194
|
|
|
if smax <= self.boundary: |
195
|
|
|
smax = self.boundary |
196
|
|
|
self.at_boundary = True |
197
|
|
|
|
198
|
|
|
return smin, smax |
199
|
|
|
|
200
|
|
|
def pre_process(self): |
201
|
|
|
pData = self.get_plugin_in_datasets()[0] |
202
|
|
|
label = pData.get_data_dimension_by_axis_label |
203
|
|
|
Ncol = pData.get_shape()[label('detector_x')] |
204
|
|
|
self.downlevel = 4 if Ncol > 1800 else 1 |
205
|
|
|
self.downsample = slice(0, Ncol, self.downlevel) |
206
|
|
|
Ncol_downsample = len(np.arange(0, Ncol, self.downlevel)) |
207
|
|
|
self.centre_fliplr = (Ncol_downsample - 1.0) / 2.0 |
208
|
|
|
self.start_shift = self._get_start_shift(self.centre_fliplr)*2 |
209
|
|
|
self.boundary = int(np.ceil(Ncol/4.0)) |
210
|
|
|
|
211
|
|
|
def process_frames(self, data): |
212
|
|
|
if not self.final: |
213
|
|
|
logging.debug('performing coarse search for iteration %s', |
214
|
|
|
self.get_iteration()) |
215
|
|
|
sino = filter.gaussian_filter(data[0][:, self.downsample], (3, 1)) |
216
|
|
|
list_shift = self._get_listshift() |
217
|
|
|
list_metric = self._coarse_search(sino, list_shift) |
218
|
|
|
self._update_lists(list(list_shift), list(list_metric)) |
219
|
|
|
|
220
|
|
|
self.coarse_cor, dist, reliability_metrics = \ |
221
|
|
|
self._analyse_result(self.list_metric, self.list_shift) |
222
|
|
|
|
223
|
|
|
return [np.array([self.coarse_cor]), np.array([dist]), |
224
|
|
|
np.array([reliability_metrics]), np.array([self.list_metric])] |
225
|
|
|
else: |
226
|
|
|
logging.debug("performing fine search") |
227
|
|
|
sino = filter.median_filter(data[0], (2, 2)) |
228
|
|
|
cor = self._fine_search(sino, self.coarse_cor) |
229
|
|
|
self.set_processing_complete() |
230
|
|
|
return [np.array([cor]), np.array([self.list_metric])] |
231
|
|
|
|
232
|
|
|
def _update_lists(self, shift, metric): |
233
|
|
|
if self.expand_direction == 'left': |
234
|
|
|
self.list_shift = shift + self.list_shift |
235
|
|
|
self.list_metric = metric + self.list_metric |
236
|
|
|
elif self.expand_direction == 'right': |
237
|
|
|
self.list_shift += shift |
238
|
|
|
self.list_metric += metric |
239
|
|
|
else: |
240
|
|
|
self.list_shift = shift |
241
|
|
|
self.list_metric = metric |
242
|
|
|
|
243
|
|
|
def _analyse_result(self, metric, shift): |
244
|
|
|
minpos = np.argmin(metric) |
245
|
|
|
dist = min(abs(len(shift) - minpos), -minpos) |
246
|
|
|
|
247
|
|
|
rot_centre = (self.centre_fliplr + shift[minpos] / 2.0)*self.downlevel |
248
|
|
|
peaks = self._find_peaks(metric) |
249
|
|
|
|
250
|
|
|
good_nPeaks = True |
251
|
|
|
if len(peaks) != 1: |
252
|
|
|
good_nPeaks = False |
253
|
|
|
good_peak_height = True if np.any(peaks) and \ |
254
|
|
|
max(peaks) > self.peak_height_min else False |
255
|
|
|
|
256
|
|
|
metric = 0.0 |
257
|
|
|
if (good_peak_height and good_nPeaks): |
258
|
|
|
metric = 1.0 |
259
|
|
|
elif (good_peak_height or good_nPeaks): |
260
|
|
|
metric = 0.5 |
261
|
|
|
|
262
|
|
|
return rot_centre, dist, metric |
263
|
|
|
|
264
|
|
|
def _find_peaks(self, metric): |
265
|
|
|
import peakutils |
266
|
|
|
grad2 = np.gradient(np.gradient(metric)) |
267
|
|
|
grad2[grad2 < 0] = 0 |
268
|
|
|
index = peakutils.indexes(grad2, thres=0.5, min_dist=3) |
269
|
|
|
return np.sort(grad2[index]) |
270
|
|
|
|
271
|
|
|
def post_process(self): |
272
|
|
|
logging.debug("in the post process function") |
273
|
|
|
in_datasets, out_datasets = self.get_datasets() |
274
|
|
|
|
275
|
|
|
# ===================================================================== |
276
|
|
|
# Analyse distance of centre values from boundary of search region |
277
|
|
|
dist_from_boundary = np.squeeze(out_datasets[1].data[...]) |
278
|
|
|
near_boundary = np.where(abs(dist_from_boundary) < self.min_dist)[0] |
279
|
|
|
nEntries = len(dist_from_boundary) |
280
|
|
|
|
281
|
|
|
# Case1: Greater than half the results are near the boundary |
282
|
|
|
if (len(near_boundary)/float(nEntries)) > 0.5: |
283
|
|
|
# find which boundary |
284
|
|
|
signs = np.sign(dist_from_boundary[near_boundary]) |
285
|
|
|
left, right = len(signs[signs < 0]), len(signs[signs > 0]) |
286
|
|
|
|
287
|
|
|
logging.debug("res: results are near boundary") |
288
|
|
|
if not self.at_boundary: |
289
|
|
|
# if they are all at the same boundary expand the search region |
290
|
|
|
if not (left and right): |
291
|
|
|
logging.debug("res: expanding") |
292
|
|
|
self.expand_direction = 'left' if left else 'right' |
293
|
|
|
# if they are at different boundaries determine which values |
294
|
|
|
# are most reliable |
295
|
|
|
else: |
296
|
|
|
logging.debug("res: choosing a boundary") |
297
|
|
|
self.expand_direction = \ |
298
|
|
|
self._choose_boundary(near_boundary, signs) |
299
|
|
|
# case that the results are close to different boundaries |
300
|
|
|
# Analyse reliability and choose direction |
301
|
|
|
else: |
302
|
|
|
logging.debug("res: at the edge of the boundary") |
303
|
|
|
# Move on to the fine search |
304
|
|
|
self._set_final_process() |
305
|
|
|
self.warning_level = 1 # change this to be more descriptive *** |
306
|
|
|
else: |
307
|
|
|
logging.debug("result is not near the boundary") |
308
|
|
|
# Move on to the fine search |
309
|
|
|
self._set_final_process() |
310
|
|
|
# ===================================================================== |
311
|
|
|
|
312
|
|
|
def _choose_boundary(self, idx, signs): |
313
|
|
|
good, maybe, bad = self._get_reliability_levels() |
314
|
|
|
sign = self._check_entries(good, signs[good]) |
315
|
|
|
self.warning_level = 0 |
316
|
|
|
if not sign: |
317
|
|
|
sign = self._check_entries(maybe, signs[maybe]) |
318
|
|
|
self.warning_level = 1 |
319
|
|
|
if not sign: |
320
|
|
|
sign = self._check_entries(bad, signs[bad]) |
321
|
|
|
self.warning_level = 2 |
322
|
|
|
return sign |
323
|
|
|
|
324
|
|
|
def _check_entries(self, idx, signs): |
325
|
|
|
if np.any(idx): |
326
|
|
|
left, right = signs[signs < 0], signs[signs > 0] |
327
|
|
|
if not (left and right): |
328
|
|
|
# use all the good ones |
329
|
|
|
return 'left' if left else 'right' |
330
|
|
|
return None |
331
|
|
|
|
332
|
|
|
def _get_reliability_levels(self, final=False): |
333
|
|
|
in_datasets, out_datasets = \ |
334
|
|
|
self.get_datasets() if not final else self.get_original_datasets() |
335
|
|
|
reliability = np.squeeze(out_datasets[2].data[...]) |
336
|
|
|
logging.debug('reliability is %s', reliability) |
337
|
|
|
good = np.where(reliability == 1.0)[0] |
338
|
|
|
maybe = np.where(reliability == 0.5)[0] |
339
|
|
|
bad = np.where(reliability == 0.0)[0] |
340
|
|
|
return good, maybe, bad |
341
|
|
|
|
342
|
|
|
def final_post_process(self): |
343
|
|
|
|
344
|
|
|
# choose which values to include |
345
|
|
|
good, maybe, bad = self._get_reliability_levels(final=True) |
346
|
|
|
# Do I need to change the warning levels here? |
347
|
|
|
entries = good if np.any(good) else maybe if np.any(maybe) else bad |
348
|
|
|
self.warning_level = 0 if np.any(good) else 1 if np.any(maybe) else 2 |
349
|
|
|
logging.debug('sinograms used in final calculations are %s', entries) |
350
|
|
|
|
351
|
|
|
# do some curve fitting here |
352
|
|
|
# Get a handle on the original datasets |
353
|
|
|
in_dataset, out_dataset = self.get_original_datasets() |
354
|
|
|
cor_raw = np.squeeze(out_dataset[0].data[...])[entries] |
355
|
|
|
cor_fit = out_dataset[1].data[...] |
356
|
|
|
fit = np.zeros(cor_fit.shape) |
357
|
|
|
fit[:] = np.median(cor_raw) |
358
|
|
|
cor_fit = fit |
359
|
|
|
out_dataset[1].data[:] = cor_fit[:] |
360
|
|
|
|
361
|
|
|
self.populate_meta_data('cor_raw', cor_raw) |
362
|
|
|
self.populate_meta_data('centre_of_rotation', |
363
|
|
|
out_dataset[1].data[:].squeeze(axis=1)) |
364
|
|
|
|
365
|
|
|
def _set_final_process(self): |
366
|
|
|
self.final = True |
367
|
|
|
self.post_process = self.final_post_process |
368
|
|
|
in_dataset, out_dataset = self.get_datasets() |
369
|
|
|
self.set_iteration_datasets( |
370
|
|
|
self.get_iteration()+1, [in_dataset[0]], [out_dataset[0]]) |
371
|
|
|
|
372
|
|
|
def populate_meta_data(self, key, value): |
373
|
|
|
datasets = self.parameters['datasets_to_populate'] |
374
|
|
|
in_meta_data = self.get_in_meta_data()[0] |
375
|
|
|
in_meta_data.set(key, value) |
376
|
|
|
for name in datasets: |
377
|
|
|
self.exp.index['in_data'][name].meta_data.set(key, value) |
378
|
|
|
|
379
|
|
|
def setup(self): |
380
|
|
|
# set up the output dataset that is created by the plugin |
381
|
|
|
in_dataset, out_dataset = self.get_datasets() |
382
|
|
|
|
383
|
|
|
self.orig_full_shape = in_dataset[0].get_shape() |
384
|
|
|
|
385
|
|
|
# reduce the data as per data_subset parameter |
386
|
|
|
self.set_preview(in_dataset[0], self.parameters['preview']) |
387
|
|
|
|
388
|
|
|
in_pData, out_pData = self.get_plugin_datasets() |
389
|
|
|
in_pData[0].plugin_data_setup('SINOGRAM', self.get_max_frames()) |
390
|
|
|
# copy all required information from in_dataset[0] |
391
|
|
|
fullData = in_dataset[0] |
392
|
|
|
|
393
|
|
|
slice_dirs = np.array(in_dataset[0].get_slice_dimensions()) |
394
|
|
|
new_shape = (np.prod(np.array(fullData.get_shape())[slice_dirs]), 1) |
395
|
|
|
self.orig_shape = \ |
396
|
|
|
(np.prod(np.array(self.orig_full_shape)[slice_dirs]), 1) |
397
|
|
|
|
398
|
|
|
self._create_metadata_dataset(out_dataset[0], new_shape) |
399
|
|
|
self._create_metadata_dataset(out_dataset[1], self.orig_shape) |
400
|
|
|
self._create_metadata_dataset(out_dataset[2], new_shape) |
401
|
|
|
|
402
|
|
|
# output metric |
403
|
|
|
new_shape = (np.prod(np.array(fullData.get_shape())[slice_dirs]), 21) |
404
|
|
|
self._create_metadata_dataset(out_dataset[3], new_shape) |
405
|
|
|
|
406
|
|
|
out_pData[0].plugin_data_setup('METADATA', self.get_max_frames()) |
407
|
|
|
out_pData[1].plugin_data_setup('METADATA', self.get_max_frames()) |
408
|
|
|
out_pData[2].plugin_data_setup('METADATA', self.get_max_frames()) |
409
|
|
|
out_pData[3].plugin_data_setup('METADATA', self.get_max_frames()) |
410
|
|
|
|
411
|
|
|
def _create_metadata_dataset(self, data, shape): |
412
|
|
|
data.create_dataset(shape=shape, |
413
|
|
|
axis_labels=['x.pixels', 'y.pixels'], |
414
|
|
|
remove=True, |
415
|
|
|
transport='hdf5') |
416
|
|
|
data.add_pattern("METADATA", core_dims=(1,), slice_dims=(0,)) |
417
|
|
|
|
418
|
|
|
def nOutput_datasets(self): |
419
|
|
|
return 4 |
420
|
|
|
|
421
|
|
|
def get_max_frames(self): |
422
|
|
|
return 'single' |
423
|
|
|
|
424
|
|
|
def fix_transport(self): |
425
|
|
|
# This plugin requires communication between processes in the post |
426
|
|
|
# process, which it does via files |
427
|
|
|
return 'hdf5' |
428
|
|
|
|
429
|
|
|
def executive_summary(self): |
430
|
|
|
if self.warning_level == 0: |
431
|
|
|
msg = "Confidence in the centre value is high." |
432
|
|
|
elif self.warning_level == 1: |
433
|
|
|
msg = "Confidence in the centre value is average." |
434
|
|
|
else: |
435
|
|
|
msg = "Confidence in the centre value is low." |
436
|
|
|
return [msg] |
437
|
|
|
|
438
|
|
View Code Duplication |
def get_citation_information(self): |
|
|
|
|
439
|
|
|
cite_info = CitationInformation() |
440
|
|
|
cite_info.description = \ |
441
|
|
|
("The center of rotation for this reconstruction was calculated " + |
442
|
|
|
"automatically using the method described in this work") |
443
|
|
|
cite_info.bibtex = \ |
444
|
|
|
("@article{vo2014reliable,\n" + |
445
|
|
|
"title={Reliable method for calculating the center of rotation " + |
446
|
|
|
"in parallel-beam tomography},\n" + |
447
|
|
|
"author={Vo, Nghia T and Drakopoulos, Michael and Atwood, " + |
448
|
|
|
"Robert C and Reinhard, Christina},\n" + |
449
|
|
|
"journal={Optics Express},\n" + |
450
|
|
|
"volume={22},\n" + |
451
|
|
|
"number={16},\n" + |
452
|
|
|
"pages={19078--19086},\n" + |
453
|
|
|
"year={2014},\n" + |
454
|
|
|
"publisher={Optical Society of America}\n" + |
455
|
|
|
"}") |
456
|
|
|
cite_info.endnote = \ |
457
|
|
|
("%0 Journal Article\n" + |
458
|
|
|
"%T Reliable method for calculating the center of rotation in " + |
459
|
|
|
"parallel-beam tomography\n" + |
460
|
|
|
"%A Vo, Nghia T\n" + |
461
|
|
|
"%A Drakopoulos, Michael\n" + |
462
|
|
|
"%A Atwood, Robert C\n" + |
463
|
|
|
"%A Reinhard, Christina\n" + |
464
|
|
|
"%J Optics Express\n" + |
465
|
|
|
"%V 22\n" + |
466
|
|
|
"%N 16\n" + |
467
|
|
|
"%P 19078-19086\n" + |
468
|
|
|
"%@ 1094-4087\n" + |
469
|
|
|
"%D 2014\n" + |
470
|
|
|
"%I Optical Society of America") |
471
|
|
|
cite_info.doi = "https://doi.org/10.1364/OE.22.019078" |
472
|
|
|
return cite_info |
473
|
|
|
|