from numpy import ndarray, gradient, ones, argmax, arange, arctan, tan from scipy.optimize import curve_fit from scipy.signal import convolve from cv2 import getRotationMatrix2D, warpAffine, INTER_NEAREST from classes.science.border import Border from function.utils import find_point, fill 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() 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 ] """ matrix = ones( compressed.shape ) for taken_point in taken_points: matrix[ taken_point[0] , taken_point[1] ] = 0 import matplotlib.pyplot as plt plt.plot( [ point[1] ] , [ point[0] ] , linestyle = '' , marker = 'x' , markersize = 15 , markeredgecolor = 'red', markeredgewidth = 5 , ) plt.imshow( compressed , aspect = 'auto' ) plt.imshow( matrix , aspect = 'auto' , alpha = 0.5 ) plt.show() """ 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 ) 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 compress( self ): """ Compress the plate data to fit the biggest dimension in a 1000 pixels axis and the smallest in a 100 pixels axis at minimum. Return the compressed data and the compression factor used. """ min_factor = max( self.data.shape ) // 1000 # min factor to have a side # with a maximum of 1000 pixels max_factor = min( self.data.shape ) // 100 # 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 middle( self ): """ Get coordinate of center """ return ( self.size()[0] // 2, self.size()[1] // 2, ) 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] ) # rad diff = int( # adjust height border tan( angle ) * ( self.border.x.size() ) ) rotation_matrix = getRotationMatrix2D( self.middle(), angle , 1 , ) self.data = warpAffine( self.data , rotation_matrix, self.size() , flags = INTER_NEAREST, ) self.border.y.min -= diff self.border.y.max -= diff def size( self ): """ get plate size """ return self.data.shape