Source code for cil.processors.Normaliser

#  Copyright 2019 United Kingdom Research and Innovation
#  Copyright 2019 The University of Manchester
#
#  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.
#
# Authors:
# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt

from cil.framework import Processor, DataContainer, AcquisitionData,\
 AcquisitionGeometry, ImageGeometry, ImageData
import numpy

[docs] class Normaliser(Processor): '''Normalisation based on flat and dark This processor read in a AcquisitionData and normalises it based on the instrument reading with and without incident photons or neutrons. Input: AcquisitionData Parameter: 2D projection with flat field (or stack) 2D projection with dark field (or stack) Output: AcquisitionDataSet ''' def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5): kwargs = { 'flat_field' : flat_field, 'dark_field' : dark_field, # very small number. Used when there is a division by zero 'tolerance' : tolerance } #DataProcessor.__init__(self, **kwargs) super(Normaliser, self).__init__(**kwargs) if not flat_field is None: self.set_flat_field(flat_field) if not dark_field is None: self.set_dark_field(dark_field) def check_input(self, dataset): if dataset.number_of_dimensions == 3 or\ dataset.number_of_dimensions == 2: return True else: raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ .format(dataset.number_of_dimensions)) def set_dark_field(self, df): if type(df) is numpy.ndarray: if len(numpy.shape(df)) == 3: raise ValueError('Dark Field should be 2D') elif len(numpy.shape(df)) == 2: self.dark_field = df elif issubclass(type(df), DataContainer): self.dark_field = self.set_dark_field(df.as_array()) def set_flat_field(self, df): if type(df) is numpy.ndarray: if len(numpy.shape(df)) == 3: raise ValueError('Flat Field should be 2D') elif len(numpy.shape(df)) == 2: self.flat_field = df elif issubclass(type(df), DataContainer): self.flat_field = self.set_flat_field(df.as_array()) @staticmethod def Normalise_projection(projection, flat, dark, tolerance): a = (projection - dark) b = (flat-dark) with numpy.errstate(divide='ignore', invalid='ignore'): c = numpy.true_divide( a, b ) c[ ~ numpy.isfinite( c )] = tolerance # set to not zero if 0/0 return c
[docs] @staticmethod def estimate_normalised_error(projection, flat, dark, delta_flat, delta_dark): '''returns the estimated relative error of the normalised projection n = (projection - dark) / (flat - dark) Dn/n = (flat-dark + projection-dark)/((flat-dark)*(projection-dark))*(Df/f + Dd/d) ''' a = (projection - dark) b = (flat-dark) df = delta_flat / flat dd = delta_dark / dark rel_norm_error = (b + a) / (b * a) * (df + dd) return rel_norm_error
def process(self, out=None): projections = self.get_input() dark = self.dark_field flat = self.flat_field if projections.number_of_dimensions == 3: if not (projections.shape[1:] == dark.shape and \ projections.shape[1:] == flat.shape): raise ValueError('Flats/Dark and projections size do not match.') a = numpy.asarray( [ Normaliser.Normalise_projection( projection, flat, dark, self.tolerance) \ for projection in projections.as_array() ] ) elif projections.number_of_dimensions == 2: a = Normaliser.Normalise_projection(projections.as_array(), flat, dark, self.tolerance) if out is None: out = type(projections)( a , True, dimension_labels=projections.dimension_labels, geometry=projections.geometry) else: out.fill(a) return out