312 lines
9.5 KiB
Python
312 lines
9.5 KiB
Python
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 ]
|
|
|
|
"""
|
|
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 )
|
|
|
|
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()
|
|
|
|
indexes = find_peak_low_high(
|
|
convolve(
|
|
mean(
|
|
self.data[ self.border.slice() ],
|
|
axis = 1 ,
|
|
) ,
|
|
ones( 200 ),
|
|
'valid' ,
|
|
),
|
|
)[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()
|
|
indexes = find_peak_low_high(
|
|
convolve(
|
|
mean(
|
|
self.data[ self.border.slice() ],
|
|
axis = 0 ,
|
|
) ,
|
|
ones( 200 ),
|
|
'valid' ,
|
|
),
|
|
)[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
|