Skip to content

Commit 8cc8fb0

Browse files
stmartineau99GOESTERN-1107078
andauthored
Updated get_unsupervised_loader to use RawDatasetWithMasks (#165)
* update domain adaptation for masked raw dataset * cleanup domain_adaptation.py, remove unused args * domain_adaptation.py: fix device arg, change arg names to match main branch --------- Co-authored-by: GOESTERN-1107078 <u25914@glogin10.usr.hpc.gwdg.de>
1 parent d453dc3 commit 8cc8fb0

2 files changed

Lines changed: 44 additions & 69 deletions

File tree

synapse_net/training/domain_adaptation.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ def mean_teacher_adaptation(
3939
n_samples_val: Optional[int] = None,
4040
train_mask_paths: Optional[Tuple[str]] = None,
4141
val_mask_paths: Optional[Tuple[str]] = None,
42+
sample_mask_key: Optional[str] = None,
4243
patch_sampler: Optional[callable] = None,
43-
pseudo_label_sampler: Optional[callable] = None,
44-
device: int = 0,
4544
check: bool = False,
4645
) -> None:
4746
"""Run domain adaptation to transfer a network trained on a source domain for a supervised
@@ -52,7 +51,7 @@ def mean_teacher_adaptation(
5251
'supervised_val_paths' are not given.
5352
- semi-supervised domain adaptation: domain adaptation on unlabeled and labeled data,
5453
when 'supervised_train_paths' and 'supervised_val_paths' are given.
55-
54+
5655
Args:
5756
name: The name for the checkpoint to be trained.
5857
unsupervsied_train_paths: Filepaths to the hdf5 files or similar file formats
@@ -89,9 +88,8 @@ def mean_teacher_adaptation(
8988
based on the patch_shape and size of the volumes used for validation.
9089
train_mask_paths: Sample masks used by the patch sampler to accept or reject patches for training.
9190
val_mask_paths: Sample masks used by the patch sampler to accept or reject patches for validation.
91+
sample_mask_key: The key to the sample mask dataset inside each file.
9292
patch_sampler: Accept or reject patches based on a condition.
93-
pseudo_label_sampler: Mask out regions of the pseudo labels where the teacher is not confident before updating the gradients.
94-
device: GPU ID for training.
9593
check: Whether to check the training and validation loaders instead of running training.
9694
""" # noqa
9795
assert (supervised_train_paths is None) == (supervised_val_paths is None)
@@ -115,7 +113,7 @@ def mean_teacher_adaptation(
115113
model = torch.load(source_checkpoint, weights_only=False)
116114
reinit_teacher = False
117115

118-
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
116+
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
119117
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=5)
120118

121119
# self training functionality
@@ -130,7 +128,8 @@ def mean_teacher_adaptation(
130128
batch_size=batch_size,
131129
n_samples=n_samples_train,
132130
sample_mask_paths=train_mask_paths,
133-
sampler=patch_sampler
131+
sample_mask_key=sample_mask_key,
132+
sampler=patch_sampler,
134133
)
135134
unsupervised_val_loader = get_unsupervised_loader(
136135
data_paths=unsupervised_val_paths,
@@ -139,7 +138,8 @@ def mean_teacher_adaptation(
139138
batch_size=batch_size,
140139
n_samples=n_samples_val,
141140
sample_mask_paths=val_mask_paths,
142-
sampler=patch_sampler
141+
sample_mask_key=sample_mask_key,
142+
sampler=patch_sampler,
143143
)
144144

145145
if supervised_train_paths is not None:
@@ -165,7 +165,7 @@ def mean_teacher_adaptation(
165165
check_loader(supervised_val_loader, n_samples=4)
166166
return
167167

168-
device = torch.device(f"cuda:{device}") if torch.cuda.is_available() else torch.device("cpu")
168+
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
169169
trainer = self_training.MeanTeacherTrainer(
170170
name=name,
171171
model=model,
@@ -187,7 +187,6 @@ def mean_teacher_adaptation(
187187
device=device,
188188
reinit_teacher=reinit_teacher,
189189
save_root=save_root,
190-
sampler=pseudo_label_sampler,
191190
)
192191
trainer.fit(n_iterations)
193192

synapse_net/training/semisupervised_training.py

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
from typing import Optional, Tuple
22

3-
import numpy as np
4-
import uuid
5-
import h5py
63
import torch
74
import torch_em
85
import torch_em.self_training as self_training
96
from torchvision import transforms
7+
from torch_em.data import RawDatasetWithMasks
108

11-
from synapse_net.file_utils import read_mrc
129
from .supervised_training import get_2d_model, get_3d_model, get_supervised_loader, _determine_ndim
1310

1411

@@ -31,26 +28,6 @@ def weak_augmentations(p: float = 0.75) -> callable:
3128
])
3229
return torch_em.transform.raw.get_raw_transform(normalizer=norm, augmentation1=aug)
3330

34-
def drop_mask_channel(x):
35-
x = x[:1]
36-
return x
37-
38-
class ComposedTransform:
39-
def __init__(self, *funcs):
40-
self.funcs = funcs
41-
42-
def __call__(self, x):
43-
for f in self.funcs:
44-
x = f(x)
45-
return x
46-
47-
class ChannelSplitterSampler:
48-
def __init__(self, sampler):
49-
self.sampler = sampler
50-
51-
def __call__(self, x):
52-
raw, mask = x[0], x[1]
53-
return self.sampler(raw, mask)
5431

5532
def get_unsupervised_loader(
5633
data_paths: Tuple[str],
@@ -59,8 +36,11 @@ def get_unsupervised_loader(
5936
batch_size: int,
6037
n_samples: Optional[int],
6138
sample_mask_paths: Optional[Tuple[str]] = None,
39+
sample_mask_key: Optional[str] = None,
40+
bg_mask_paths: Optional[Tuple[str]] = None,
41+
bg_mask_key: Optional[str] = None,
6242
sampler: Optional[callable] = None,
63-
exclude_top_and_bottom: bool = False,
43+
exclude_top_and_bottom: bool = False,
6444
) -> torch.utils.data.DataLoader:
6545
"""Get a dataloader for unsupervised segmentation training.
6646
@@ -73,61 +53,57 @@ def get_unsupervised_loader(
7353
batch_size: The batch size for training.
7454
n_samples: The number of samples per epoch. By default this will be estimated
7555
based on the patch_shape and size of the volumes used for training.
76-
exclude_top_and_bottom: Whether to exluce the five top and bottom slices to
77-
avoid artifacts at the border of tomograms.
7856
sample_mask_paths: The filepaths to the corresponding sample masks for each tomogram.
57+
sample_mask_key: The key to the sample mask dataset inside each file.
58+
bg_mask_paths: The filepaths to the background masks for each tomogram.
59+
bg_mask_key: The key to the background mask dataset inside each file.
7960
sampler: Accept or reject patches based on a condition.
61+
exclude_top_and_bottom: Whether to exclude the five top and bottom slices to
62+
avoid artifacts at the border of tomograms.
8063
8164
Returns:
8265
The PyTorch dataloader.
8366
"""
84-
# We exclude the top and bottom slices where the tomogram reconstruction is bad.
85-
# TODO this seems unneccesary if we have a boundary mask - remove?
8667
if exclude_top_and_bottom:
87-
roi = np.s_[5:-5, :, :]
68+
roi = (slice(5, -5), slice(None), slice(None))
8869
else:
8970
roi = None
90-
# stack tomograms and masks and write to temp files to use as input to RawDataset()
71+
9172
if sample_mask_paths is not None:
9273
assert len(data_paths) == len(sample_mask_paths), \
93-
f"Expected equal number of data_paths and and sample_masks_paths, got {len(data_paths)} data paths and {len(sample_mask_paths)} mask paths."
94-
95-
stacked_paths = []
96-
for i, (data_path, mask_path) in enumerate(zip(data_paths, sample_mask_paths)):
97-
raw = read_mrc(data_path)[0]
98-
mask = read_mrc(mask_path)[0]
99-
stacked = np.stack([raw, mask], axis=0)
100-
101-
tmp_path = f"/tmp/stacked{i}_{uuid.uuid4().hex}.h5"
102-
with h5py.File(tmp_path, "w") as f:
103-
f.create_dataset("raw", data=stacked, compression="gzip")
104-
stacked_paths.append(tmp_path)
105-
106-
# update variables for RawDataset()
107-
data_paths = tuple(stacked_paths)
108-
base_transform = torch_em.transform.get_raw_transform()
109-
raw_transform = ComposedTransform(base_transform, drop_mask_channel)
110-
sampler = ChannelSplitterSampler(sampler)
111-
with_channels = True
112-
else:
113-
raw_transform = torch_em.transform.get_raw_transform()
114-
with_channels = False
115-
sampler = None
74+
f"Expected equal number of data_paths and sample_mask_paths, got {len(data_paths)} and {len(sample_mask_paths)}."
75+
if bg_mask_paths is not None:
76+
assert len(data_paths) == len(bg_mask_paths), \
77+
f"Expected equal number of data_paths and bg_mask_paths, got {len(data_paths)} and {len(bg_mask_paths)}."
11678

11779
_, ndim = _determine_ndim(patch_shape)
80+
raw_transform = torch_em.transform.get_raw_transform()
11881
transform = torch_em.transform.get_augmentations(ndim=ndim)
82+
augmentations = (weak_augmentations(), weak_augmentations())
11983

12084
if n_samples is None:
12185
n_samples_per_ds = None
12286
else:
12387
n_samples_per_ds = int(n_samples / len(data_paths))
12488

125-
augmentations = (weak_augmentations(), weak_augmentations())
126-
12789
datasets = [
128-
torch_em.data.RawDataset(path, raw_key, patch_shape, raw_transform, transform, roi=roi,
129-
n_samples=n_samples_per_ds, sampler=sampler, ndim=ndim, with_channels=with_channels, augmentations=augmentations)
130-
for path in data_paths
90+
RawDatasetWithMasks(
91+
raw_path=data_path,
92+
raw_key=raw_key,
93+
patch_shape=patch_shape,
94+
raw_transform=raw_transform,
95+
transform=transform,
96+
roi=roi,
97+
n_samples=n_samples_per_ds,
98+
sampler=sampler,
99+
ndim=ndim,
100+
augmentations=augmentations,
101+
sample_mask_path=sample_mask_paths[i] if sample_mask_paths is not None else None,
102+
sample_mask_key=sample_mask_key,
103+
bg_mask_path=bg_mask_paths[i] if bg_mask_paths is not None else None,
104+
bg_mask_key=bg_mask_key,
105+
)
106+
for i, data_path in enumerate(data_paths)
131107
]
132108
ds = torch.utils.data.ConcatDataset(datasets)
133109

0 commit comments

Comments
 (0)