Skip to content

Commit 93fb61a

Browse files
committed
Fix CPU/GPU switching in cupy-dependent scripts
Replace bare 'import cupy' at module level with try/except ImportError blocks that fall back to numpy on machines without cupy/cucim installed. Changes per script: - intersection.py: safe cupy import + GPU_AVAILABLE flag - combine_labels.py: safe cupy/cupyx import + added CPU fallback merge_labels_cpu() using cle directly - counts_validation.py: safe cucim/cupy import + GPU_AVAILABLE flag - segmentation_instances_clahe.py: safe cupy/cucim import with skimage fallbacks + to_numpy helper replacing all cp.asnumpy() calls - get_basic_regionprops.py: safe cupy/cucim import + replaced .get() calls with float() for numpy compatibility - ROI_colocalization_regionprops.py: safe cupy/cucim import + replaced .get() calls with float()/int() for numpy compatibility - segmentation_validation_f1_score.py: removed cupy entirely (sklearn requires numpy arrays, not cupy arrays)
1 parent fc61696 commit 93fb61a

7 files changed

Lines changed: 91 additions & 40 deletions

scripts/ROI_colocalization_regionprops.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
import argparse
44
import numpy as np
55
import pandas as pd
6-
import cupy as cp
7-
from cucim.skimage.measure import regionprops
6+
try:
7+
import cupy as cp
8+
from cucim.skimage.measure import regionprops
9+
GPU_AVAILABLE = cp.cuda.is_available()
10+
except ImportError:
11+
import numpy as cp
12+
from skimage.measure import regionprops
13+
GPU_AVAILABLE = False
814
from skimage.io import imread
915
from tqdm import tqdm
1016

