from numpy import ndarray, ones, argmax, arange, arctan, tan, pi, mean, max, min from scipy.optimize import curve_fit from scipy.signal import convolve, find_peaks from scipy.ndimage import rotate from classes.science.border import Border from classes.science.calibration_spectrum import CalibrationSpectrum from function.utils import find_point, fill, find_peak_low_high from function.fit import linear from logging import getLogger from gettext import gettext as _ class Plate: """ Matrix of pixel """ def __init__( self , data ): if not isinstance( data , ndarray ): raise TypeError( _( 'data must be a ndarray' ) ) if len( data.shape ) != 2: raise ValueError( _( 'data must be a 2d matrix' ) ) self.data = data self.set_border()\ .rotate()\ .set_spectrum()\ .set_calibration_spectrum() def compress( self ): """ Compress the plate data to fit the biggest dimension in a 2000 pixels axis and the smallest in a 200 pixels axis at minimum. Return the compressed data and the compression factor used. """ min_factor = max( self.data.shape ) // 2000 # min factor to have a side # with a maximum of 1000 pixels max_factor = min( self.data.shape ) // 200 # max factor to have # a side with a minimum of 100 pixel if min_factor < max_factor: factor = int( mean( ( max_factor , min_factor ) ) ) else: # the smallest side will be less than 100 pixels with the # minimum compression factor logger = getLogger( 'naroo reader' ) logger.warning( _( ( 'slow compression: ratio between height and width' ' is greater than 10 ({ratio:.2f})' ) ).format( ratio = max( self.size() ) / min( self.size() ) ) ) factor = max_factor return self.data[ : : factor, : : factor, ] , factor def get_points( self , compressed ): first_column = find_point( compressed[ :, 0, ], 0, ) last_column = find_point( compressed[ : , - 1, ], compressed.shape[1] - 1, ) first_line = find_point( compressed[ 0, :, ] , 0 , 'y', ) if len( first_line ) < 2: last_column = last_column[ 1 : ] if len( first_line ) < 3: first_line = [] else: first_line = first_line[ 1 : - 1 ] last_line = find_point( compressed[ - 1, : , ] , compressed.shape[0] - 1, 'y' , ) if len( last_line ) < 2: last_column = last_column[ : - 1 ] if len( last_line ) < 3: last_line = [] else: last_line = last_line[ 1 : - 1 ] return first_column + last_column + first_line + last_line def rotate( self ): """ Auto-rotate to be vertically and horizontally aligned """ indexes_max = argmax( convolve( self.data[ 1 * self.border.y.size() // 4: 3 * self.border.y.size() // 4, 1 * self.border.x.size() // 4: 3 * self.border.x.size() // 4 ] , ones( ( 500 , 1 ) ), 'valid' , ) , axis = 0, ) abciss = arange( 1 * self.border.x.size() // 4, 3 * self.border.x.size() // 4 ) fit_result = curve_fit( linear , abciss , indexes_max, )[0] angle = arctan( fit_result[0] ) * pi / 180 # rad diff = int( # adjust height border tan( angle ) * ( self.border.x.size() ) ) self.data = rotate( self.data, angle , ) self.border.y.min -= diff self.border.y.max -= diff return self def set_border( self ): """ Set current border (without area outside the plate) """ compressed , factor = self.compress() points = self.get_points( compressed ) self.border = Border() self.border.x.min = 0 self.border.x.max = compressed.shape[1] - 1 self.border.y.min = 0 self.border.y.max = compressed.shape[0] - 1 extremum = [] x_half = compressed.shape[1] // 2 y_half = compressed.shape[0] // 2 for index in range( len( points ) ): point = points[ index ] point[0] -= int( compressed.shape[0] == point[0] ) # keep in point[1] -= int( compressed.shape[1] == point[1] ) # range taken_points = fill( compressed, point , 2000 , # intensity threshold ) x = [ taken_point[1] for taken_point in taken_points ] y = [ taken_point[0] for taken_point in taken_points ] if len( x ) > 5 and len( y ) > 5: if max( x ) < x_half: if self.border.x.min < max( x ): self.border.x.min = max( x ) # biggest min elif min( x ) > x_half: # elif to only accept one side if self.border.x.max > min( x ): self.border.x.max = min( x ) # smallest max elif max( y ) < y_half: if self.border.y.min < max( y ): self.border.y.min = max( y ) # same elif min( y ) > y_half: if self.border.y.max > min( y ): self.border.y.max = min( y ) # same offset = 3 self.border.x.min += offset self.border.y.min += offset self.border.x.max -= offset self.border.y.min -= offset self.border.scale( factor ) return self def set_calibration_spectrum( self ): """ Set calibration sprectrum area """ self.calibration_spectrum = CalibrationSpectrum() def indicator( list_ , matrix ): """ Define an indicator which define if the horizontal slice has a chance to be a part of a calibration """ avg = mean( matrix ) if mean( list_ ) > 0.75 * avg: return 0 if mean( list_ ) < 0.25 * avg: return 1 positions = where( list_ > mean( list_ ) )[0] if len( positions ) < 100: return 2 if len( positions ) > 400: return 3 distance = mean( positions[ 1 : ] - positions[ : - 1 ] ) if distance < 10: return 4 return 10 list_ = [ indicator( self.data[ i , self.border.slice()[1], ] , self.data[ self.border.slice() ], ) for i in range( self.border.y.min, self.border.y.max, ) ] import matplotlib.pyplot as plt plt.plot( list_ ) plt.show() return self def set_spectrum( self ): """ Set spectrum area """ self.spectrum = Border() list_ = convolve( mean( self.data[ self.border.slice() ], axis = 1 , ) , ones( 200 ), 'valid' , ) indexes = find_peak_low_high( list_ , ( max( list_ ) + mean( list_ ) ) / 2, )[0] self.spectrum.y.min = indexes[0] + self.border.y.min + 100 self.spectrum.y.max = indexes[1] + self.border.y.min + 100 import matplotlib.pyplot as plt plt.imshow( self.data[ self.border.slice() ] , aspect = 'auto' ) plt.show() list_ = convolve( mean( self.data[ self.border.slice() ], axis = 0 , ) , ones( 200 ), 'valid' , ) indexes = find_peak_low_high( list_ , mean( list_ ) + max( list_ ) / 2, )[0] self.spectrum.x.min = indexes[0] + self.border.x.min + 100 self.spectrum.x.max = indexes[1] + self.border.x.min + 100 return self def size( self ): """ get plate size """ return self.data.shape