Skip to content

Commit 1ef9713

Browse files
committed
choice cp3 or cpsam in segment cells
1 parent 2dfc289 commit 1ef9713

7 files changed

Lines changed: 223 additions & 9 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dependencies = [
4444
"scikit-image",
4545
"lxml",
4646
"packaging",
47-
"cellpose[distributed]",
47+
"cellpose[distributed]>3",
4848
]
4949

5050
[project.optional-dependencies]

src/fish_feats/NapaCells.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def __init__( self, ffeats ):
198198
super().__init__()
199199
layout = QVBoxLayout()
200200

201-
methods = [ "Epyseg", "CellPose", "Load segmented file", "Empty" ]
201+
methods = [ "Epyseg", "CellPoseSAM", "CellPose3", "Load segmented file", "Empty" ]
202202
defmeth = "Epyseg"
203203
celldiameter = 30
204204
self.chunksize = 1500
@@ -210,6 +210,9 @@ def __init__( self, ffeats ):
210210
celldiameter = int(float(self.paras["cell_diameter"]))
211211
if "method" in self.paras:
212212
defmeth = self.paras["method"]
213+
## compatibility with previous version
214+
if defmeth == "CellPose":
215+
defmeth = "CellPoseSAM"
213216
if self.mig.junction_filename(dim=2, ifexist=True) != "":
214217
defmeth = "Load segmented file"
215218
self.junction_filename = self.mig.junction_filename(dim=2, ifexist=True)
@@ -253,7 +256,7 @@ def __init__( self, ffeats ):
253256

254257
def visibility( self ):
255258
""" Set the visibility of the parameters according to the method """
256-
self.diam_group.setVisible( self.methodsChoice.currentText() in ["CellPose"] )
259+
self.diam_group.setVisible( self.methodsChoice.currentText() in ["CellPose", "CellPoseSAM", "CellPose3"] )
257260
self.file_group.setVisible( self.methodsChoice.currentText() == "Load segmented file" )
258261

259262
def get_junction_filename( self ):

src/fish_feats/SegmentObj.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,73 @@ def junctions_to_label(img):
214214

215215
def run_cellpose(img, scaleXY, diameter=7, verbose=True):
216216
from cellpose import models
217-
model = models.CellposeModel(gpu=True, model_type='cyto3')
217+
model = models.CellposeModel( gpu=True )
218218
diamet = diameter/scaleXY ## increase it ?
219-
mask, flow, style = model.eval(img, invert=False, diameter=diamet, channels=[0,0], do_3D=False, cellprob_threshold=0.05)
219+
mask, flow, style = model.eval(img, invert=False, diameter=diamet, do_3D=False, cellprob_threshold=0.05)
220220
## convert cellpose result to label image (cellpose result are not touching border, make it as junctions)
221221
return fromcellpose_tojunctions(mask)
222222

