From 8b604d8ae2dd00d88db0ccfe097e23281891578c Mon Sep 17 00:00:00 2001 From: linarphy Date: Fri, 25 Aug 2023 17:24:12 +0200 Subject: [PATCH] Update to support i18n & logging - Add --log-file option - Add internationalization support with gettext - Add logging featur - Remove verbosity flag - Update string formatting - Fix Plate::set_border method --- classes/science/plate.py | 166 +++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 40 deletions(-) diff --git a/classes/science/plate.py b/classes/science/plate.py index 43a0936..329715b 100644 --- a/classes/science/plate.py +++ b/classes/science/plate.py @@ -1,7 +1,13 @@ -from numpy import ndarray, argmax, max, quantile, arange, where, convolve, ones +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: """ @@ -10,15 +16,17 @@ class Plate: def __init__( self , data ): if not isinstance( data , ndarray ): - raise TypeError( 'data must be a 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 , factor = 10 ): + def set_border( self ): """ Set current border (without area outside the plate) """ - compressed = self.compress( factor ) + compressed , factor = self.compress() points = self.get_points( compressed ) self.border = Border() @@ -39,24 +47,45 @@ class Plate: taken_points = fill( compressed, point , - 1000 , # intensity threshold + 2000 , # intensity threshold ) x = [ taken_point[1] for taken_point in taken_points ] y = [ taken_point[0] for taken_point in taken_points ] - 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 + """ + 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 @@ -116,36 +145,93 @@ class Plate: return first_column + last_column + first_line + last_line - def compress( self , factor ): + 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 """ - maxes = max( - self.data[ self.border.slice() ], - axis = 0 , - ) - indexes = where( - maxes > quantile( maxes , 0.5 ) + 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( - self.border.x.min, - self.border.x.max - )[ indexes ] - indexes_max = argmax( - self.data[ self.border.slice() ], - axis = 0 , - )[ indexes ] - indexes_max = convolve( - indexes_max , - ones( 100 ) , - 'same' , - ) / 100 - import matplotlib.pyplot as plt - plt.plot( abciss , indexes_max ) - plt.show() + 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