From 78f962def0bdb188efe8f72aac7f26838f5c4b62 Mon Sep 17 00:00:00 2001
From: Evelina Ametova <evelinaametova@gmail.com>
Date: Wed, 12 Jun 2019 11:17:40 +0100
Subject: data readers, writer, resizer to composite_data_container branch

---
 Wrappers/Python/ccpi/io/NEXUSDataReader.py  | 144 ++++++++++++++
 Wrappers/Python/ccpi/io/NEXUSDataWriter.py  | 153 +++++++++++++++
 Wrappers/Python/ccpi/io/NikonDataReader.py  | 282 ++++++++++++++++++++++++++++
 Wrappers/Python/ccpi/io/__init__.py         |   6 +-
 Wrappers/Python/ccpi/processors/Resizer.py  | 262 ++++++++++++++++++++++++++
 Wrappers/Python/ccpi/processors/__init__.py |  10 +
 6 files changed, 856 insertions(+), 1 deletion(-)
 create mode 100644 Wrappers/Python/ccpi/io/NEXUSDataReader.py
 create mode 100644 Wrappers/Python/ccpi/io/NEXUSDataWriter.py
 create mode 100644 Wrappers/Python/ccpi/io/NikonDataReader.py
 create mode 100755 Wrappers/Python/ccpi/processors/Resizer.py
 create mode 100755 Wrappers/Python/ccpi/processors/__init__.py

(limited to 'Wrappers/Python')

diff --git a/Wrappers/Python/ccpi/io/NEXUSDataReader.py b/Wrappers/Python/ccpi/io/NEXUSDataReader.py
new file mode 100644
index 0000000..a742dd5
--- /dev/null
+++ b/Wrappers/Python/ccpi/io/NEXUSDataReader.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Apr  3 10:30:25 2019
+
+@author: evelina
+"""
+
+
+import numpy
+import os
+import matplotlib.pyplot as plt
+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)
+'''
\ No newline at end of file
diff --git a/Wrappers/Python/ccpi/io/NEXUSDataWriter.py b/Wrappers/Python/ccpi/io/NEXUSDataWriter.py
new file mode 100644
index 0000000..d35e86b
--- /dev/null
+++ b/Wrappers/Python/ccpi/io/NEXUSDataWriter.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Thu May  2 10:11:20 2019
+
+@author: evelina
+"""
+
+
+import numpy
+import os
+import matplotlib.pyplot as plt
+from ccpi.framework import AcquisitionData, AcquisitionGeometry, ImageData, ImageGeometry
+from NikonDataReader import NikonDataReader
+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()
+'''
\ No newline at end of file
diff --git a/Wrappers/Python/ccpi/io/NikonDataReader.py b/Wrappers/Python/ccpi/io/NikonDataReader.py
new file mode 100644
index 0000000..6f02aa0
--- /dev/null
+++ b/Wrappers/Python/ccpi/io/NikonDataReader.py
@@ -0,0 +1,282 @@
+#!/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 matplotlib.pyplot as plt
+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
new file mode 100755
index 0000000..cba5897
--- /dev/null
+++ b/Wrappers/Python/ccpi/processors/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Apr 30 13:51:09 2019
+
+@author: ofn77899
+"""
+
+from .CenterOfRotationFinder import CenterOfRotationFinder
+from .Normalizer import Normalizer
+from .Resizer import Resizer
-- 
cgit v1.2.3