223+
def run_cellpose_3( img, scaleXY, diameter=7, verbose=True, progress_bar=None ):
224+
""" Use CellPose3 instead of main Cellpose(SAM), using Appose environment """
225+
feature = "cp3-"+ut.get_cuda_feature()
226+
try:
227+
pixi_file = resources.files("fish_feats.resources").joinpath("pixi_cp3.toml")
228+
ut.show_info("Build/Load tensorflow environment")
229+
env = appose.pixi( pixi_file ).log_debug()
230+
env = env.subscribe_output( lambda line: print("OUT:", line, end="") )
231+
env = env.subscribe_error( lambda line: print("DBG:", line, end="") )
232+
env = env.environment( feature ).build()
233+
ut.show_info(f"Environment built at: {env.base()}")
234+
python = env.python().init("import numpy as np; from cellpose import models" \
235+
"")
236+
if progress_bar is None:
237+
progress_bar = ut.start_progress( None, total=1, descr="Cellpose3 segmentation" )
238+
toclose = True
239+
else:
240+
progress_bar.set_description( "Cellpose3 segmentation" )
241+
toclose = False
242+
243+
def log_listener(event):
244+
""" Transfer appose task message to the main logger """
245+
if event.current and event.maximum:
246+
print( f"Segmenting slice {event.current}/{event.maximum}" )
247+
else:
248+
if event.message:
249+
print( f"[task] {event.message} " )
250+
251+
try:
252+
cellpose3_script = resources.files("fish_feats.resources").joinpath("run_cellpose3.py")
253+
cellpose3_script = cellpose3_script.read_text()
254+
with share_as_ndarray(img) as image:
255+
task = python.task( cellpose3_script )
256+
task.listen( log_listener )
257+
task.inputs["img"] = image
258+
task.inputs["cell_diameter"] = diameter
259+
task.inputs["scale_xy"] = scaleXY
260+
task.wait_for()
261+
result = image.ndarray()
262+
mask = np.uint16( result )
263+
return fromcellpose_tojunctions(mask)
264+
except Exception as e:
265+
raise RuntimeError("Running cellpose3 in separated environement failed") from e
266+
finally:
267+
python.close()
268+
if toclose:
269+
ut.close_progress( None, progress_bar=progress_bar )
270+
except Exception as e:
271+
raise RuntimeError("Cellpose3 in separated environement failed") from e
272+
273+
def getNuclei_stardist2DAsso3D(nucimg, scaleXY, proba=0.55, overlap=0.1, assoMode="Munkres", assolim=3, threshold_overlap=0.25, verbose=True, progress_bar=None):
274+
""" Segment nuclei with Stardist2D and reconstruct in 3D - return the nuclei list """
275+
276+
## segment 2D
277+
appose = not ut.has_dependency( "stardist" )
278+
if appose:
279+
labnuc = stardist2D_appose(nucimg, prob=proba, over=overlap, progress_bar=progress_bar)
280+
else:
281+
labnuc = stardist2D_local(nucimg, prob=proba, over=overlap, progress_bar=progress_bar)
282+
283+
223284
def fromcellpose_tojunctions(mask):
224285
bined = binary_closing( find_boundaries(mask), footprint=np.ones((10,10)) )
225286
return junctions_to_label( skeletonize( bined ) )
@@ -233,9 +294,13 @@ def segmentJunctions(img, mode, scaleXY, imagedir, imagename, diameter=7, chunki
233294
if os.path.exists(tmpdir_path):
234295
os.chmod(tmpdir_path, stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH|stat.S_IWUSR|stat.S_IWGRP|stat.S_IWOTH|stat.S_IXUSR)
235296
shutil.rmtree(tmpdir_path, ignore_errors=True)
236-
if (isinstance(mode, str) and mode.lower() == "cellpose"):
237-
show_info("Starting segmentation with CellPose...")
238-
seged = run_cellpose(img, scaleXY, diameter=diameter, verbose=verbose)
297+
if (isinstance(mode, str) and mode[0:8].lower() == "cellpose"):
298+
if mode.lower() == "cellposesam":
299+
show_info("Starting segmentation with CellPose-SAM...")
300+
seged = run_cellpose(img, scaleXY, diameter=diameter, verbose=verbose)
301+
elif mode.lower() == "cellpose3":
302+
show_info("Starting segmentation with CellPose3, cyto3 model...")
303+
seged = run_cellpose_3(img, scaleXY, diameter=diameter, verbose=verbose)
239304
if (isinstance(mode, str) and mode.lower() == "epyseg-dask"):
240305
from fish_feats.DaskedEpyseg import run_dasked_epyseg
241306
show_info("Starting segmentation with dasked Epyseg...")

src/fish_feats/Utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,21 @@ def get_env_name():
800800
env_name = "cuda" if is_gpu_platform else "default"
801801
return env_name
802802

803+
def get_cuda_feature():
804+
""" Get the drivers cuda version through nvidia-smi and build the relevant feature for pixi """
805+
import subprocess, re
806+
result = subprocess.run(['nvidia-smi'], capture_output=True, text=True)
807+
match = re.search(r'CUDA Version:\s+([\d.]+)', result.stdout)
808+
if match:
809+
cuda_version = match.group(1)
810+
811+
## get only major version
812+
if cuda_version.split( "." )[0] == "12":
813+
return "cu126"
814+
if cuda_version.split( "." )[0] == "13":
815+
return "cu130"
816+
return "cpu"
817+
803818
#### Handle versions of napari
804819
def version_napari_above( compare_version ):
805820
""" Get the current version of napari """

src/fish_feats/resources/pixi.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
name = "tensorflow-env"
2+
name = "fishfeats-tensorflow-env"
33
version = "0.0.0"
44
description = "Modules with tensorflow for FishFeats: epyseg, stardist, tensorflow and keras"
55
authors = ["Gaelle Letort"]
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
[workspace]
2+
authors = [
3+
"Gaelle Letort <gaelle.letort@pasteur.fr>",
4+
]
5+
channels = ["conda-forge"]
6+
name = "fishfeats_cellpose3"
7+
platforms = ["osx-arm64", "win-64", "linux-64", "osx-64"]
8+
version = "0.1.0"
9+
10+
[dependencies]
11+
python = ">=3.10.3,<3.13"
12+
packaging = ">=26.2,<27"
13+
14+
[pypi-dependencies]
15+
appose = ">=0.10.1, <0.12"
16+
17+
# --- PyTorch features ---
18+
# See for more details on torch versions and CUDA support: https://pytorch.org/get-started/previous-versions/
19+
# macOS does not support CUDA and will always fall back to CPU.
20+
21+
# CPU: all platforms use the CPU index
22+
[feature.cpu.target.linux.pypi-dependencies]
23+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cpu" }
24+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cpu" }
25+
26+
[feature.cpu.target.win.pypi-dependencies]
27+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cpu" }
28+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cpu" }
29+
30+
[feature.cpu.target.osx-arm64.pypi-dependencies]
31+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cpu" }
32+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cpu" }
33+
34+
# Deprecated: osx-64 support should be drop in favor of osx-arm64
35+
[feature.cpu.target.osx-64.pypi-dependencies]
36+
torch = { version = ">=2.2.2, <2.11", index = "https://download.pytorch.org/whl/cpu" }
37+
torchvision = { version = ">=0.17.2, <0.26", index = "https://download.pytorch.org/whl/cpu" }
38+
39+
# CUDA 12.6: Linux/Windows use cu126 index, macOS falls back to CPU
40+
[feature.cu126.target.linux.pypi-dependencies]
41+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cu126" }
42+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cu126" }
43+
44+
[feature.cu126.target.win.pypi-dependencies]
45+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cu126" }
46+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cu126" }
47+
48+
[feature.cu126.target.osx-arm64.pypi-dependencies]
49+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cpu" }
50+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cpu" }
51+
52+
# Deprecated: osx-64 support should be drop in favor of osx-arm64
53+
[feature.cu126.target.osx-64.pypi-dependencies]
54+
torch = { version = ">=2.2.2, <2.11", index = "https://download.pytorch.org/whl/cpu" }
55+
torchvision = { version = ">=0.17.2, <0.26", index = "https://download.pytorch.org/whl/cpu" }
56+
57+
# CUDA 13.0: Linux/Windows use cu130 index, macOS falls back to CPU
58+
[feature.cu130.target.linux.pypi-dependencies]
59+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cu130" }
60+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cu130" }
61+
62+
[feature.cu130.target.win.pypi-dependencies]
63+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cu130" }
64+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cu130" }
65+
66+
[feature.cu130.target.osx-arm64.pypi-dependencies]
67+
torch = { version = ">=2.9, <2.11", index = "https://download.pytorch.org/whl/cpu" }
68+
torchvision = { version = ">=0.24, <0.26", index = "https://download.pytorch.org/whl/cpu" }
69+
70+
# Deprecated: osx-64 support should be drop in favor of osx-arm64
71+
[feature.cu130.target.osx-64.pypi-dependencies]
72+
torch = { version = ">=2.2.2, <2.11", index = "https://download.pytorch.org/whl/cpu" }
73+
torchvision = { version = ">=0.17.2, <0.26", index = "https://download.pytorch.org/whl/cpu" }
74+
75+
# --- CellPose version features ---
76+
[feature.cp3.pypi-dependencies]
77+
cellpose = ">=3, <4"
78+
79+
[feature.cp4.pypi-dependencies]
80+
cellpose = ">=4, <5"
81+
82+
# --- Environments ---
83+
[environments]
84+
default = { features = ["cp3", "cu126"] }
85+
cp3-cpu = { features = ["cp3", "cpu"] }
86+
cp4-cpu = { features = ["cp4", "cpu"] }
87+
cp3-cu126 = { features = ["cp3", "cu126"] }
88+
cp4-cu126 = { features = ["cp4", "cu126"] }
89+
cp3-cu130 = { features = ["cp3", "cu130"] }
90+
cp4-cu130 = { features = ["cp4", "cu130"] }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import logging
2+
import numpy as np
3+
appose_mode = 'task' in globals()
4+
5+
if not appose_mode:
6+
print("not implemented")
7+
exit
8+
try:
9+
from cellpose import models
10+
except ModuleNotFoundError as e:
11+
print("ERR: Problem with Cellpose3 installation")
12+
raise e
13+
14+
class ApposeLogHandler(logging.Handler):
15+
def emit(self, record):
16+
msg = self.format(record)
17+
task.update( message=msg )
18+
19+
def setup_logger( name="" ):
20+
logger = logging.getLogger(name)
21+
handler = ApposeLogHandler()
22+
formatter = logging.Formatter('[FishFeats-Cellpose3]-%(message)s')
23+
handler.setFormatter( formatter )
24+
logger.addHandler(handler)
25+
logger.setLevel( 20 )
26+
return logger
27+
28+
logger = setup_logger()
29+
logger.info("Starting segmentation")
30+
31+
image = np.array( img.ndarray() )
32+
logger.info("Starting")
33+
34+
model = models.CellposeModel( gpu=True, model_type='cyto3' )
35+
diamet = cell_diameter/scale_xy
36+
mask, flow, style = model.eval(image, invert=False, diameter=diamet, channels=[0,0], do_3D=False, cellprob_threshold=0.05)
37+
38+
if mask is not None:
39+
## Write the results on the shared memory object
40+
img.ndarray()[:] = mask
41+
logger.info( "Segmentation finished" )

0 commit comments

Comments
 (0)