99from fish_feats .Association import associateNucleus , associateNucleusOverlap
1010from skimage .filters import sato
1111from skimage .morphology import skeletonize , binary_closing
12- from skimage .segmentation import find_boundaries
13- from skimage .segmentation import clear_border
12+ from skimage .segmentation import find_boundaries , clear_border
1413from skimage .measure import label
1514from skimage .exposure import adjust_gamma
1615import time
1716import tempfile
17+ from importlib import resources
18+ import appose
1819
1920"""
2021 Functions to segment junctions or nuclei
2122"""
2223
23- def run_epyseg (input_folder ):
24+ def run_epyseg_appose ( input_folder ):
25+ """ Run epyseg on a separate virtual environement through appose """
26+ try :
27+ pixi_file = resources .files ("fish_feats.resources" ).joinpath ("pixi.toml" )
28+ epyseg_script = resources .files ("fish_feats.resources" ).joinpath ("run_epyseg.py" )
29+ epyseg_script = epyseg_script .read_text ()
30+ ut .show_info ("Build/Load tensorflow environment" )
31+ env = appose .pixi ( pixi_file ).log_debug ()
32+ env = env .subscribe_output ( lambda line : print ("OUT:" , line , end = "" ) )
33+ env = env .subscribe_error ( lambda line : print ("DBG:" , line , end = "" ) )
34+ env_name = ut .get_env_name ()
35+ env = env .environment (env_name ).build ()
36+ ut .show_info (f"Environment built at: { env .base ()} " )
37+ python = env .python ().init ("import numpy as np; import tensorflow as tf;" \
38+ "from epyseg.deeplearning.deepl import EZDeepLearning; import epyseg.deeplearning.deepl as deepl;" )
39+ python .debug (lambda msg : print ("[DBG]" , msg ))
40+
41+ def log_listener (event ):
42+ """ Transfer appose task message to the main logger """
43+ if event .message :
44+ print ( f"[task] { event .message } " )
45+
46+ try :
47+ task = python .task ( epyseg_script )
48+ task .listen ( log_listener )
49+ task .inputs ["input_folder" ] = input_folder
50+ task .wait_for ()
51+ except Exception as e :
52+ raise RuntimeError ("Running epyseg in separated environement failed" ) from e
53+ finally :
54+ python .close ()
55+ except Exception as e :
56+ print (e )
57+ raise RuntimeError ("Epyseg in separated environement failed" ) from e
58+
59+ def run_epyseg_local (input_folder ):
2460 import tensorflow as tf
2561
2662 # libraries loaded checking epyseg to see if everything is functional
@@ -93,12 +129,14 @@ def run_epyseg(input_folder):
93129 #deepTA = None
94130 del deepTA
95131
132+
96133def run_epyseg_onimage (img , filedir , filename , verbose = True ):
134+ """ Run epyseg on an image: create temp dir because of epyseg requirements """
97135 from PIL import Image
98- import os
99136
100137 binimg = None
101138 tmpdir_path = None
139+ appose = not ut .has_dependency ( "epyseg" )
102140 try :
103141 with tempfile .TemporaryDirectory () as tmpdir :
104142 print ("tmp dir " + str (tmpdir ))
@@ -114,7 +152,10 @@ def run_epyseg_onimage(img, filedir, filename, verbose=True):
114152 print ("Warning, issue in creating " + predict_output_folder + " folder" )
115153
116154 ## run Epyseg on tmp directory (contains current image)
117- run_epyseg (tmpdir )
155+ if appose :
156+ run_epyseg_appose ( tmpdir )
157+ else :
158+ run_epyseg_local (tmpdir )
118159
119160 ## return result and delete files
120161 im = Image .open (os .path .join (tmpdir ,"predict" ,inputname ))
@@ -132,7 +173,6 @@ def run_epyseg_onimage(img, filedir, filename, verbose=True):
132173
133174def run_epyseg_onimage_fold (img , filedir , filename , verbose = True ):
134175 from PIL import Image
135- import os
136176 import shutil
137177 import stat
138178
@@ -332,7 +372,13 @@ def prepNuclei(img):
332372 img = (img - quants [0 ])/ (quants [1 ]- quants [0 ])
333373 return img
334374
335- def stardist2D (img , prob , over ):
375+ def share_as_ndarray (img : np .ndarray ) -> appose .NDArray :
376+ """Copies a NumPy array into a same-sized newly allocated block of shared memory."""
377+ shared = appose .NDArray (str (img .dtype ), img .shape )
378+ shared .ndarray ()[:] = img
379+ return shared
380+
381+ def stardist2D_local (img , prob , over , progress_bar = None ):
336382 """ run stardist model, segment nuclei in 2D """
337383 try :
338384 from csbdeep .utils import Path , normalize
@@ -355,13 +401,73 @@ def stardist2D(img, prob, over):
355401 nuclei [ind ,] = labels
356402 return nuclei
357403
358- def getNuclei_stardist2DAsso3D (nucimg , scaleXY , proba = 0.55 , overlap = 0.1 , assoMode = "Munkres" , assolim = 3 , threshold_overlap = 0.25 , verbose = True ):
404+ def stardist2D_appose ( img , prob , over , progress_bar = None ):
405+ """ run stardist model, segment nuclei in 2D """
406+ try :
407+ pixi_file = resources .files ("fish_feats.resources" ).joinpath ("pixi.toml" )
408+ ut .show_info ("Build/Load tensorflow environment" )
409+ env = appose .pixi ( pixi_file ).log_debug ()
410+ env = env .subscribe_output ( lambda line : print ("OUT:" , line , end = "" ) )
411+ env = env .subscribe_error ( lambda line : print ("DBG:" , line , end = "" ) )
412+ env_name = ut .get_env_name ()
413+ env = env .environment (env_name ).build ()
414+ ut .show_info (f"Environment built at: { env .base ()} " )
415+ python = env .python ().init ("import numpy as np; import tensorflow as tf;" \
416+ "from stardist.models import StarDist2D" )
417+ #python.debug(lambda msg: print("[DBG]", msg))
418+ if progress_bar is None :
419+ progress_bar = ut .start_progress ( None , total = 1 , descr = "Stardist segmentation" )
420+ toclose = True
421+ else :
422+ progress_bar .set_description ( "Stardist segmentation" )
423+ toclose = False
424+
425+ def log_listener (event ):
426+ """ Transfer appose task message to the main logger """
427+ if event .current and event .maximum :
428+ print ( f"Segmenting slice { event .current } /{ event .maximum } " )
429+ #ut.show_info( f"Segmenting slice {event.current}/{event.maximum}" )
430+ #progress_bar.update( cur )
431+ #progress_bar.total = total
432+ else :
433+ if event .message :
434+ print ( f"[task] { event .message } " )
435+
436+ try :
437+ stardist_script = resources .files ("fish_feats.resources" ).joinpath ("run_stardist.py" )
438+ stardist_script = stardist_script .read_text ()
439+ with share_as_ndarray (img ) as image :
440+ task = python .task ( stardist_script )
441+ task .listen ( log_listener )
442+ task .inputs ["img" ] = image
443+ task .inputs ["stardist_probability" ] = prob
444+ task .inputs ["stardist_overlap" ] = over
445+ task .wait_for ()
446+ result = image .ndarray ()
447+ return np .uint16 ( result )
448+ except Exception as e :
449+ raise RuntimeError ("Running stardist in separated environement failed" ) from e
450+ finally :
451+ python .close ()
452+ if toclose :
453+ ut .close_progress ( None , progress_bar = progress_bar )
454+ except Exception as e :
455+ raise RuntimeError ("Stardist in separated environement failed" ) from e
456+
457+ def getNuclei_stardist2DAsso3D (nucimg , scaleXY , proba = 0.55 , overlap = 0.1 , assoMode = "Munkres" , assolim = 3 , threshold_overlap = 0.25 , verbose = True , progress_bar = None ):
359458 """ Segment nuclei with Stardist2D and reconstruct in 3D - return the nuclei list """
360459
361460 ## segment 2D
362- labnuc = stardist2D (nucimg , prob = proba , over = overlap )
461+ appose = not ut .has_dependency ( "stardist" )
462+ if appose :
463+ labnuc = stardist2D_appose (nucimg , prob = proba , over = overlap , progress_bar = progress_bar )
464+ else :
465+ labnuc = stardist2D_local (nucimg , prob = proba , over = overlap , progress_bar = progress_bar )
466+
363467 if labnuc is None :
364468 return None
469+ if progress_bar is not None :
470+ progress_bar .set_description ( "Reconstructing now in 3D..." )
365471 ## reconstruct 3D
366472 if assoMode == "Munkres" :
367473 labels = associateNucleus (labnuc , dlimit = assolim , scaleXY = scaleXY ) ## distance 2D in micrcons -2.5
0 commit comments