summaryrefslogtreecommitdiffstats
path: root/Wrappers/Python
diff options
context:
space:
mode:
authorEdoardo Pasca <edo.paskino@gmail.com>2019-06-12 14:21:07 +0100
committerEdoardo Pasca <edo.paskino@gmail.com>2019-06-12 14:21:07 +0100
commit25b16a2532d3fd470ce3cba58a40cdf7a3ea2a33 (patch)
tree1a05fd0468e19bf5dbe4a0531bdec4914856608a /Wrappers/Python
parent60adff8dc5eb28c64ca6786178bd38abecf355e0 (diff)
parentd7fa15c50ee9155ac24ecfa88ca74b111690532e (diff)
downloadframework-25b16a2532d3fd470ce3cba58a40cdf7a3ea2a33.tar.gz
framework-25b16a2532d3fd470ce3cba58a40cdf7a3ea2a33.tar.bz2
framework-25b16a2532d3fd470ce3cba58a40cdf7a3ea2a33.tar.xz
framework-25b16a2532d3fd470ce3cba58a40cdf7a3ea2a33.zip
Merge branch 'composite_operator_datacontainer' into demos
Conflicts: Wrappers/Python/ccpi/processors/__init__.py
Diffstat (limited to 'Wrappers/Python')
-rw-r--r--Wrappers/Python/ccpi/io/NEXUSDataReader.py143
-rw-r--r--Wrappers/Python/ccpi/io/NEXUSDataWriter.py151
-rw-r--r--Wrappers/Python/ccpi/io/NikonDataReader.py281
-rw-r--r--Wrappers/Python/ccpi/io/__init__.py6
-rwxr-xr-xWrappers/Python/ccpi/processors/Resizer.py262
-rwxr-xr-xWrappers/Python/ccpi/processors/__init__.py1
6 files changed, 843 insertions, 1 deletions
diff --git a/Wrappers/Python/ccpi/io/NEXUSDataReader.py b/Wrappers/Python/ccpi/io/NEXUSDataReader.py
new file mode 100644
index 0000000..cf67e27
--- /dev/null
+++ b/Wrappers/Python/ccpi/io/NEXUSDataReader.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Apr 3 10:30:25 2019
+
+@author: evelina
+"""
+
+
+import numpy
+import os
+from ccpi.framework import AcquisitionData, AcquisitionGeometry, ImageData, ImageGeometry
+
+
+h5pyAvailable = True
+try:
+ import h5py
+except:
+ h5pyAvailable = False
+
+
+class NEXUSDataReader(object):
+
+ def __init__(self,
+ **kwargs):
+
+ '''
+ Constructor
+
+ Input:
+
+ nexus_file full path to NEXUS file
+ '''
+
+ self.nexus_file = kwargs.get('nexus_file', None)
+
+ if self.nexus_file is not None:
+ self.set_up(nexus_file = self.nexus_file,
+ roi = self.roi,
+ binning = self.binning)
+
+ def set_up(self,
+ nexus_file = None):
+
+ self.nexus_file = nexus_file
+
+ # check that h5py library is installed
+ if (h5pyAvailable == False):
+ raise Exception('h5py is not available, cannot load NEXUS files.')
+
+ if self.nexus_file == None:
+ raise Exception('Path to nexus file is required.')
+
+ # check if nexus file exists
+ if not(os.path.isfile(self.nexus_file)):
+ raise Exception('File\n {}\n does not exist.'.format(self.nexus_file))
+
+ def load_data(self):
+
+ '''
+ Parse NEXUS file and returns either ImageData or Acquisition Data
+ depending on file content
+ '''
+
+ try:
+ with h5py.File(self.nexus_file,'r') as file:
+
+ if (file.attrs['creator'] != 'NEXUSDataWriter.py'):
+ raise Exception('We can parse only files created by NEXUSDataWriter.py')
+
+ ds_data = file['entry1/tomo_entry/data/data']
+ data = numpy.array(ds_data, dtype = 'float32')
+
+ dimension_labels = []
+
+ for i in range(data.ndim):
+ dimension_labels.append(ds_data.attrs['dim{}'.format(i)])
+
+ if ds_data.attrs['data_type'] == 'ImageData':
+ self._geometry = ImageGeometry(voxel_num_x = ds_data.attrs['voxel_num_x'],
+ voxel_num_y = ds_data.attrs['voxel_num_y'],
+ voxel_num_z = ds_data.attrs['voxel_num_z'],
+ voxel_size_x = ds_data.attrs['voxel_size_x'],
+ voxel_size_y = ds_data.attrs['voxel_size_y'],
+ voxel_size_z = ds_data.attrs['voxel_size_z'],
+ center_x = ds_data.attrs['center_x'],
+ center_y = ds_data.attrs['center_y'],
+ center_z = ds_data.attrs['center_z'],
+ channels = ds_data.attrs['channels'])
+
+ return ImageData(array = data,
+ deep_copy = False,
+ geometry = self._geometry,
+ dimension_labels = dimension_labels)
+
+ else: # AcquisitionData
+ self._geometry = AcquisitionGeometry(geom_type = ds_data.attrs['geom_type'],
+ dimension = ds_data.attrs['dimension'],
+ dist_source_center = ds_data.attrs['dist_source_center'],
+ dist_center_detector = ds_data.attrs['dist_center_detector'],
+ pixel_num_h = ds_data.attrs['pixel_num_h'],
+ pixel_size_h = ds_data.attrs['pixel_size_h'],
+ pixel_num_v = ds_data.attrs['pixel_num_v'],
+ pixel_size_v = ds_data.attrs['pixel_size_v'],
+ channels = ds_data.attrs['channels'],
+ angles = numpy.array(file['entry1/tomo_entry/data/rotation_angle'], dtype = 'float32'))
+ #angle_unit = file['entry1/tomo_entry/data/rotation_angle'].attrs['units'])
+
+ return AcquisitionData(array = data,
+ deep_copy = False,
+ geometry = self._geometry,
+ dimension_labels = dimension_labels)
+
+ except:
+ print("Error reading nexus file")
+ raise
+
+ def get_geometry(self):
+
+ '''
+ Return either ImageGeometry or AcquisitionGeometry
+ depepnding on file content
+ '''
+
+ return self._geometry
+
+
+'''
+# usage example
+reader = NEXUSDataReader()
+reader.set_up(nexus_file = '/home/evelina/test_nexus.nxs')
+acquisition_data = reader.load_data()
+print(acquisition_data)
+ag = reader.get_geometry()
+print(ag)
+
+reader = NEXUSDataReader()
+reader.set_up(nexus_file = '/home/evelina/test_nexus_im.nxs')
+image_data = reader.load_data()
+print(image_data)
+ig = reader.get_geometry()
+print(ig)
+'''
diff --git a/Wrappers/Python/ccpi/io/NEXUSDataWriter.py b/Wrappers/Python/ccpi/io/NEXUSDataWriter.py
new file mode 100644
index 0000000..f780f79
--- /dev/null
+++ b/Wrappers/Python/ccpi/io/NEXUSDataWriter.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Thu May 2 10:11:20 2019
+
+@author: evelina
+"""
+
+
+import numpy
+import os
+from ccpi.framework import AcquisitionData, AcquisitionGeometry, ImageData, ImageGeometry
+import datetime
+
+
+h5pyAvailable = True
+try:
+ import h5py
+except:
+ h5pyAvailable = False
+
+
+class NEXUSDataWriter(object):
+
+ def __init__(self,
+ **kwargs):
+
+ self.data_container = kwargs.get('data_container', None)
+ self.file_name = kwargs.get('file_name', None)
+
+ if ((self.data_container is not None) and (self.file_name is not None)):
+ self.set_up(data_container = self.data_container,
+ file_name = self.file_name)
+
+ def set_up(self,
+ data_container = None,
+ file_name = None):
+
+ self.data_container = data_container
+ self.file_name = file_name
+
+ if not ((isinstance(self.data_container, ImageData)) or
+ (isinstance(self.data_container, AcquisitionData))):
+ raise Exception('Writer supports only following data types:\n' +
+ ' - ImageData\n - AcquisitionData')
+
+ # check that h5py library is installed
+ if (h5pyAvailable == False):
+ raise Exception('h5py is not available, cannot load NEXUS files.')
+
+ def write_file(self):
+
+ # if the folder does not exist, create the folder
+ if not os.path.isdir(os.path.dirname(self.file_name)):
+ os.mkdir(os.path.dirname(self.file_name))
+
+ # create the file
+ with h5py.File(self.file_name, 'w') as f:
+
+ # give the file some important attributes
+ f.attrs['file_name'] = self.file_name
+ f.attrs['file_time'] = str(datetime.datetime.utcnow())
+ f.attrs['creator'] = 'NEXUSDataWriter.py'
+ f.attrs['NeXus_version'] = '4.3.0'
+ f.attrs['HDF5_Version'] = h5py.version.hdf5_version
+ f.attrs['h5py_version'] = h5py.version.version
+
+ # create the NXentry group
+ nxentry = f.create_group('entry1/tomo_entry')
+ nxentry.attrs['NX_class'] = 'NXentry'
+
+ # create dataset to store data
+ ds_data = f.create_dataset('entry1/tomo_entry/data/data',
+ (self.data_container.as_array().shape),
+ dtype = 'float32',
+ data = self.data_container.as_array())
+
+ # set up dataset attributes
+ if (isinstance(self.data_container, ImageData)):
+ ds_data.attrs['data_type'] = 'ImageData'
+ else:
+ ds_data.attrs['data_type'] = 'AcquisitionData'
+
+ for i in range(self.data_container.as_array().ndim):
+ ds_data.attrs['dim{}'.format(i)] = self.data_container.dimension_labels[i]
+
+ if (isinstance(self.data_container, AcquisitionData)):
+ ds_data.attrs['geom_type'] = self.data_container.geometry.geom_type
+ ds_data.attrs['dimension'] = self.data_container.geometry.dimension
+ ds_data.attrs['dist_source_center'] = self.data_container.geometry.dist_source_center
+ ds_data.attrs['dist_center_detector'] = self.data_container.geometry.dist_center_detector
+ ds_data.attrs['pixel_num_h'] = self.data_container.geometry.pixel_num_h
+ ds_data.attrs['pixel_size_h'] = self.data_container.geometry.pixel_size_h
+ ds_data.attrs['pixel_num_v'] = self.data_container.geometry.pixel_num_v
+ ds_data.attrs['pixel_size_v'] = self.data_container.geometry.pixel_size_v
+ ds_data.attrs['channels'] = self.data_container.geometry.channels
+ ds_data.attrs['n_angles'] = self.data_container.geometry.angles.shape[0]
+
+ # create the dataset to store rotation angles
+ ds_angles = f.create_dataset('entry1/tomo_entry/data/rotation_angle',
+ (self.data_container.geometry.angles.shape),
+ dtype = 'float32',
+ data = self.data_container.geometry.angles)
+
+ #ds_angles.attrs['units'] = self.data_container.geometry.angle_unit
+
+ else: # ImageData
+
+ ds_data.attrs['voxel_num_x'] = self.data_container.geometry.voxel_num_x
+ ds_data.attrs['voxel_num_y'] = self.data_container.geometry.voxel_num_y
+ ds_data.attrs['voxel_num_z'] = self.data_container.geometry.voxel_num_z
+ ds_data.attrs['voxel_size_x'] = self.data_container.geometry.voxel_size_x
+ ds_data.attrs['voxel_size_y'] = self.data_container.geometry.voxel_size_y
+ ds_data.attrs['voxel_size_z'] = self.data_container.geometry.voxel_size_z
+ ds_data.attrs['center_x'] = self.data_container.geometry.center_x
+ ds_data.attrs['center_y'] = self.data_container.geometry.center_y
+ ds_data.attrs['center_z'] = self.data_container.geometry.center_z
+ ds_data.attrs['channels'] = self.data_container.geometry.channels
+
+
+'''
+# usage example
+xtek_file = '/home/evelina/nikon_data/SophiaBeads_256_averaged.xtekct'
+reader = NikonDataReader()
+reader.set_up(xtek_file = xtek_file,
+ binning = [3, 1],
+ roi = [200, 500, 1500, 2000],
+ normalize = True)
+
+data = reader.load_projections()
+ag = reader.get_geometry()
+
+writer = NEXUSDataWriter()
+writer.set_up(file_name = '/home/evelina/test_nexus.nxs',
+ data_container = data)
+
+writer.write_file()
+
+ig = ImageGeometry(voxel_num_x = 100,
+ voxel_num_y = 100)
+
+im = ImageData(array = numpy.zeros((100, 100), dtype = 'float'),
+ geometry = ig)
+
+im_writer = NEXUSDataWriter()
+
+writer.set_up(file_name = '/home/evelina/test_nexus_im.nxs',
+ data_container = im)
+
+writer.write_file()
+'''
diff --git a/Wrappers/Python/ccpi/io/NikonDataReader.py b/Wrappers/Python/ccpi/io/NikonDataReader.py
new file mode 100644
index 0000000..703b65b
--- /dev/null
+++ b/Wrappers/Python/ccpi/io/NikonDataReader.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Apr 3 10:30:25 2019
+
+@author: evelina
+"""
+
+from ccpi.framework import AcquisitionData, AcquisitionGeometry
+import numpy
+import os
+
+
+pilAvailable = True
+try:
+ from PIL import Image
+except:
+ pilAvailable = False
+
+
+class NikonDataReader(object):
+
+ def __init__(self,
+ **kwargs):
+ '''
+ Constructor
+
+ Input:
+
+ xtek_file full path to .xtexct file
+
+ roi region-of-interest to load. If roi = -1 (default),
+ full projections will be loaded. Otherwise roi is
+ given by [(row0, row1), (column0, column1)], where
+ row0, column0 are coordinates of top left corner and
+ row1, column1 are coordinates of bottom right corner.
+
+ binning number of pixels to bin (combine) along 0 (column)
+ and 1 (row) dimension. If binning = [1, 1] (default),
+ projections in original resolution are loaded. Note,
+ if binning[0] != binning[1], then loaded projections
+ will have anisotropic pixels, which are currently not
+ supported by the Framework
+
+ normalize normalize loaded projections by detector
+ white level (I_0). Default value is False,
+ i.e. no normalization.
+
+ flip default = False, flip projections in the left-right direction
+
+ '''
+
+ self.xtek_file = kwargs.get('xtek_file', None)
+ self.roi = kwargs.get('roi', -1)
+ self.binning = kwargs.get('binning', [1, 1])
+ self.normalize = kwargs.get('normalize', False)
+ self.flip = kwargs.get('flip', False)
+
+ if self.xtek_file is not None:
+ self.set_up(xtek_file = self.xtek_file,
+ roi = self.roi,
+ binning = self.binning,
+ normalize = self.normalize,
+ flip = self.flip)
+
+ def set_up(self,
+ xtek_file = None,
+ roi = -1,
+ binning = [1, 1],
+ normalize = False,
+ flip = False):
+
+ self.xtek_file = xtek_file
+ self.roi = roi
+ self.binning = binning
+ self.normalize = normalize
+ self.flip = flip
+
+ if self.xtek_file == None:
+ raise Exception('Path to xtek file is required.')
+
+ # check if xtek file exists
+ if not(os.path.isfile(self.xtek_file)):
+ raise Exception('File\n {}\n does not exist.'.format(self.xtek_file))
+
+ # check that PIL library is installed
+ if (pilAvailable == False):
+ raise Exception("PIL (pillow) is not available, cannot load TIFF files.")
+
+ # parse xtek file
+ with open(self.xtek_file, 'r') as f:
+ content = f.readlines()
+
+ content = [x.strip() for x in content]
+
+ for line in content:
+ # filename of TIFF files
+ if line.startswith("Name"):
+ self._experiment_name = line.split('=')[1]
+ # number of projections
+ elif line.startswith("Projections"):
+ num_projections = int(line.split('=')[1])
+ # white level - used for normalization
+ elif line.startswith("WhiteLevel"):
+ self._white_level = float(line.split('=')[1])
+ # number of pixels along Y axis
+ elif line.startswith("DetectorPixelsY"):
+ pixel_num_v_0 = int(line.split('=')[1])
+ # number of pixels along X axis
+ elif line.startswith("DetectorPixelsX"):
+ pixel_num_h_0 = int(line.split('=')[1])
+ # pixel size along X axis
+ elif line.startswith("DetectorPixelSizeX"):
+ pixel_size_h_0 = float(line.split('=')[1])
+ # pixel size along Y axis
+ elif line.startswith("DetectorPixelSizeY"):
+ pixel_size_v_0 = float(line.split('=')[1])
+ # source to center of rotation distance
+ elif line.startswith("SrcToObject"):
+ source_x = float(line.split('=')[1])
+ # source to detector distance
+ elif line.startswith("SrcToDetector"):
+ detector_x = float(line.split('=')[1])
+ # initial angular position of a rotation stage
+ elif line.startswith("InitialAngle"):
+ initial_angle = float(line.split('=')[1])
+ # angular increment (in degrees)
+ elif line.startswith("AngularStep"):
+ angular_step = float(line.split('=')[1])
+
+ if self.roi == -1:
+ self._roi_par = [(0, pixel_num_v_0), \
+ (0, pixel_num_h_0)]
+ else:
+ self._roi_par = self.roi.copy()
+ if self._roi_par[0] == -1:
+ self._roi_par[0] = (0, pixel_num_v_0)
+ if self._roi_par[1] == -1:
+ self._roi_par[1] = (0, pixel_num_h_0)
+
+ # calculate number of pixels and pixel size
+ if (self.binning == [1, 1]):
+ pixel_num_v = self._roi_par[0][1] - self._roi_par[0][0]
+ pixel_num_h = self._roi_par[1][1] - self._roi_par[1][0]
+ pixel_size_v = pixel_size_v_0
+ pixel_size_h = pixel_size_h_0
+ else:
+ pixel_num_v = (self._roi_par[0][1] - self._roi_par[0][0]) // self.binning[0]
+ pixel_num_h = (self._roi_par[1][1] - self._roi_par[1][0]) // self.binning[1]
+ pixel_size_v = pixel_size_v_0 * self.binning[0]
+ pixel_size_h = pixel_size_h_0 * self.binning[1]
+
+ '''
+ Parse the angles file .ang or _ctdata.txt file and returns the angles
+ as an numpy array.
+ '''
+ input_path = os.path.dirname(self.xtek_file)
+ angles_ctdata_file = os.path.join(input_path, '_ctdata.txt')
+ angles_named_file = os.path.join(input_path, self._experiment_name+'.ang')
+ angles = numpy.zeros(num_projections, dtype = 'float')
+
+ # look for _ctdata.txt
+ if os.path.exists(angles_ctdata_file):
+ # read txt file with angles
+ with open(angles_ctdata_file) as f:
+ content = f.readlines()
+ # skip firt three lines
+ # read the middle value of 3 values in each line as angles in degrees
+ index = 0
+ for line in content[3:]:
+ angles[index] = float(line.split(' ')[1])
+ index += 1
+ angles = angles + initial_angle
+
+ # look for ang file
+ elif os.path.exists(angles_named_file):
+ # read the angles file which is text with first line as header
+ with open(angles_named_file) as f:
+ content = f.readlines()
+ # skip first line
+ index = 0
+ for line in content[1:]:
+ angles[index] = float(line.split(':')[1])
+ index += 1
+ angles = numpy.flipud(angles + initial_angle) # angles are in the reverse order
+
+ else: # calculate angles based on xtek file
+ angles = initial_angle + angular_step * range(num_projections)
+
+ # fill in metadata
+ self._ag = AcquisitionGeometry(geom_type = 'cone',
+ dimension = '3D',
+ angles = angles,
+ pixel_num_h = pixel_num_h,
+ pixel_size_h = pixel_size_h,
+ pixel_num_v = pixel_num_v,
+ pixel_size_v = pixel_size_v,
+ dist_source_center = source_x,
+ dist_center_detector = detector_x - source_x,
+ channels = 1,
+ angle_unit = 'degree')
+
+ def get_geometry(self):
+
+ '''
+ Return AcquisitionGeometry object
+ '''
+
+ return self._ag
+
+ def load_projections(self):
+
+ '''
+ Load projections and return AcquisitionData container
+ '''
+
+ # get path to projections
+ path_projection = os.path.dirname(self.xtek_file)
+
+ # get number of projections
+ num_projections = numpy.shape(self._ag.angles)[0]
+
+ # allocate array to store projections
+ data = numpy.zeros((num_projections, self._ag.pixel_num_v, self._ag.pixel_num_h), dtype = float)
+
+ for i in range(num_projections):
+
+ filename = (path_projection + '/' + self._experiment_name + '_{:04d}.tif').format(i + 1)
+
+ try:
+ tmp = numpy.asarray(Image.open(filename), dtype = float)
+ except:
+ print('Error reading\n {}\n file.'.format(filename))
+ raise
+
+ if (self.binning == [1, 1]):
+ data[i, :, :] = tmp[self._roi_par[0][0]:self._roi_par[0][1], self._roi_par[1][0]:self._roi_par[1][1]]
+ else:
+ shape = (self._ag.pixel_num_v, self.binning[0],
+ self._ag.pixel_num_h, self.binning[1])
+ data[i, :, :] = tmp[self._roi_par[0][0]:(self._roi_par[0][0] + (((self._roi_par[0][1] - self._roi_par[0][0]) // self.binning[0]) * self.binning[0])), \
+ self._roi_par[1][0]:(self._roi_par[1][0] + (((self._roi_par[1][1] - self._roi_par[1][0]) // self.binning[1]) * self.binning[1]))].reshape(shape).mean(-1).mean(1)
+
+ if (self.normalize):
+ data /= self._white_level
+ data[data > 1] = 1
+
+ if self.flip:
+ return AcquisitionData(array = data[:, :, ::-1],
+ deep_copy = False,
+ geometry = self._ag,
+ dimension_labels = ['angle', \
+ 'vertical', \
+ 'horizontal'])
+ else:
+ return AcquisitionData(array = data,
+ deep_copy = False,
+ geometry = self._ag,
+ dimension_labels = ['angle', \
+ 'vertical', \
+ 'horizontal'])
+
+
+'''
+# usage example
+xtek_file = '/home/evelina/nikon_data/SophiaBeads_256_averaged.xtekct'
+reader = NikonDataReader()
+reader.set_up(xtek_file = xtek_file,
+ binning = [1, 1],
+ roi = -1,
+ normalize = True,
+ flip = True)
+
+data = reader.load_projections()
+print(data)
+ag = reader.get_geometry()
+print(ag)
+
+plt.imshow(data.as_array()[1, :, :])
+plt.show()
+'''
diff --git a/Wrappers/Python/ccpi/io/__init__.py b/Wrappers/Python/ccpi/io/__init__.py
index 9233d7a..455faba 100644
--- a/Wrappers/Python/ccpi/io/__init__.py
+++ b/Wrappers/Python/ccpi/io/__init__.py
@@ -15,4 +15,8 @@
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
-# limitations under the License. \ No newline at end of file
+# limitations under the License.
+
+from .NEXUSDataReader import NEXUSDataReader
+from .NEXUSDataWriter import NEXUSDataWriter
+from .NikonDataReader import NikonDataReader
diff --git a/Wrappers/Python/ccpi/processors/Resizer.py b/Wrappers/Python/ccpi/processors/Resizer.py
new file mode 100755
index 0000000..7509a90
--- /dev/null
+++ b/Wrappers/Python/ccpi/processors/Resizer.py
@@ -0,0 +1,262 @@
+# -*- coding: utf-8 -*-
+# This work is part of the Core Imaging Library developed by
+# Visual Analytics and Imaging System Group of the Science Technology
+# Facilities Council, STFC
+
+# Copyright 2018 Edoardo Pasca
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+from ccpi.framework import DataProcessor, AcquisitionData, ImageData
+import warnings
+
+
+class Resizer(DataProcessor):
+
+ def __init__(self,
+ roi = -1,
+ binning = 1):
+
+ '''
+ Constructor
+
+ Input:
+
+ roi region-of-interest to crop. If roi = -1 (default), then no crop.
+ Otherwise roi is given by a list with ndim elements,
+ where each element is either -1 if no crop along this
+ dimension or a tuple with beginning and end coodinates to crop to.
+ Example:
+ to crop 4D array along 2nd dimension:
+ roi = [-1, -1, (100, 900), -1]
+
+ binning number of pixels to bin (combine) along each dimension.
+ If binning = 1, then projections in original resolution are loaded.
+ Otherwise, binning is given by a list with ndim integers.
+ Example:
+ to rebin 3D array along 1st direction:
+ binning = [1, 5, 1]
+ '''
+
+ kwargs = {'roi': roi,
+ 'binning': binning}
+
+ super(Resizer, self).__init__(**kwargs)
+
+ def check_input(self, data):
+ if not ((isinstance(data, ImageData)) or
+ (isinstance(data, AcquisitionData))):
+ raise Exception('Processor supports only following data types:\n' +
+ ' - ImageData\n - AcquisitionData')
+ elif (data.geometry == None):
+ raise Exception('Geometry is not defined.')
+ else:
+ return True
+
+ def process(self):
+
+ data = self.get_input()
+ ndim = len(data.dimension_labels)
+
+ geometry_0 = data.geometry
+ geometry = geometry_0.clone()
+
+ if (self.roi == -1):
+ roi_par = [-1] * ndim
+ else:
+ roi_par = self.roi.copy()
+ if (len(roi_par) != ndim):
+ raise Exception('Number of dimensions and number of elements in roi parameter do not match')
+
+ if (self.binning == 1):
+ binning = [1] * ndim
+ else:
+ binning = self.binning.copy()
+ if (len(binning) != ndim):
+ raise Exception('Number of dimensions and number of elements in binning parameter do not match')
+
+ if (isinstance(data, ImageData)):
+ if ((all(x == -1 for x in roi_par)) and (all(x == 1 for x in binning))):
+ for key in data.dimension_labels:
+ if data.dimension_labels[key] == 'channel':
+ geometry.channels = geometry_0.channels
+ roi_par[key] = (0, geometry.channels)
+ elif data.dimension_labels[key] == 'horizontal_y':
+ geometry.voxel_size_y = geometry_0.voxel_size_y
+ geometry.voxel_num_y = geometry_0.voxel_num_y
+ roi_par[key] = (0, geometry.voxel_num_y)
+ elif data.dimension_labels[key] == 'vertical':
+ geometry.voxel_size_z = geometry_0.voxel_size_z
+ geometry.voxel_num_z = geometry_0.voxel_num_z
+ roi_par[key] = (0, geometry.voxel_num_z)
+ elif data.dimension_labels[key] == 'horizontal_x':
+ geometry.voxel_size_x = geometry_0.voxel_size_x
+ geometry.voxel_num_x = geometry_0.voxel_num_x
+ roi_par[key] = (0, geometry.voxel_num_x)
+ else:
+ for key in data.dimension_labels:
+ if data.dimension_labels[key] == 'channel':
+ if (roi_par[key] != -1):
+ geometry.channels = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0] + ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.channels = geometry_0.channels // binning[key]
+ roi_par[key] = (0, geometry.channels * binning[key])
+ elif data.dimension_labels[key] == 'horizontal_y':
+ if (roi_par[key] != -1):
+ geometry.voxel_num_y = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ geometry.voxel_size_y = geometry_0.voxel_size_y * binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0] + ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.voxel_num_y = geometry_0.voxel_num_y // binning[key]
+ geometry.voxel_size_y = geometry_0.voxel_size_y * binning[key]
+ roi_par[key] = (0, geometry.voxel_num_y * binning[key])
+ elif data.dimension_labels[key] == 'vertical':
+ if (roi_par[key] != -1):
+ geometry.voxel_num_z = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ geometry.voxel_size_z = geometry_0.voxel_size_z * binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0] + ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.voxel_num_z = geometry_0.voxel_num_z // binning[key]
+ geometry.voxel_size_z = geometry_0.voxel_size_z * binning[key]
+ roi_par[key] = (0, geometry.voxel_num_z * binning[key])
+ elif data.dimension_labels[key] == 'horizontal_x':
+ if (roi_par[key] != -1):
+ geometry.voxel_num_x = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ geometry.voxel_size_x = geometry_0.voxel_size_x * binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0]+ ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.voxel_num_x = geometry_0.voxel_num_x // binning[key]
+ geometry.voxel_size_x = geometry_0.voxel_size_x * binning[key]
+ roi_par[key] = (0, geometry.voxel_num_x * binning[key])
+
+ else: # AcquisitionData
+ if ((all(x == -1 for x in roi_par)) and (all(x == 1 for x in binning))):
+ for key in data.dimension_labels:
+ if data.dimension_labels[key] == 'channel':
+ geometry.channels = geometry_0.channels
+ roi_par[key] = (0, geometry.channels)
+ elif data.dimension_labels[key] == 'angle':
+ geometry.angles = geometry_0.angles
+ roi_par[key] = (0, len(geometry.angles))
+ elif data.dimension_labels[key] == 'vertical':
+ geometry.pixel_size_v = geometry_0.pixel_size_v
+ geometry.pixel_num_v = geometry_0.pixel_num_v
+ roi_par[key] = (0, geometry.pixel_num_v)
+ elif data.dimension_labels[key] == 'horizontal':
+ geometry.pixel_size_h = geometry_0.pixel_size_h
+ geometry.pixel_num_h = geometry_0.pixel_num_h
+ roi_par[key] = (0, geometry.pixel_num_h)
+ else:
+ for key in data.dimension_labels:
+ if data.dimension_labels[key] == 'channel':
+ if (roi_par[key] != -1):
+ geometry.channels = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0] + ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.channels = geometry_0.channels // binning[key]
+ roi_par[key] = (0, geometry.channels * binning[key])
+ elif data.dimension_labels[key] == 'angle':
+ if (roi_par[key] != -1):
+ geometry.angles = geometry_0.angles[roi_par[key][0]:roi_par[key][1]]
+ else:
+ geometry.angles = geometry_0.angles
+ roi_par[key] = (0, len(geometry.angles))
+ if (binning[key] != 1):
+ binning[key] = 1
+ warnings.warn('Rebinning in angular dimensions is not supported: \n binning[{}] is set to 1.'.format(key))
+ elif data.dimension_labels[key] == 'vertical':
+ if (roi_par[key] != -1):
+ geometry.pixel_num_v = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ geometry.pixel_size_v = geometry_0.pixel_size_v * binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0] + ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.pixel_num_v = geometry_0.pixel_num_v // binning[key]
+ geometry.pixel_size_v = geometry_0.pixel_size_v * binning[key]
+ roi_par[key] = (0, geometry.pixel_num_v * binning[key])
+ elif data.dimension_labels[key] == 'horizontal':
+ if (roi_par[key] != -1):
+ geometry.pixel_num_h = (roi_par[key][1] - roi_par[key][0]) // binning[key]
+ geometry.pixel_size_h = geometry_0.pixel_size_h * binning[key]
+ roi_par[key] = (roi_par[key][0], roi_par[key][0] + ((roi_par[key][1] - roi_par[key][0]) // binning[key]) * binning[key])
+ else:
+ geometry.pixel_num_h = geometry_0.pixel_num_h // binning[key]
+ geometry.pixel_size_h = geometry_0.pixel_size_h * binning[key]
+ roi_par[key] = (0, geometry.pixel_num_h * binning[key])
+
+ if ndim == 2:
+ n_pix_0 = (roi_par[0][1] - roi_par[0][0]) // binning[0]
+ n_pix_1 = (roi_par[1][1] - roi_par[1][0]) // binning[1]
+ shape = (n_pix_0, binning[0],
+ n_pix_1, binning[1])
+ data_resized = data.as_array()[roi_par[0][0]:(roi_par[0][0] + n_pix_0 * binning[0]),
+ roi_par[1][0]:(roi_par[1][0] + n_pix_1 * binning[1])].reshape(shape).mean(-1).mean(1)
+ if ndim == 3:
+ n_pix_0 = (roi_par[0][1] - roi_par[0][0]) // binning[0]
+ n_pix_1 = (roi_par[1][1] - roi_par[1][0]) // binning[1]
+ n_pix_2 = (roi_par[2][1] - roi_par[2][0]) // binning[2]
+ shape = (n_pix_0, binning[0],
+ n_pix_1, binning[1],
+ n_pix_2, binning[2])
+ data_resized = data.as_array()[roi_par[0][0]:(roi_par[0][0] + n_pix_0 * binning[0]),
+ roi_par[1][0]:(roi_par[1][0] + n_pix_1 * binning[1]),
+ roi_par[2][0]:(roi_par[2][0] + n_pix_2 * binning[2])].reshape(shape).mean(-1).mean(1).mean(2)
+ if ndim == 4:
+ n_pix_0 = (roi_par[0][1] - roi_par[0][0]) // binning[0]
+ n_pix_1 = (roi_par[1][1] - roi_par[1][0]) // binning[1]
+ n_pix_2 = (roi_par[2][1] - roi_par[2][0]) // binning[2]
+ n_pix_3 = (roi_par[3][1] - roi_par[3][0]) // binning[3]
+ shape = (n_pix_0, binning[0],
+ n_pix_1, binning[1],
+ n_pix_2, binning[2],
+ n_pix_3, binning[3])
+ data_resized = data.as_array()[roi_par[0][0]:(roi_par[0][0] + n_pix_0 * binning[0]),
+ roi_par[1][0]:(roi_par[1][0] + n_pix_1 * binning[1]),
+ roi_par[2][0]:(roi_par[2][0] + n_pix_2 * binning[2]),
+ roi_par[3][0]:(roi_par[3][0] + n_pix_3 * binning[3])].reshape(shape).mean(-1).mean(1).mean(2).mean(3)
+
+ out = type(data)(array = data_resized,
+ deep_copy = False,
+ dimension_labels = data.dimension_labels,
+ geometry = geometry)
+
+ return out
+
+
+'''
+#usage exaample
+ig = ImageGeometry(voxel_num_x = 200,
+ voxel_num_y = 200,
+ voxel_num_z = 200,
+ voxel_size_x = 1,
+ voxel_size_y = 1,
+ voxel_size_z = 1,
+ center_x = 0,
+ center_y = 0,
+ center_z = 0,
+ channels = 200)
+
+im = ImageData(array = numpy.zeros((200, 200, 200, 200)),
+ geometry = ig,
+ deep_copy = False,
+ dimension_labels = ['channel',\
+ 'vertical',\
+ 'horizontal_y',\
+ 'horizontal_x'])
+
+
+resizer = Resizer(binning = [1, 1, 7, 1], roi = -1)
+resizer.input = im
+data_resized = resizer.process()
+print(data_resized)
+'''
diff --git a/Wrappers/Python/ccpi/processors/__init__.py b/Wrappers/Python/ccpi/processors/__init__.py
index f8d050e..cba5897 100755
--- a/Wrappers/Python/ccpi/processors/__init__.py
+++ b/Wrappers/Python/ccpi/processors/__init__.py
@@ -7,3 +7,4 @@ Created on Tue Apr 30 13:51:09 2019
from .CenterOfRotationFinder import CenterOfRotationFinder
from .Normalizer import Normalizer
+from .Resizer import Resizer