@@ -97,7 +103,7 @@ def get_regionprops(label_img, intensity_img, file_path, label_id,channels, ROI_
97103
pass
98104
df.loc[i, f'{channels[1]} label id'] = int(prop.label)
99105
try:
100-
df.loc[i, f'{channels[1]} Size'] = prop.area.get() # area = pixel/voxel count (2D/3D)
106+
df.loc[i, f'{channels[1]} Size'] = float(prop.area) # area = pixel/voxel count (2D/3D)
101107
except ValueError:
102108
print(f"Skipping size for region {prop.label} due to numerical error.")
103109
df.loc[i, f'{channels[1]} Size'] = np.nan
@@ -122,22 +128,22 @@ def get_regionprops(label_img, intensity_img, file_path, label_id,channels, ROI_
122128
print(f"Skipping minor axis length for region {prop.label} due to numerical error.")
123129
df.loc[i, f'{channels[1]} MinorAxisLength'] = np.nan
124130
try:
125-
df.loc[i, f'{channels[1]} MeanIntensity'] = prop.intensity_mean.get()
131+
df.loc[i, f'{channels[1]} MeanIntensity'] = float(prop.intensity_mean)
126132
except ValueError:
127133
print(f"Skipping mean intensity for region {prop.label} due to numerical error.")
128134
df.loc[i, f'{channels[1]} MeanIntensity'] = np.nan
129135
try:
130-
df.loc[i, f'{channels[1]} MedianIntensity'] = prop.median_intensity.get()
136+
df.loc[i, f'{channels[1]} MedianIntensity'] = float(prop.median_intensity)
131137
except ValueError:
132138
print(f"Skipping median intensity for region {prop.label} due to numerical error.")
133139
df.loc[i, f'{channels[1]} MedianIntensity'] = np.nan
134140
try:
135-
df.loc[i, f'{channels[1]} MaxIntensity'] = prop.intensity_max.get()
141+
df.loc[i, f'{channels[1]} MaxIntensity'] = float(prop.intensity_max)
136142
except ValueError:
137143
print(f"Skipping max intensity for region {prop.label} due to numerical error.")
138144
df.loc[i, f'{channels[1]} MaxIntensity'] = np.nan
139145
try:
140-
df.loc[i, f'{channels[1]} StdIntensity'] = prop.std_intensity.get()
146+
df.loc[i, f'{channels[1]} StdIntensity'] = float(prop.std_intensity)
141147
except ValueError:
142148
print(f"Skipping std intensity for region {prop.label} due to numerical error.")
143149
df.loc[i, f'{channels[1]} StdIntensity'] = np.nan
@@ -174,15 +180,15 @@ def get_intensity_only_regionprops(ROI_mask, intensity_img, file_path, label_id,
174180
df.loc[0, f'{channels[0]} label id'] = label_id
175181
if ROI_size.lower() == 'y':
176182
# Calculate ROI size in pixels/voxels (works for both 2D and 3D)
177-
roi_size = int(cp.sum(ROI_mask_gpu > 0).get())
183+
roi_size = int(cp.sum(ROI_mask_gpu > 0))
178184
df.loc[0, f'{channels[0]} Size'] = roi_size
179185

180186
# Intensity measurements
181-
df.loc[0, f'{channels[1]} MeanIntensity'] = float(cp.mean(intensity_values).get())
182-
df.loc[0, f'{channels[1]} MedianIntensity'] = float(cp.median(intensity_values).get())
183-
df.loc[0, f'{channels[1]} StdIntensity'] = float(cp.std(intensity_values).get())
184-
df.loc[0, f'{channels[1]} MaxIntensity'] = float(cp.max(intensity_values).get())
185-
df.loc[0, f'{channels[1]} MinIntensity'] = float(cp.min(intensity_values).get())
187+
df.loc[0, f'{channels[1]} MeanIntensity'] = float(cp.mean(intensity_values))
188+
df.loc[0, f'{channels[1]} MedianIntensity'] = float(cp.median(intensity_values))
189+
df.loc[0, f'{channels[1]} StdIntensity'] = float(cp.std(intensity_values))
190+
df.loc[0, f'{channels[1]} MaxIntensity'] = float(cp.max(intensity_values))
191+
df.loc[0, f'{channels[1]} MinIntensity'] = float(cp.min(intensity_values))
186192

187193
return df
188194

scripts/combine_labels.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
import numpy as np
33
from tifffile import imread, imwrite
44
import argparse
5-
import cupy as cp
6-
import cupyx.scipy.ndimage as cpndi
5+
try:
6+
import cupy as cp
7+
import cupyx.scipy.ndimage as cpndi
8+
GPU_AVAILABLE = cp.cuda.is_available()
9+
except ImportError:
10+
import numpy as cp
11+
import scipy.ndimage as cpndi
12+
GPU_AVAILABLE = False
713
from tqdm import tqdm
814
import pyclesperanto_prototype as cle
915

@@ -49,18 +55,27 @@ def merge_labels_gpu(label1, label2):
4955

5056
return result.astype(np.uint32) # Convert back to uint32
5157

58+
def merge_labels_cpu(label1, label2):
59+
label1_img = imread(label1).astype(np.float32)
60+
label2_img = imread(label2).astype(np.float32)
61+
label1_cle = cle.push(label1_img)
62+
label2_cle = cle.push(label2_img)
63+
result_cle = cle.create_like(label1_cle)
64+
cle.combine_labels(label1_cle, label2_cle, result_cle)
65+
return cle.pull(result_cle).astype(np.uint32)
66+
5267

5368

5469
for idx, filename in enumerate(tqdm(filenames, total=len(filenames), desc="Processing images")):
5570
label1 = os.path.join(args.input, filename + args.label1_tag)
5671
label2 = os.path.join(args.input, filename + args.label2_tag)
5772

58-
if cp.cuda.is_available():
73+
if GPU_AVAILABLE:
5974
result = merge_labels_gpu(label1, label2)
6075
print('\nUsing GPU')
6176
else:
62-
print('\nGPU not available. This script requires GPU.')
63-
break
77+
result = merge_labels_cpu(label1, label2)
78+
print('\nUsing CPU')
6479

6580
imwrite(os.path.join(args.input, filename + args.output_tag),
6681
result, compression='zlib')

scripts/counts_validation.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
import pandas as pd
66
from tqdm import tqdm
77
import sys
8-
from cucim.skimage.measure import regionprops as regionprops_gpu
9-
import cupy as cp
8+
try:
9+
from cucim.skimage.measure import regionprops as regionprops_gpu
10+
import cupy as cp
11+
GPU_AVAILABLE = cp.cuda.is_available()
12+
except ImportError:
13+
regionprops_gpu = None
14+
import numpy as cp
15+
GPU_AVAILABLE = False
1016

1117
def parse_args():
1218
parser = argparse.ArgumentParser(description='Validate segmentation results against manual segmentation results.')
@@ -77,7 +83,7 @@ def main():
7783
sys.exit(1)
7884

7985
results = []
80-
use_gpu = cp.cuda.is_available()
86+
use_gpu = GPU_AVAILABLE
8187

8288
for gt_file, pred_file in tqdm(zip(ground_truths, predictions), total=len(ground_truths), desc="Processing images"):
8389
result = validate_segmentation(gt_file, pred_file, use_gpu)

scripts/get_basic_regionprops.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
import sys
44
import pandas as pd
55
from skimage import io
6-
import cupy as cp
7-
from cucim.skimage.measure import regionprops
6+
try:
7+
import cupy as cp
8+
from cucim.skimage.measure import regionprops
9+
GPU_AVAILABLE = cp.cuda.is_available()
10+
except ImportError:
11+
import numpy as cp
12+
from skimage.measure import regionprops
13+
GPU_AVAILABLE = False
814
from tqdm import tqdm
915
import numpy as np
1016

@@ -93,9 +99,9 @@ def get_regionprops(label_img_path, intensity_img_path=None):
9399
for i, prop in enumerate(props):
94100
df.loc[i, 'Filename'] = os.path.basename(label_img_path)
95101
df.loc[i, 'Labels_ID'] = int(prop.label)
96-
df.loc[i, 'Area'] = prop.area.get() * area_scale
102+
df.loc[i, 'Area'] = float(prop.area) * area_scale
97103
df.loc[i, 'Area_Unit'] = area_unit
98-
#df.loc[i, 'Perimeter'] = prop.perimeter.get()
104+
#df.loc[i, 'Perimeter'] = float(prop.perimeter)
99105

100106
# Check if the image is 3D and skip eccentricity
101107
if label_img.ndim == 3:
@@ -107,17 +113,17 @@ def get_regionprops(label_img_path, intensity_img_path=None):
107113
df.loc[i, 'MajorAxisLength_Unit'] = length_unit
108114
df.loc[i, 'MinorAxisLength'] = prop.minor_axis_length * length_scale
109115
df.loc[i, 'MinorAxisLength_Unit'] = length_unit
110-
df.loc[i, 'MeanIntensity'] = prop.intensity_mean.get()
111-
df.loc[i, 'MaxIntensity'] = prop.intensity_max.get()
116+
df.loc[i, 'MeanIntensity'] = float(prop.intensity_mean)
117+
df.loc[i, 'MaxIntensity'] = float(prop.intensity_max)
112118

113119
else:
114120
props = regionprops(label_img)
115121
for i, prop in enumerate(props):
116122
df.loc[i, 'Filename'] = os.path.basename(label_img_path)
117123
df.loc[i, 'Labels_ID'] = int(prop.label)
118-
df.loc[i, 'Area'] = prop.area.get() * area_scale
124+
df.loc[i, 'Area'] = float(prop.area) * area_scale
119125
df.loc[i, 'Area_Unit'] = area_unit
120-
#df.loc[i, 'Perimeter'] = prop.perimeter.get()
126+
#df.loc[i, 'Perimeter'] = float(prop.perimeter)
121127

122128
# Check if the image is 3D and skip eccentricity
123129
if label_img.ndim == 3:

scripts/intersection.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import os
22
from tifffile import imread, imwrite
33
import argparse
4-
import cupy as cp
54
from tqdm import tqdm
65

6+
try:
7+
import cupy as cp
8+
GPU_AVAILABLE = cp.cuda.is_available()
9+
except ImportError:
10+
import numpy as cp
11+
GPU_AVAILABLE = False
12+
713
"""
814
Description: This script creates a new image by intersecting two images.
915
This is done by setting the pixel values of the original image to zero where the mask image is zero.
@@ -43,7 +49,7 @@ def intersection_gpu(mask, original):
4349
mask = os.path.join(args.input, filename + args.maskfiles)
4450
original = os.path.join(args.input, filename + args.intersectfiles)
4551

46-
if cp.cuda.is_available():
52+
if GPU_AVAILABLE:
4753
result = intersection_gpu(mask, original)
4854
print('\nUsing GPU')
4955
else:

scripts/segmentation_instances_clahe.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import os
22
import glob
3+
import numpy as np
34
import tifffile as tf
45
import argparse
56
from skimage import exposure
6-
import cupy as cp
7-
from cucim.skimage import morphology
8-
from cucim.skimage.filters import gaussian
9-
from cucim.skimage.filters.thresholding import threshold_otsu
10-
from cucim.skimage.measure import label
7+
try:
8+
import cupy as cp
9+
from cucim.skimage import morphology
10+
from cucim.skimage.filters import gaussian
11+
from cucim.skimage.filters.thresholding import threshold_otsu
12+
from cucim.skimage.measure import label
13+
GPU_AVAILABLE = cp.cuda.is_available()
14+
except ImportError:
15+
import numpy as cp
16+
from skimage import morphology
17+
from skimage.filters import gaussian
18+
from skimage.filters import threshold_otsu
19+
from skimage.measure import label
20+
GPU_AVAILABLE = False
21+
22+
to_numpy = cp.asnumpy if GPU_AVAILABLE else np.asarray
1123
import pyclesperanto_prototype as cle
1224
from tqdm import tqdm
1325

@@ -70,7 +82,7 @@ def intersect_clahe_go(mask,image, kernel_size, clip_limit, nbins, outline_sigma
7082
mask = cp.asarray(tf.imread(mask))
7183
image = cp.asarray(tf.imread(image))
7284
image[mask == 0] = 0
73-
image_clahe = exposure.equalize_adapthist(cp.asnumpy(image), kernel_size=kernel_size, clip_limit=clip_limit, nbins=nbins)
85+
image_clahe = exposure.equalize_adapthist(to_numpy(image), kernel_size=kernel_size, clip_limit=clip_limit, nbins=nbins)
7486
image_gol = gaussian(cp.asarray(image_clahe), sigma=outline_sigma, preserve_range=True)
7587
threshold = threshold_otsu(image_gol)
7688
binary = image_gol >= threshold
@@ -79,7 +91,7 @@ def intersect_clahe_go(mask,image, kernel_size, clip_limit, nbins, outline_sigma
7991
label_image = cle.push(cp.asarray(label_image))
8092
label_image = cle.exclude_small_labels(label_image, None, LOWER_THRESHOLD)
8193
label_image = cle.exclude_large_labels(label_image, None, UPPER_THRESHOLD)
82-
label_image = cp.asnumpy(label_image)
94+
label_image = to_numpy(label_image)
8395
return label_image
8496

8597
for idx, (mask_file, intensity_file) in enumerate(tqdm(zip(mask_files, intensity_files),

scripts/segmentation_validation_f1_score.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import argparse
3-
import cupy as cp
3+
import numpy as np
44
from skimage.io import imread
55
import pandas as pd
66
from sklearn.metrics import f1_score
@@ -57,7 +57,7 @@ def parse_args():
5757
for i in tqdm(range(len(predictions)), total=len(predictions), desc="Processing images"):
5858
prediction = imread(predictions[i])
5959
ground_truth = imread(ground_truths[i])
60-
f1 = f1_score(cp.ravel(ground_truth), cp.ravel(prediction), average='micro')
60+
f1 = f1_score(np.ravel(ground_truth), np.ravel(prediction), average='micro')
6161

6262
filename = os.path.basename(predictions[i])[:-11]
6363

0 commit comments

Comments
 (0)