Skip to content

Commit dfcceeb

Browse files
committed
threshold example
1 parent fc75ae9 commit dfcceeb

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

demo/testing/wrong_treshold.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from skimage.measure import regionprops, label
4+
from skimage.filters import threshold_otsu
5+
6+
# --- 1. Generate a synthetic microscopy image ---
7+
# Create a blank canvas
8+
image_size = 200
9+
image = np.zeros((image_size, image_size))
10+
11+
# Create coordinates for the image
12+
x, y = np.mgrid[0:image_size, 0:image_size]
13+
14+
# Define the "cell" using a 2D Gaussian profile
15+
# This is our measurement channel (Channel A)
16+
radius_x, radius_y = 30, 40
17+
center_x, center_y = image_size // 2, image_size // 2
18+
intensity_profile = np.exp(-((x - center_x)**2 / (2 * radius_x**2) + (y - center_y)**2 / (2 * radius_y**2)))
19+
20+
# Add intensity and some random noise
21+
channel_A_measurement = 150 * intensity_profile + 20 * np.random.rand(image_size, image_size)
22+
channel_A_measurement = np.clip(channel_A_measurement, 0, 255)
23+
24+
# Create an ideal segmentation channel (Channel B)
25+
# This channel clearly marks the entire object's boundary
26+
# The shape is made slightly larger to simulate a common biological reality (e.g., cytoplasm around a signal)
27+
segment_radius_x, segment_radius_y = 35, 45
28+
channel_B_segmentation = (((x - center_x)**2 / segment_radius_x**2 + (y - center_y)**2 / segment_radius_y**2) < 1).astype(float) * 200
29+
channel_B_segmentation += 20 * np.random.rand(image_size, image_size)
30+
channel_B_segmentation = np.clip(channel_B_segmentation, 0, 255)
31+
32+
33+
# --- 2. BIASED APPROACH: Segmenting and measuring in the same channel (Channel A) ---
34+
# Calculate a threshold based on the measurement channel
35+
thresh_A = threshold_otsu(channel_A_measurement)
36+
37+
# Create a mask: only the brightest pixels are selected
38+
biased_mask = channel_A_measurement > thresh_A
39+
40+
# Measure the mean intensity within this biased mask
41+
biased_intensity = np.mean(channel_A_measurement[biased_mask])
42+
43+
44+
# --- 3. CORRECT APPROACH: Segmenting in Channel B, measuring in Channel A ---
45+
# Calculate a threshold on the ideal segmentation channel
46+
thresh_B = threshold_otsu(channel_B_segmentation)
47+
48+
# Create an unbiased mask that covers the entire object
49+
unbiased_mask = channel_B_segmentation > thresh_B
50+
51+
# Apply the unbiased mask to the measurement channel and measure the intensity
52+
unbiased_intensity = np.mean(channel_A_measurement[unbiased_mask])
53+
54+
55+
# --- 4. Visualize the results ---
56+
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
57+
fig.suptitle('Comparison of Biased vs. Unbiased Measurement Method', fontsize=20)
58+
plt.subplots_adjust(top=0.9)
59+
60+
# Column titles
61+
axes[0, 0].set_title("Source Images", fontsize=14)
62+
axes[0, 1].set_title("Problem: Segmenting the Measurement Channel", fontsize=14)
63+
axes[0, 2].set_title("Solution: Using an Independent Channel", fontsize=14)
64+
65+
# Row 1: Images and mask overlays
66+
ax = axes[0, 0]
67+
im = ax.imshow(channel_A_measurement, cmap='viridis')
68+
ax.set_ylabel("Channel A (Measurement)", fontsize=12)
69+
ax.axis('off')
70+
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
71+
72+
ax = axes[0, 1]
73+
ax.imshow(channel_A_measurement, cmap='viridis')
74+
ax.contour(biased_mask, colors='red', linewidths=1.5) # Red contour for the biased mask
75+
ax.text(5, 190, 'Mask from Channel A', color='white', backgroundcolor='red')
76+
ax.axis('off')
77+
78+
ax = axes[0, 2]
79+
ax.imshow(channel_A_measurement, cmap='viridis')
80+
ax.contour(unbiased_mask, colors='cyan', linewidths=1.5) # Cyan contour for the correct mask
81+
ax.text(5, 190, 'Mask from Channel B', color='black', backgroundcolor='cyan')
82+
ax.axis('off')
83+
84+
# Row 2: Segmentation channel and masks
85+
ax = axes[1, 0]
86+
im = ax.imshow(channel_B_segmentation, cmap='magma')
87+
ax.set_ylabel("Channel B (Segmentation)", fontsize=12)
88+
ax.axis('off')
89+
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
90+
91+
ax = axes[1, 1]
92+
ax.imshow(biased_mask, cmap='gray')
93+
ax.set_title("Mask selects only the brightest region", fontsize=12)
94+
ax.axis('off')
95+
96+
ax = axes[1, 2]
97+
ax.imshow(unbiased_mask, cmap='gray')
98+
ax.set_title("Mask covers the entire object", fontsize=12)
99+
ax.axis('off')
100+
101+
# Add the final measurement results to the bottom of the figure
102+
plt.figtext(0.5, 0.02,
103+
f"BIASED APPROACH: Measured Mean Intensity = {biased_intensity:.2f}\n"
104+
f"CORRECT APPROACH: Measured Mean Intensity = {unbiased_intensity:.2f}\n"
105+
f"DIFFERENCE: The biased measurement is artificially inflated by {((biased_intensity - unbiased_intensity) / unbiased_intensity) * 100:.1f}%!",
106+
ha="center", fontsize=16, bbox={"facecolor":"lightcoral", "alpha":0.6, "pad":10})
107+
108+
plt.tight_layout(rect=[0, 0.08, 1, 0.95])
109+
plt.show()

0 commit comments

Comments
 (0)