Skip to content

Commit 5049f2a

Browse files
authored
Merge pull request #23 from AllenNeuralDynamics/downsample-strided-conv
refactor: improved foreground sampling
2 parents a1a0cf5 + fbba620 commit 5049f2a

2 files changed

Lines changed: 64 additions & 57 deletions

File tree

src/aind_exaspim_image_compression/machine_learning/data_handling.py

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ def __init__(
4545
anisotropy=(0.748, 0.748, 1.0),
4646
boundary_buffer=5000,
4747
foreground_sampling_rate=0.3,
48-
min_brightness=200,
4948
n_examples_per_epoch=300,
50-
normalization_percentiles=(0.5, 99.9),
49+
normalization_percentiles=(1, 99.9),
5150
prefetch_foreground_sampling=16,
5251
sigma_bm4d=16,
5352
):
@@ -58,7 +57,6 @@ def __init__(
5857
self.anisotropy = anisotropy
5958
self.boundary_buffer = boundary_buffer
6059
self.foreground_sampling_rate = foreground_sampling_rate
61-
self.min_brightness = min_brightness
6260
self.n_examples_per_epoch = n_examples_per_epoch
6361
self.normalization_percentiles = normalization_percentiles
6462
self.patch_shape = patch_shape
@@ -163,15 +161,13 @@ def __getitem__(self, dummy_input):
163161
164162
Returns
165163
-------
166-
tuple
167-
A tuple containing:
168-
- noise : numpy.ndarray
169-
Noisy image patch, normalized and clipped.
170-
- denoised : numpy.ndarray
171-
Denoised image patch, normalized and clipped using the same
172-
scale as the noisy patch.
173-
- (mn, mx) : Tuple[float]
174-
Lower and upper percentiles used for normalization.
164+
noise : numpy.ndarray
165+
Noisy image patch, normalized and clipped.
166+
denoised : numpy.ndarray
167+
Denoised image patch, normalized and clipped using the same scale
168+
as the noisy patch.
169+
(mn, mx) : Tuple[float]
170+
Lower and upper percentiles used for normalization.
175171
"""
176172
# Get image patches
177173
brain_id = self.sample_brain()
@@ -181,8 +177,8 @@ def __getitem__(self, dummy_input):
181177
denoised = bm4d(noise, self.sigma_bm4d)
182178

183179
# Normalize image patches
184-
noise = np.clip((noise - mn) / max(mx - mn, 1), 0, 5)
185-
denoised = np.clip((denoised - mn) / max(mx - mn, 1), 0, 5)
180+
noise = np.clip((noise - mn) / (mx - mn + 1e-8), 0, 5)
181+
denoised = np.clip((denoised - mn) / (mx - mn + 1e-8), 0, 5)
186182
return noise, denoised, (mn, mx)
187183

188184
def sample_brain(self):
@@ -230,7 +226,7 @@ def sample_foreground_voxel(self, brain_id):
230226
Tuple[int]
231227
Voxel coordinate representing a likely foreground location.
232228
"""
233-
if self.skeletons[brain_id] is not None and np.random.random() > 0.5:
229+
if self.skeletons[brain_id] is not None:
234230
return self.sample_skeleton_voxel(brain_id)
235231
elif self.segmentations[brain_id] is not None:
236232
return self.sample_segmentation_voxel(brain_id)
@@ -294,11 +290,11 @@ def sample_segmentation_voxel(self, brain_id):
294290
Voxel coordinate whose patch contains a sufficiently large object
295291
or had the largest object after 5 * self.prefetch attempts.
296292
"""
297-
cnt = 0
293+
best_volume = 0
298294
best_voxel = self.sample_interior_voxel(brain_id)
299-
max_volume = 0
300-
while max_volume < 3000:
301-
with ThreadPoolExecutor() as executor:
295+
cnt = 0
296+
with ThreadPoolExecutor() as executor:
297+
while best_volume < 1600:
302298
# Read random image patches
303299
pending = dict()
304300
for _ in range(self.prefetch_foreground_sampling):
@@ -318,14 +314,14 @@ def sample_segmentation_voxel(self, brain_id):
318314

319315
if len(cnts) > 1:
320316
volume = np.max(cnts[1:])
321-
if volume > max_volume:
317+
if volume > best_volume:
322318
best_voxel = voxel
323-
max_volume = volume
319+
best_volume = volume
324320

325-
# Check number of tries
326-
cnt += 1
327-
if cnt > 5:
328-
break
321+
# Check number of tries
322+
cnt += 1
323+
if cnt > 5:
324+
break
329325
return best_voxel
330326

331327
def sample_bright_voxel(self, brain_id):
@@ -339,15 +335,15 @@ def sample_bright_voxel(self, brain_id):
339335
340336
Returns
341337
-------
342-
brightest_voxel : Tuple[int]
338+
best_voxel : Tuple[int]
343339
Voxel coordinate whose patch is sufficiently bright or is the
344-
highest observed brightness after 5 * self.prefetch attempts.
340+
highest observed brightness after 4 * self.prefetch attempts.
345341
"""
342+
best_brightness = 0
343+
best_voxel = self.sample_interior_voxel(brain_id)
346344
cnt = 0
347-
brightest_voxel = self.sample_interior_voxel(brain_id)
348-
max_brightness = 0
349-
while max_brightness < self.min_brightness:
350-
with ThreadPoolExecutor() as executor:
345+
with ThreadPoolExecutor() as executor:
346+
while best_brightness < 1600:
351347
# Read random image patches
352348
pending = dict()
353349
for _ in range(self.prefetch_foreground_sampling):
@@ -361,19 +357,16 @@ def sample_bright_voxel(self, brain_id):
361357
for thread in as_completed(pending.keys()):
362358
voxel = pending.pop(thread)
363359
img_patch = thread.result()
364-
brightness = np.sum(img_patch > 500)
365-
if brightness > 100:
366-
brightest_voxel = voxel
367-
max_brightness = brightness
368-
369-
if max_brightness > self.min_brightness:
370-
break
371-
372-
# Check number of tries
373-
cnt += 1
374-
if cnt > 5:
375-
break
376-
return brightest_voxel
360+
brightness = np.sum(img_patch > 100)
361+
if brightness > best_brightness:
362+
best_voxel = voxel
363+
best_brightness = brightness
364+
365+
# Check number of tries
366+
cnt += 1
367+
if cnt > 5:
368+
break
369+
return best_voxel
377370

378371
# --- Helpers ---
379372
def __len__(self):
@@ -422,8 +415,15 @@ def read_precomputed_patch(self, brain_id, center):
422415
numpy.ndarray
423416
Image patch.
424417
"""
425-
s = img_util.get_slices(center, self.patch_shape)
426-
return self.segmentations[brain_id][s].read().result()
418+
try:
419+
s = img_util.get_slices(center, self.patch_shape)
420+
return self.segmentations[brain_id][s].read().result()
421+
except Exception as e:
422+
print("Exception:", e)
423+
print("Brain ID:", brain_id)
424+
print("img.shape:", self.imgs[brain_id].shape)
425+
print("label_mask.shape:", self.segmentations[brain_id].shape)
426+
return np.zeros(self.patch_shape)
427427

428428
def to_voxels(self, xyz_arr):
429429
"""
@@ -449,8 +449,8 @@ class ValidateDataset(Dataset):
449449
def __init__(
450450
self,
451451
patch_shape,
452-
normalization_percentiles=[0.5, 99.9],
453-
sigma_bm4d=10,
452+
normalization_percentiles=(1, 99.9),
453+
sigma_bm4d=16,
454454
):
455455
"""
456456
Instantiates a ValidateDataset object.
@@ -459,12 +459,12 @@ def __init__(
459459
----------
460460
patch_shape : Tuple[int]
461461
Shape of image patches to be extracted.
462-
normalization_percentiles : List[float], optional
462+
normalization_percentiles : Tuple[float], optional
463463
Upper and lower percentiles used to normalize the input image.
464-
Default is [0.5, 99.9].
464+
Default is (0.5, 99.9).
465465
sigma_bm4d : float, optional
466466
Smoothing parameter used in the BM4D denoising algorithm. Default
467-
is 10.
467+
is 16.
468468
"""
469469
# Call parent class
470470
super(ValidateDataset, self).__init__()
@@ -523,8 +523,8 @@ def ingest_example(self, brain_id, voxel):
523523
denoised = bm4d(noise, self.sigma_bm4d)
524524

525525
# Normalize image patches
526-
noise = np.clip((noise - mn) / max(mx - mn, 1), 0, 5)
527-
denoised = np.clip((denoised - mn) / max(mx - mn, 1), 0, 5)
526+
noise = np.clip((noise - mn) / (mx - mn + 1e-8), 0, 5)
527+
denoised = np.clip((denoised - mn) / (mx - mn + 1e-8), 0, 5)
528528

529529
# Store results
530530
self.example_ids.append((brain_id, voxel))

src/aind_exaspim_image_compression/machine_learning/unet3d.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
"""
32
Created on Fri Aug 14 15:00:00 2025
43
@@ -193,7 +192,14 @@ def __init__(self, in_channels, out_channels):
193192

194193
# Instance attributes
195194
self.maxpool_conv = nn.Sequential(
196-
nn.MaxPool3d(2), DoubleConv(in_channels, out_channels)
195+
nn.Conv3d(
196+
in_channels,
197+
out_channels,
198+
kernel_size=3,
199+
stride=2,
200+
padding=1
201+
),
202+
DoubleConv(out_channels, out_channels)
197203
)
198204

199205
def forward(self, x):
@@ -273,7 +279,7 @@ def forward(self, x1, x2):
273279
274280
Returns
275281
-------
276-
torch.Tensor
282+
x : torch.Tensor
277283
Output tensor after upsampling, concatenation with the skip
278284
connection, and double convolution. The output shape is
279285
(B, out_channels, D, H2, W2).
@@ -287,7 +293,8 @@ def forward(self, x1, x2):
287293
[diffX // 2, diffX - diffX // 2, diffY // 2, diffY - diffY // 2],
288294
)
289295
x = torch.cat([x2, x1], dim=1)
290-
return self.conv(x)
296+
x = self.conv(x)
297+
return x
291298

292299

293300
class OutConv(nn.Module):

0 commit comments

Comments
 (0)