@@ -16,15 +16,11 @@ def identify_crystal_raster(result, r: RasterGridRequest) -> CenterOfMassModel |
max_image = max ( images , key = lambda img : img . spots_low_res )
logger . debug ( f " Image with maximum spots_low_res: { max_image } " )
logger . debug ( f " Maximum spots_low_res value: { max_image . spots_low_res } " )
logger . debug (
f " Maximum image found at grid coordiantes { max_image . nx } , { max_image . ny } "
)
logger . debug ( f " Maximum image found at grid coordiantes { max_image . nx } , { max_image . ny } " )
logger . debug (
f " Maximum image found at umL { max_image . nx * r . grid_size_mm . x } , { max_image . ny * r . grid_size_mm . y } "
)
com = CenterOfMassModel (
n_x = max_image . nx , n_y = max_image . ny , max_image = max_image . number
)
com = CenterOfMassModel ( n_x = max_image . nx , n_y = max_image . ny , max_image = max_image . number )
com_mm = com . get_com_mm ( r )
grid_mm_x = com_mm . x
grid_mm_y = com_mm . y
@@ -168,16 +164,11 @@ def create_quality_filtered_array(
if min_spots is None :
min_spots = min (
( result . spots for result in scan_results if result . spots is not None ) ,
default = 1 ,
( result . spots for result in scan_results if result . spots is not None ) , default = 1
)
if min_low_res_spots is None :
min_low_res_spots = min (
(
result . spots_low_res
for result in scan_results
if result . spots_low_res is not None
) ,
( result . spots_low_res for result in scan_results if result . spots_low_res is not None ) ,
default = 1 ,
)
if min_background is None :
@@ -187,8 +178,7 @@ def create_quality_filtered_array(
if min_efficiency is None :
min_efficiency = 1.0
max_spots_low_res = max (
( result . spots_low_res for result in scan_results if result . spots is not None ) ,
default = 1 ,
( result . spots_low_res for result in scan_results if result . spots is not None ) , default = 1
)
for result in scan_results :
@@ -231,22 +221,16 @@ def get_xtal_size(crystal_size, result_array, r: RasterGridRequest):
cols = np . any ( object_mask , axis = 0 )
row_min , row_max = np . where ( rows ) [ 0 ] [ [ 0 , - 1 ] ]
col_min , col_max = np . where ( cols ) [ 0 ] [ [ 0 , - 1 ] ]
logger . info (
f " Largest bbox: width= { col_max - col_min } , height= { row_max - row_min } "
)
logger . info ( f " Largest bbox: width= { col_max - col_min } , height= { row_max - row_min } " )
logger . info (
f " Largest bbox: width= { ( col_max - col_min ) * r . grid_size_mm . x } , y= { ( row_max - row_min ) * r . grid_size_mm . y } "
)
if r . n_x == 1 :
logger . debug ( " calculating z " )
crystal_size = CrystalSize (
x = crystal_size . x ,
y = crystal_size . y ,
z = ( col_max - col_min ) * r . grid_size_mm . y * 1000 ,
)
logger . info (
f " Crystal Size: x= { crystal_size . x } , y= { crystal_size . y } , z= { crystal_size . z } "
x = crystal_size . x , y = crystal_size . y , z = ( col_max - col_min ) * r . grid_size_mm . y * 1000
)
logger . info ( f " Crystal Size: x= { crystal_size . x } , y= { crystal_size . y } , z= { crystal_size . z } " )
else :
logger . debug ( " calculating x and y " )
@@ -255,9 +239,7 @@ def get_xtal_size(crystal_size, result_array, r: RasterGridRequest):
y = ( col_max - col_min ) * r . grid_size_mm . y * 1000 ,
z = crystal_size . z ,
)
logger . info (
f " Crystal Size: x= { crystal_size . x } , y= { crystal_size . y } , z= { crystal_size . z } "
)
logger . info ( f " Crystal Size: x= { crystal_size . x } , y= { crystal_size . y } , z= { crystal_size . z } " )
except ValueError as e :
logger . error ( f " error calculating xtal size: { e } " )
crystal_size = CrystalSize ( x = 0 , y = 0 , z = 0 )
@@ -269,9 +251,7 @@ def get_best_b_factor(result_list: List):
if not result_list :
return None
best_b_factor = min (
( img for img in result_list if img . b is not None ) ,
key = lambda img : img . b ,
default = None ,
( img for img in result_list if img . b is not None ) , key = lambda img : img . b , default = None
)
if best_b_factor is None :
return None
@@ -283,9 +263,7 @@ def get_best_res(result_list: List):
if not result_list :
return None
best_res = min (
( img for img in result_list if img . res is not None ) ,
key = lambda img : img . res ,
default = None ,
( img for img in result_list if img . res is not None ) , key = lambda img : img . res , default = None
)
if best_res is None :
return None
@@ -308,9 +286,7 @@ def get_result_list_from_com(images, com: CenterOfMassModel):
start_y , end_y = round ( cy - 1 ) , round ( cy + 1 )
logger . info ( f " range x { start_x } { end_x } , y { start_y } { end_y } " )
result_list = [
img
for img in images
if start_x < = img . nx < = end_x and start_y < = img . ny < = end_y
img for img in images if start_x < = img . nx < = end_x and start_y < = img . ny < = end_y
]
return result_list
@@ -353,10 +329,7 @@ def raster_highest_score(images) -> CenterOfMassModel | None:
return _to_com_model ( _max_cell ( score_array ) , images , " Highest score " )
def has_sufficient_low_res_spots (
result_array : np . ndarray ,
min_spots_low_res : float ,
) - > bool :
def has_sufficient_low_res_spots ( result_array : np . ndarray , min_spots_low_res : float ) - > bool :
if result_array is None or result_array . size == 0 :
logger . warning ( " Empty result_array passed to has_sufficient_low_res_spots " )
return False
@@ -365,20 +338,14 @@ def has_sufficient_low_res_spots(
logger . info ( f " Max spots_low_res in raster result_array: { max_val } " )
if max_val < min_spots_low_res :
logger . info (
" Raster rejected: max spots_low_res "
f " { max_val } < required { min_spots_low_res } "
)
logger . info ( f " Raster rejected: max spots_low_res { max_val } < required { min_spots_low_res } " )
return False
return True
def compute_crystal_score_array (
scan_results : List ,
w_bkg : float = 0.25 ,
w_low_res : float = 0.55 ,
w_indexed : float = 0.20 ,
scan_results : List , w_bkg : float = 0.25 , w_low_res : float = 0.55 , w_indexed : float = 0.20
) - > np . ndarray :
""" Combine bkg (25 % ), spots_low_res (55 % ), and spots_indexed (20 % ) into a 0– 100 score.
@@ -395,9 +362,7 @@ def compute_crystal_score_array(
return np . zeros_like ( a , dtype = float )
return ( a - mn ) / ( mx - mn ) * 100.0
return (
w_bkg * _norm ( arr_bkg ) + w_low_res * _norm ( arr_low ) + w_indexed * _norm ( arr_idx )
)
return w_bkg * _norm ( arr_bkg ) + w_low_res * _norm ( arr_low ) + w_indexed * _norm ( arr_idx )
def _max_cell ( arr : np . ndarray ) - > tuple [ int , int ] :
@@ -445,12 +410,7 @@ def _draw_panel(
linewidths = 1.8 ,
)
rect = mpatches . Rectangle (
( max_nx - 0.5 , max_ny - 0.5 ) ,
1 ,
1 ,
linewidth = 2 ,
edgecolor = " red " ,
facecolor = " none " ,
( max_nx - 0.5 , max_ny - 0.5 ) , 1 , 1 , linewidth = 2 , edgecolor = " red " , facecolor = " none "
)
ax . add_patch ( rect )
@@ -466,10 +426,7 @@ def _draw_panel(
xtal_size = get_xtal_size ( CrystalSize ( x = 0 , y = 0 , z = 0 ) , mask . astype ( float ) , r )
size_line = f " \n { xtal_size . x : .0f } × { xtal_size . y : .0f } µm "
ax . set_title (
f " { label } \n thresh≈ { threshold : .1f } | { n_cells } cells { size_line } " ,
fontsize = 8 ,
)
ax . set_title ( f " { label } \n thresh≈ { threshold : .1f } | { n_cells } cells { size_line } " , fontsize = 8 )
ax . set_xlabel ( " nx " , fontsize = 7 )
ax . set_ylabel ( " ny " , fontsize = 7 )
ax . tick_params ( labelsize = 6 )
@@ -506,9 +463,7 @@ def crystal_mask_otsu_nonzero(arr: np.ndarray) -> tuple[np.ndarray, float]:
return arr > threshold , threshold
def crystal_mask_mean_sigma (
arr : np . ndarray , n_sigma : float = 0.5
) - > tuple [ np . ndarray , float ] :
def crystal_mask_mean_sigma ( arr : np . ndarray , n_sigma : float = 0.5 ) - > tuple [ np . ndarray , float ] :
""" Threshold = mean + n_sigma * std of nonzero values.
n_sigma=0.5 keeps cells within ~1/2 std above average signal.
@@ -569,9 +524,7 @@ def crystal_mask_dbscan(
return mask , min_signal
def crystal_mask_kmeans (
arr : np . ndarray , n_clusters : int = 3
) - > tuple [ np . ndarray , float ] :
def crystal_mask_kmeans ( arr : np . ndarray , n_clusters : int = 3 ) - > tuple [ np . ndarray , float ] :
""" K-means on signal values (1-D feature).
Splits cells into n_clusters groups; the cluster with the highest
@@ -590,10 +543,7 @@ def crystal_mask_kmeans(
CRYSTAL_METHOD_MAP : dict = {
" corner " : ( " Corner background " , crystal_mask_corner_background ) ,
" otsu " : ( " Otsu (nonzero) " , crystal_mask_otsu_nonzero ) ,
" mean_sigma " : (
" Mean + 0.5σ " ,
lambda arr : crystal_mask_mean_sigma ( arr , n_sigma = 0.5 ) ,
) ,
" mean_sigma " : ( " Mean + 0.5σ " , lambda arr : crystal_mask_mean_sigma ( arr , n_sigma = 0.5 ) ) ,
" percentile " : (
" Top-40 % s ignal " ,
lambda arr : crystal_mask_signal_percentile ( arr , percentile = 60.0 ) ,
@@ -671,9 +621,7 @@ def compare_crystal_methods_scored(
results , w_bkg = w_bkg , w_low_res = w_low_res , w_indexed = w_indexed
)
method_entries = [
( name , label , fn ) for name , ( label , fn ) in CRYSTAL_METHOD_MAP . items ( )
]
method_entries = [ ( name , label , fn ) for name , ( label , fn ) in CRYSTAL_METHOD_MAP . items ( ) ]
fig , axes = plt . subplots ( 2 , 3 , figsize = ( 15 , 9 ) )
for ax , ( name , label , fn ) in zip ( axes . flat , method_entries ) :
@@ -692,7 +640,9 @@ def compare_crystal_methods_scored(
spine . set_edgecolor ( " yellow " )
spine . set_linewidth ( 3 )
weight_note = f " bkg× { w_bkg : .0% } + spots_low_res× { w_low_res : .0% } + spots_indexed× { w_indexed : .0% } "
weight_note = (
f " bkg× { w_bkg : .0% } + spots_low_res× { w_low_res : .0% } + spots_indexed× { w_indexed : .0% } "
)
fig . suptitle (
f " Crystal region detection on composite score [ { weight_note } ] \n "
" cyan = crystal outline | red square = max-score cell | score range 0– 100 " ,