3535from queue import Queue
3636
3737# Third Party Imports
38+ from skimage .filters import threshold_otsu
3839
3940# Local Imports
4041from navigate .model .analysis .boundary_detect import find_tissue_boundary_2d
@@ -132,6 +133,30 @@ def detect_tissue2(image_data, percentage=0.0):
132133 return False
133134
134135
136+ def detect_tissue3 (image_data , threshold = 150 ):
137+ """Detect tissue in an image using the computed Otsu threshold.
138+
139+ This function computes the Otsu threshold value for an image and compares that
140+ computed value against a minimum threshold.
141+
142+ Parameters:
143+ -----------
144+ image_data : numpy.ndarray
145+ The input image data as a NumPy array.
146+ threshold : float, optional
147+ The minimum computed Otsu threshold required for tissue detection.
148+ Default is 150.
149+
150+ Returns:
151+ --------
152+ bool
153+ True if the computed Otsu threshold is greater than or equal to
154+ ``threshold``; False otherwise.
155+ """
156+ t = threshold_otsu (image_data )
157+ return t >= threshold
158+
159+
135160class DetectTissueInStack :
136161 """Detect Tissue in a Stack of Images.
137162
@@ -140,7 +165,9 @@ class DetectTissueInStack:
140165 presence.
141166 """
142167
143- def __init__ (self , model , planes = 1 , percentage = 0.75 , detect_func = None ):
168+ def __init__ (
169+ self , model , planes = 1 , percentage = 0.75 , detect_func = None , threshold = 150
170+ ):
144171 """Initialize the DetectTissueInStack class.
145172
146173 Parameters:
@@ -150,11 +177,14 @@ def __init__(self, model, planes=1, percentage=0.75, detect_func=None):
150177 planes : int, optional
151178 The number of Z planes to capture in the stack. Default is 1.
152179 percentage : float, optional
153- The minimum percentage of tissue required to consider a frame as having
154- tissue. Default is 0.75 (75%).
180+ The minimum fraction of tissue-positive frames required for the stack to
181+ be considered as having tissue. Default is 0.75 (75%).
155182 detect_func : function, optional
156183 The custom tissue detection function to use. If not specified, the default
157- `detect_tissue` function will be used.
184+ `detect_tissue3` function will be used. The function must accept
185+ ``(image_data, threshold)`` and return whether the frame contains tissue.
186+ threshold : float, optional
187+ The intensity threshold passed to ``detect_func``. Default is 150.
158188 """
159189
160190 #: navigate.model.Model: The model object representing the microscope.
@@ -163,14 +193,16 @@ def __init__(self, model, planes=1, percentage=0.75, detect_func=None):
163193 #: int: The number of Z planes to capture in the stack.
164194 self .planes = int (planes )
165195
166- #: float: The minimum percentage of tissue required to consider a frame as
167- # having tissue.
196+ #: float: The intensity threshold for tissue detection.
197+ self .threshold = float (threshold )
198+
199+ #: float: The minimum percentage of images having tissue in a stack.
168200 self .percentage = float (percentage )
169201
170202 # if not specify a detect function, use the default one
171203 if detect_func is None :
172204 #: function: The tissue detection function used to analyze image frames.
173- self .detect_func = detect_tissue
205+ self .detect_func = detect_tissue3
174206 else :
175207 self .detect_func = detect_func
176208
@@ -198,14 +230,26 @@ def pre_func_signal(self):
198230 """
199231
200232 microscope_config = self .model .configuration ["experiment" ]["MicroscopeState" ]
233+
234+ # primary z and f axes, secondary stack settings
235+ self .primary_z_axis = microscope_config .get ("primary_z_axis" , "z" )
236+ self .primary_f_axis = microscope_config .get ("primary_f_axis" , "f" )
237+
201238 # get current z and f position
202239 pos = self .model .get_stage_position ()
203240
241+ #: current stage position
242+ self .starting_pos = pos
243+
204244 #: float: The current Z position of the microscope stage.
205- self .current_z_pos = pos ["z_pos" ] + float (microscope_config ["start_position" ])
245+ self .current_z_pos = pos [f"{ self .primary_z_axis } _pos" ] + float (
246+ microscope_config ["start_position" ]
247+ )
206248
207249 #: float: The current F position of the microscope stage.
208- self .current_f_pos = pos ["f_pos" ] + float (microscope_config ["start_focus" ])
250+ self .current_f_pos = pos [f"{ self .primary_f_axis } _pos" ] + float (
251+ microscope_config ["start_focus" ]
252+ )
209253
210254 # calculate Z and F stage step sizes
211255 z_pos_range = float (microscope_config ["end_position" ]) - float (
@@ -239,11 +283,15 @@ def in_func_signal(self):
239283
240284 # move to Z anf F position
241285 self .model .logger .debug (
242- f"move to position (z, f): ({ self .current_z_pos } , { self .current_f_pos } ), "
286+ f"move to position ({ self .primary_z_axis } , { self .primary_f_axis } ): "
287+ f"({ self .current_z_pos } , { self .current_f_pos } ), "
243288 f"{ self .scan_num } , { self .model .frame_id } "
244289 )
245290 self .model .move_stage (
246- {"z_abs" : self .current_z_pos , "f_abs" : self .current_f_pos },
291+ {
292+ f"{ self .primary_z_axis } _abs" : self .current_z_pos ,
293+ f"{ self .primary_f_axis } _abs" : self .current_f_pos ,
294+ },
247295 wait_until_done = True ,
248296 )
249297 self .scan_num += 1
@@ -260,9 +308,21 @@ def end_func_signal(self):
260308 True if the signal phase should end, False otherwise.
261309 """
262310 self .model .logger .debug (
263- f"*** detect tissue signal end function: " f" { self .scan_num } "
311+ f"*** detect tissue signal end function: { self .scan_num } "
264312 )
265313 if self .scan_num >= self .planes :
314+ # move stage back to starting position
315+ self .model .move_stage (
316+ {
317+ f"{ self .primary_z_axis } _abs" : self .starting_pos [
318+ f"{ self .primary_z_axis } _pos"
319+ ],
320+ f"{ self .primary_f_axis } _abs" : self .starting_pos [
321+ f"{ self .primary_f_axis } _pos"
322+ ],
323+ },
324+ wait_until_done = True ,
325+ )
266326 return True
267327 self .current_z_pos += self .z_step
268328 self .current_f_pos += self .f_step
@@ -281,6 +341,9 @@ def pre_func_data(self):
281341 #: int: The number of received frames.
282342 self .received_frames = 0
283343
344+ #: int: The number of images having tissue detected.
345+ self .tissue_frames = 0
346+
284347 #: bool: Flag indicating whether tissue is detected.
285348 self .has_tissue_flag = False
286349
@@ -305,13 +368,16 @@ def in_func_data(self, frame_ids):
305368 if not self .has_tissue_flag :
306369 for frame_id in frame_ids :
307370 # check if the frame has tissue
308- r = self .detect_func (self .model .data_buffer [frame_id ], self .percentage )
371+ r = self .detect_func (self .model .data_buffer [frame_id ], self .threshold )
309372 if r :
310- self .model .logger .debug (
311- f"*** this frame has enough percentage of tissue!{ frame_id } "
312- )
313- self .has_tissue_flag = True
314- break
373+ self .model .logger .debug (f"*** this frame has tissue!{ frame_id } " )
374+ self .tissue_frames += 1
375+ # self.has_tissue_flag = True
376+ self .has_tissue_flag = (
377+ self .tissue_frames / self .planes
378+ ) >= self .percentage
379+ if self .has_tissue_flag :
380+ break
315381 self .received_frames += len (frame_ids )
316382 return self .has_tissue_flag
317383
@@ -330,7 +396,9 @@ def end_func_data(self):
330396
331397
332398class DetectTissueInStackAndReturn (DetectTissueInStack ):
333- def __init__ (self , model , planes = 1 , percentage = 0.75 , detect_func = None ):
399+ def __init__ (
400+ self , model , planes = 1 , percentage = 0.75 , detect_func = None , threshold = 150
401+ ):
334402 """Initialize the DetectTissueInStackAndReturn class.
335403
336404 Parameters:
@@ -340,13 +408,16 @@ def __init__(self, model, planes=1, percentage=0.75, detect_func=None):
340408 planes : int, optional
341409 The number of Z planes to capture in the stack. Default is 1.
342410 percentage : float, optional
343- The minimum percentage of tissue required to consider a frame as having
344- tissue. Default is 0.75 (75%).
411+ The minimum fraction of tissue-positive frames required for the stack to
412+ be considered as having tissue. Default is 0.75 (75%).
345413 detect_func : function, optional
346414 The custom tissue detection function to use. If not specified, the default
347- `detect_tissue` function will be used.
415+ `detect_tissue3` function will be used. The function must accept
416+ ``(image_data, threshold)`` and return whether the frame contains tissue.
417+ threshold : float, optional
418+ The intensity threshold passed to ``detect_func``. Default is 150.
348419 """
349- super ().__init__ (model , planes , percentage , detect_func )
420+ super ().__init__ (model , planes , percentage , detect_func , threshold )
350421
351422 self .detect_tissue_queue = Queue ()
352423 self .result_sent_flag = False
@@ -429,7 +500,13 @@ class DetectTissueInStackAndRecord(DetectTissueInStack):
429500 """
430501
431502 def __init__ (
432- self , model , planes = 1 , percentage = 0.75 , position_records = [], detect_func = None
503+ self ,
504+ model ,
505+ planes = 1 ,
506+ percentage = 0.75 ,
507+ position_records = None ,
508+ detect_func = None ,
509+ threshold = 150 ,
433510 ):
434511 """Initialize the DetectTissueInStackAndRecord class.
435512
@@ -440,18 +517,23 @@ def __init__(
440517 planes : int, optional
441518 The number of Z planes to capture in the stack. Default is 1.
442519 percentage : float, optional
443- The minimum percentage of tissue required to consider a frame as having
444- tissue. Default is 0.75 (75%).
520+ The minimum fraction of tissue-positive frames required for the stack to
521+ be considered as having tissue. Default is 0.75 (75%).
445522 position_records : list, optional
446- A list to record positions where tissue was detected. Default is an empty
447- list.
523+ A list to record positions where tissue was detected. A new list is
524+ created when no list is provided .
448525 detect_func : function, optional
449526 The custom tissue detection function to use. If not specified, the default
450- `detect_tissue` function will be used.
527+ `detect_tissue3` function will be used. The function must accept
528+ ``(image_data, threshold)`` and return whether the frame contains tissue.
529+ threshold : float, optional
530+ The intensity threshold passed to ``detect_func``. Default is 150.
451531 """
452- super ().__init__ (model , planes , percentage , detect_func )
532+ super ().__init__ (model , planes , percentage , detect_func , threshold )
453533
454534 #: list: A list to record positions where tissue was detected.
535+ if position_records is None :
536+ position_records = []
455537 self .position_records = position_records
456538
457539 def pre_func_data (self ):
0 commit comments