synapse_net.training.domain_adaptation

  1import os
  2import tempfile
  3from glob import glob
  4from pathlib import Path
  5from typing import Optional, Tuple
  6
  7from sklearn.model_selection import train_test_split
  8
  9import mrcfile
 10import torch
 11import torch_em
 12import torch_em.self_training as self_training
 13from elf.io import open_file
 14
 15from torch_em.transform.invertible_augmentations import MeanTeacherAugmenters, DEFAULT_WEAK_AUGMENTATIONS
 16
 17from .dataloaders import get_supervised_loader, get_unsupervised_loader, _determine_ndim
 18from .models import get_raw_transform, _check_backbone, _get_model
 19from .supervised_training import _derive_key_from_files
 20from ..inference.inference import get_model_path, compute_scale_from_voxel_size, get_available_models
 21from ..inference.util import _restore_legacy_attributes, _Scaler
 22
 23
 24def mean_teacher_adaptation(
 25    name: str,
 26    unsupervised_train_paths: Tuple[str],
 27    unsupervised_val_paths: Tuple[str],
 28    patch_shape: Tuple[int, int, int],
 29    save_root: Optional[str] = None,
 30    source_checkpoint: Optional[str] = None,
 31    supervised_train_paths: Optional[Tuple[str]] = None,
 32    supervised_val_paths: Optional[Tuple[str]] = None,
 33    confidence_threshold: float = 0.9,
 34    raw_key: str = "raw",
 35    raw_key_supervised: str = "raw",
 36    label_key: Optional[str] = None,
 37    batch_size: int = 1,
 38    lr: float = 1e-4,
 39    n_iterations: int = int(1e4),
 40    n_samples_train: Optional[int] = None,
 41    n_samples_val: Optional[int] = None,
 42    train_mask_paths: Optional[Tuple[str]] = None,
 43    val_mask_paths: Optional[Tuple[str]] = None,
 44    sample_mask_key: Optional[str] = None,
 45    unsupervised_sampler: Optional[callable] = None,
 46    supervised_sampler: Optional[callable] = None,
 47    backbone: Optional[str] = None,
 48    model_type: Optional[str] = None,
 49    encoder_checkpoint: Optional[str] = None,
 50    aug_dict: Optional[dict] = None,
 51    separate_backward: bool = False,
 52    check: bool = False,
 53) -> None:
 54    """Run domain adaptation to transfer a network trained on a source domain for a supervised
 55    segmentation task to perform this task on a different target domain.
 56
 57    We support different domain adaptation settings:
 58    - unsupervised domain adaptation: the default mode when 'supervised_train_paths' and
 59     'supervised_val_paths' are not given.
 60    - semi-supervised domain adaptation: domain adaptation on unlabeled and labeled data,
 61      when 'supervised_train_paths' and 'supervised_val_paths' are given.
 62
 63    Args:
 64        name: The name for the checkpoint to be trained.
 65        unsupervised_train_paths: Filepaths to the hdf5 files or similar file formats
 66            for the training data in the target domain.
 67            This training data is used for unsupervised learning, so it does not require labels.
 68        unsupervised_val_paths: Filepaths to the hdf5 files or similar file formats
 69            for the validation data in the target domain.
 70            This validation data is used for unsupervised learning, so it does not require labels.
 71        patch_shape: The patch shape used for a training example.
 72            In order to run 2d training pass a patch shape with a singleton in the z-axis,
 73            e.g. 'patch_shape = [1, 512, 512]'.
 74        save_root: Folder where the checkpoint will be saved.
 75        source_checkpoint: Checkpoint to the initial model trained on the source domain.
 76            This is used to initialize the teacher model.
 77            If the checkpoint is not given, then both student and teacher model are initialized
 78            from scratch. In this case `supervised_train_paths` and `supervised_val_paths` have to
 79            be given in order to provide training data from the source domain.
 80        supervised_train_paths: Filepaths to the hdf5 files for the training data in the source domain.
 81            This training data is optional. If given, it is used for supervised learning and requires labels.
 82        supervised_val_paths: Filepaths to the hdf5 files for the validation data in the source domain.
 83            This validation data is optional. If given, it is used for supervised learning and requires labels.
 84        confidence_threshold: The threshold for filtering data in the unsupervised loss.
 85            The label filtering is done based on the uncertainty of network predictions, and only
 86            the data with higher certainty than this threshold is used for training.
 87        raw_key: The key that holds the raw data inside of the hdf5 or similar files.
 88        raw_key_supervised: The key that holds the raw data inside of the hdf5 files for supervised learning.
 89        label_key: The key that holds the labels inside of the hdf5 files for supervised learning.
 90            This is only required if `supervised_train_paths` and `supervised_val_paths` are given.
 91        batch_size: The batch size for training.
 92        lr: The initial learning rate.
 93        n_iterations: The number of iterations to train for.
 94        n_samples_train: The number of train samples per epoch. By default this will be estimated
 95            based on the patch_shape and size of the volumes used for training.
 96        n_samples_val: The number of val samples per epoch. By default this will be estimated
 97            based on the patch_shape and size of the volumes used for validation.
 98        train_mask_paths: Sample masks used by the unsupervised sampler to accept or reject patches for training.
 99        val_mask_paths: Sample masks used by the unsupervised sampler to accept or reject patches for validation.
100        sample_mask_key: The key to the sample mask dataset inside each file.
101        unsupervised_sampler: Sampler to accept or reject patches for the unsupervised data stream.
102        supervised_sampler: Sampler to accept or reject patches for the supervised data stream.
103            Pass `False` to disable.
104        backbone: The pretrained encoder. Options: "sam", "sam2", "dinov2", "dinov3" (ViT encoders of a UNETR),
105            or "cryosiam" (3D CNN encoder of a CryoZooUNet).
106            The ViT backbones must be set together with `model_type`.
107        model_type: Model type for the selected ViT `backbone` model family, for example "vit_b" or
108            "vit_b_em_organelles" for "sam".
109        encoder_checkpoint: Path to the pretrained encoder weights for `backbone`.
110            By default they are fetched for `backbone` and `model_type`.
111        aug_dict: The augmentations for the teacher and the student, with keys "intensity" and "geometrical".
112            Defaults to Gaussian blur and noise, plus random flips and 90-degree rotations.
113            The noise is scaled with `clip_max` to match the range of `raw_transform`.
114        separate_backward: Whether to backpropagate each loss term separately to reduce peak memory.
115        check: Whether to check the training and validation loaders instead of running training.
116    """
117    assert (supervised_train_paths is None) == (supervised_val_paths is None)
118    _check_backbone(backbone, model_type, patch_shape)
119    is_2d, _ = _determine_ndim(patch_shape)
120
121    if source_checkpoint is None:
122        # training from scratch only makes sense if we have supervised training data
123        # that's why we have the assertion here.
124        assert supervised_train_paths is not None
125        print("Mean teacher training from scratch (AdaMT).")
126        model = _get_model(
127            patch_shape, out_channels=2, backbone=backbone,
128            model_type=model_type, encoder_checkpoint=encoder_checkpoint,
129        )
130        reinit_teacher = True
131    else:
132        print("Mean teacher training initialized from source model:", source_checkpoint)
133        if os.path.isdir(source_checkpoint):
134            model = torch_em.util.load_model(source_checkpoint)
135        else:
136            model = _restore_legacy_attributes(torch.load(source_checkpoint, weights_only=False))
137        reinit_teacher = False
138
139    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
140    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=5)
141
142    # self training functionality
143    pseudo_labeler = self_training.DefaultPseudoLabeler(confidence_threshold=confidence_threshold)
144    loss = self_training.SelfTrainingLossWithInvertibleAugmentations()
145    loss_and_metric = self_training.SelfTrainingLossAndMetricWithInvertibleAugmentations()
146
147    raw_transform, clip_max = get_raw_transform(backbone)
148
149    ndim = 2 if is_2d else 3
150    if aug_dict is None:
151        aug_dict = {
152            "intensity": {
153                "RandomGaussianBlur": {"kernel_size": (19, 19), "sigma": (0.1, 3.0)},
154                "RandomGaussianNoise": {"mean": 0.0, "std": 0.1 if clip_max is None else 0.015 * clip_max},
155            },
156            "geometrical": DEFAULT_WEAK_AUGMENTATIONS["geometrical"],
157        }
158    augmenters = MeanTeacherAugmenters(ndim=ndim, clip_max=clip_max, aug_dict=aug_dict)
159
160    unsupervised_train_loader = get_unsupervised_loader(
161        data_paths=unsupervised_train_paths,
162        raw_key=raw_key,
163        patch_shape=patch_shape,
164        batch_size=batch_size,
165        n_samples=n_samples_train,
166        sample_mask_paths=train_mask_paths,
167        sample_mask_key=sample_mask_key,
168        sampler=unsupervised_sampler,
169        raw_transform=raw_transform,
170    )
171    unsupervised_val_loader = get_unsupervised_loader(
172        data_paths=unsupervised_val_paths,
173        raw_key=raw_key,
174        patch_shape=patch_shape,
175        batch_size=batch_size,
176        n_samples=n_samples_val,
177        sample_mask_paths=val_mask_paths,
178        sample_mask_key=sample_mask_key,
179        sampler=unsupervised_sampler,
180        raw_transform=raw_transform,
181    )
182
183    if supervised_train_paths is not None:
184        assert label_key is not None
185        supervised_train_loader = get_supervised_loader(
186            supervised_train_paths, raw_key_supervised, label_key,
187            patch_shape, batch_size, n_samples=n_samples_train,
188            sampler=supervised_sampler, raw_transform=raw_transform,
189        )
190        supervised_val_loader = get_supervised_loader(
191            supervised_val_paths, raw_key_supervised, label_key,
192            patch_shape, batch_size, n_samples=n_samples_val,
193            sampler=supervised_sampler, raw_transform=raw_transform,
194        )
195    else:
196        supervised_train_loader = None
197        supervised_val_loader = None
198
199    if check:
200        from torch_em.util.debug import check_loader
201        check_loader(unsupervised_train_loader, n_samples=4)
202        check_loader(unsupervised_val_loader, n_samples=4)
203        if supervised_train_loader is not None:
204            check_loader(supervised_train_loader, n_samples=4)
205            check_loader(supervised_val_loader, n_samples=4)
206        return
207
208    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
209    trainer = self_training.MeanTeacherTrainerWithInvertibleAugmentations(
210        name=name,
211        model=model,
212        optimizer=optimizer,
213        lr_scheduler=scheduler,
214        pseudo_labeler=pseudo_labeler,
215        unsupervised_loss=loss,
216        unsupervised_loss_and_metric=loss_and_metric,
217        supervised_train_loader=supervised_train_loader,
218        unsupervised_train_loader=unsupervised_train_loader,
219        supervised_val_loader=supervised_val_loader,
220        unsupervised_val_loader=unsupervised_val_loader,
221        supervised_loss=loss,
222        supervised_loss_and_metric=loss_and_metric,
223        logger=self_training.SelfTrainingTensorboardLogger,
224        # On the CPU, mixed precision uses bfloat16. It is very slow without hardware support,
225        # and the kornia augmentations do not accept it.
226        mixed_precision=device.type == "cuda",
227        log_image_interval=100,
228        compile_model=False,
229        device=device,
230        reinit_teacher=reinit_teacher,
231        save_root=save_root,
232        augmenter=augmenters,
233        separate_backward=separate_backward,
234    )
235    trainer.fit(n_iterations)
236
237
238# TODO patch shapes for other models
239PATCH_SHAPES = {
240    "vesicles_3d": [48, 256, 256],
241}
242"""@private
243"""
244
245
246def _get_paths(input_folder, pattern, resize_training_data, model_name, tmp_dir, val_fraction):
247    files = sorted(glob(os.path.join(input_folder, "**", pattern), recursive=True))
248    if len(files) == 0:
249        raise ValueError(f"Could not load any files from {input_folder} with pattern {pattern}")
250
251    # Heuristic: if we have less then 4 files then we crop a part of the volumes for validation.
252    # And resave the volumes.
253    resave_val_crops = len(files) < 4
254
255    # We only resave the data if we resave val crops or resize the training data
256    resave_data = resave_val_crops or resize_training_data
257    if not resave_data:
258        train_paths, val_paths = train_test_split(files, test_size=val_fraction)
259        return train_paths, val_paths
260
261    train_paths, val_paths = [], []
262    for file_path in files:
263        file_name = os.path.basename(file_path)
264        data = open_file(file_path, mode="r")["data"][:]
265
266        if resize_training_data:
267            with mrcfile.open(file_path) as f:
268                voxel_size = f.voxel_size
269            voxel_size = {ax: vox_size / 10.0 for ax, vox_size in zip("xyz", voxel_size.item())}
270            scale = compute_scale_from_voxel_size(voxel_size, model_name)
271            scaler = _Scaler(scale, verbose=False)
272            data = scaler.scale_input(data)
273
274        if resave_val_crops:
275            n_slices = data.shape[0]
276            val_slice = int((1.0 - val_fraction) * n_slices)
277            train_data, val_data = data[:val_slice], data[val_slice:]
278
279            train_path = os.path.join(tmp_dir, Path(file_name).with_suffix(".h5")).replace(".h5", "_train.h5")
280            with open_file(train_path, mode="w") as f:
281                f.create_dataset("data", data=train_data, compression="lzf")
282            train_paths.append(train_path)
283
284            val_path = os.path.join(tmp_dir, Path(file_name).with_suffix(".h5")).replace(".h5", "_val.h5")
285            with open_file(val_path, mode="w") as f:
286                f.create_dataset("data", data=val_data, compression="lzf")
287            val_paths.append(val_path)
288
289        else:
290            output_path = os.path.join(tmp_dir, Path(file_name).with_suffix(".h5"))
291            with open_file(output_path, mode="w") as f:
292                f.create_dataset("data", data=data, compression="lzf")
293            train_paths.append(output_path)
294
295    if not resave_val_crops:
296        train_paths, val_paths = train_test_split(train_paths, test_size=val_fraction)
297
298    return train_paths, val_paths
299
300
301def _parse_patch_shape(patch_shape, model_name):
302    if patch_shape is None:
303        patch_shape = PATCH_SHAPES[model_name]
304    return patch_shape
305
306
307def _resolve_source_checkpoint(source_model, source_checkpoint):
308    if source_checkpoint is None:
309        return get_model_path(source_model)
310    if not os.path.exists(source_checkpoint):
311        raise ValueError(f"The source checkpoint does not exist: {source_checkpoint}")
312    return source_checkpoint
313
314
315def main():
316    """@private
317    """
318    import argparse
319
320    parser = argparse.ArgumentParser(
321        description="Adapt a model to data from a different domain using unsupervised domain adaptation.\n\n"
322        "You can use this function to adapt the SynapseNet model for vesicle segmentation like this:\n"
323        "synapse_net.run_domain_adaptation -n adapted_model -i /path/to/data --file_pattern *.mrc --source_model vesicles_3d\n"  # noqa
324        "The trained model will be saved in the folder 'checkpoints/adapted_model' (or whichever name you pass to the '-n' argument)."  # noqa
325        "You can then use this model for segmentation with the SynapseNet GUI or CLI. "
326        "Check out the information below for details on the arguments of this function.",
327        formatter_class=argparse.RawTextHelpFormatter
328    )
329    parser.add_argument("--name", "-n", required=True, help="The name of the model to be trained. ")
330    parser.add_argument("--input_folder", "-i", required=True, help="The folder with the training data.")
331    parser.add_argument("--file_pattern", default="*",
332                        help="The pattern for selecting files for training. For example '*.mrc' to select mrc files.")
333    parser.add_argument("--key", help="The internal file path for the training data. Will be derived from the file extension by default.")  # noqa
334    available_models = get_available_models()
335    parser.add_argument(
336        "--source_model",
337        default="vesicles_3d",
338        help="The source model sets the default checkpoint and the metadata for patch size and data resizing. "
339        "By default the model 'vesicles_3d' for vesicle segmentation in volumetric data is used.\n"
340        "The --source_checkpoint argument overrides the default checkpoint.\n"
341        f"The following source models are available: {available_models}"
342    )
343    parser.add_argument(
344        "--source_checkpoint",
345        help="Path to a custom source checkpoint directory or serialized PyTorch model. "
346        "If not given, the command downloads the checkpoint for --source_model."
347    )
348    parser.add_argument(
349        "--resize_training_data", action="store_true",
350        help="Whether to resize the training data to fit the voxel size of the source model's trainign data."
351    )
352    parser.add_argument("--n_iterations", type=int, default=int(1e4), help="The number of iterations for training.")
353    parser.add_argument(
354        "--patch_shape", nargs=3, type=int,
355        help="The patch shape for training. By default the patch shape the source model was trained with is used."
356    )
357
358    # More optional argument:
359    parser.add_argument("--batch_size", type=int, default=1, help="The batch size for training.")
360    parser.add_argument("--n_samples_train", type=int, help="The number of samples per epoch for training. If not given will be derived from the data size.")  # noqa
361    parser.add_argument("--n_samples_val", type=int, help="The number of samples per epoch for validation. If not given will be derived from the data size.")  # noqa
362    parser.add_argument("--val_fraction", type=float, default=0.15, help="The fraction of the data to use for validation. This has no effect if 'val_folder' and 'val_label_folder' were passed.")  # noqa
363    parser.add_argument("--check", action="store_true", help="Visualize samples from the data loaders to ensure correct data instead of running training.")  # noqa
364    parser.add_argument("--save_root", help="Root path for saving the checkpoint and log dir.")
365
366    args = parser.parse_args()
367
368    try:
369        source_checkpoint = _resolve_source_checkpoint(args.source_model, args.source_checkpoint)
370    except ValueError as error:
371        parser.error(str(error))
372    patch_shape = _parse_patch_shape(args.patch_shape, args.source_model)
373    with tempfile.TemporaryDirectory() as tmp_dir:
374        unsupervised_train_paths, unsupervised_val_paths = _get_paths(
375            args.input_folder, args.file_pattern, args.resize_training_data,
376            args.source_model, tmp_dir, args.val_fraction,
377        )
378        unsupervised_train_paths, raw_key = _derive_key_from_files(unsupervised_train_paths, args.key)
379
380        mean_teacher_adaptation(
381            name=args.name,
382            unsupervised_train_paths=unsupervised_train_paths,
383            unsupervised_val_paths=unsupervised_val_paths,
384            patch_shape=patch_shape,
385            source_checkpoint=source_checkpoint,
386            raw_key=raw_key,
387            n_iterations=args.n_iterations,
388            batch_size=args.batch_size,
389            n_samples_train=args.n_samples_train,
390            n_samples_val=args.n_samples_val,
391            check=args.check,
392            save_root=args.save_root,
393        )
def mean_teacher_adaptation( name: str, unsupervised_train_paths: Tuple[str], unsupervised_val_paths: Tuple[str], patch_shape: Tuple[int, int, int], save_root: Optional[str] = None, source_checkpoint: Optional[str] = None, supervised_train_paths: Optional[Tuple[str]] = None, supervised_val_paths: Optional[Tuple[str]] = None, confidence_threshold: float = 0.9, raw_key: str = 'raw', raw_key_supervised: str = 'raw', label_key: Optional[str] = None, batch_size: int = 1, lr: float = 0.0001, n_iterations: int = 10000, n_samples_train: Optional[int] = None, n_samples_val: Optional[int] = None, train_mask_paths: Optional[Tuple[str]] = None, val_mask_paths: Optional[Tuple[str]] = None, sample_mask_key: Optional[str] = None, unsupervised_sampler: Optional[<built-in function callable>] = None, supervised_sampler: Optional[<built-in function callable>] = None, backbone: Optional[str] = None, model_type: Optional[str] = None, encoder_checkpoint: Optional[str] = None, aug_dict: Optional[dict] = None, separate_backward: bool = False, check: bool = False) -> None:
 25def mean_teacher_adaptation(
 26    name: str,
 27    unsupervised_train_paths: Tuple[str],
 28    unsupervised_val_paths: Tuple[str],
 29    patch_shape: Tuple[int, int, int],
 30    save_root: Optional[str] = None,
 31    source_checkpoint: Optional[str] = None,
 32    supervised_train_paths: Optional[Tuple[str]] = None,
 33    supervised_val_paths: Optional[Tuple[str]] = None,
 34    confidence_threshold: float = 0.9,
 35    raw_key: str = "raw",
 36    raw_key_supervised: str = "raw",
 37    label_key: Optional[str] = None,
 38    batch_size: int = 1,
 39    lr: float = 1e-4,
 40    n_iterations: int = int(1e4),
 41    n_samples_train: Optional[int] = None,
 42    n_samples_val: Optional[int] = None,
 43    train_mask_paths: Optional[Tuple[str]] = None,
 44    val_mask_paths: Optional[Tuple[str]] = None,
 45    sample_mask_key: Optional[str] = None,
 46    unsupervised_sampler: Optional[callable] = None,
 47    supervised_sampler: Optional[callable] = None,
 48    backbone: Optional[str] = None,
 49    model_type: Optional[str] = None,
 50    encoder_checkpoint: Optional[str] = None,
 51    aug_dict: Optional[dict] = None,
 52    separate_backward: bool = False,
 53    check: bool = False,
 54) -> None:
 55    """Run domain adaptation to transfer a network trained on a source domain for a supervised
 56    segmentation task to perform this task on a different target domain.
 57
 58    We support different domain adaptation settings:
 59    - unsupervised domain adaptation: the default mode when 'supervised_train_paths' and
 60     'supervised_val_paths' are not given.
 61    - semi-supervised domain adaptation: domain adaptation on unlabeled and labeled data,
 62      when 'supervised_train_paths' and 'supervised_val_paths' are given.
 63
 64    Args:
 65        name: The name for the checkpoint to be trained.
 66        unsupervised_train_paths: Filepaths to the hdf5 files or similar file formats
 67            for the training data in the target domain.
 68            This training data is used for unsupervised learning, so it does not require labels.
 69        unsupervised_val_paths: Filepaths to the hdf5 files or similar file formats
 70            for the validation data in the target domain.
 71            This validation data is used for unsupervised learning, so it does not require labels.
 72        patch_shape: The patch shape used for a training example.
 73            In order to run 2d training pass a patch shape with a singleton in the z-axis,
 74            e.g. 'patch_shape = [1, 512, 512]'.
 75        save_root: Folder where the checkpoint will be saved.
 76        source_checkpoint: Checkpoint to the initial model trained on the source domain.
 77            This is used to initialize the teacher model.
 78            If the checkpoint is not given, then both student and teacher model are initialized
 79            from scratch. In this case `supervised_train_paths` and `supervised_val_paths` have to
 80            be given in order to provide training data from the source domain.
 81        supervised_train_paths: Filepaths to the hdf5 files for the training data in the source domain.
 82            This training data is optional. If given, it is used for supervised learning and requires labels.
 83        supervised_val_paths: Filepaths to the hdf5 files for the validation data in the source domain.
 84            This validation data is optional. If given, it is used for supervised learning and requires labels.
 85        confidence_threshold: The threshold for filtering data in the unsupervised loss.
 86            The label filtering is done based on the uncertainty of network predictions, and only
 87            the data with higher certainty than this threshold is used for training.
 88        raw_key: The key that holds the raw data inside of the hdf5 or similar files.
 89        raw_key_supervised: The key that holds the raw data inside of the hdf5 files for supervised learning.
 90        label_key: The key that holds the labels inside of the hdf5 files for supervised learning.
 91            This is only required if `supervised_train_paths` and `supervised_val_paths` are given.
 92        batch_size: The batch size for training.
 93        lr: The initial learning rate.
 94        n_iterations: The number of iterations to train for.
 95        n_samples_train: The number of train samples per epoch. By default this will be estimated
 96            based on the patch_shape and size of the volumes used for training.
 97        n_samples_val: The number of val samples per epoch. By default this will be estimated
 98            based on the patch_shape and size of the volumes used for validation.
 99        train_mask_paths: Sample masks used by the unsupervised sampler to accept or reject patches for training.
100        val_mask_paths: Sample masks used by the unsupervised sampler to accept or reject patches for validation.
101        sample_mask_key: The key to the sample mask dataset inside each file.
102        unsupervised_sampler: Sampler to accept or reject patches for the unsupervised data stream.
103        supervised_sampler: Sampler to accept or reject patches for the supervised data stream.
104            Pass `False` to disable.
105        backbone: The pretrained encoder. Options: "sam", "sam2", "dinov2", "dinov3" (ViT encoders of a UNETR),
106            or "cryosiam" (3D CNN encoder of a CryoZooUNet).
107            The ViT backbones must be set together with `model_type`.
108        model_type: Model type for the selected ViT `backbone` model family, for example "vit_b" or
109            "vit_b_em_organelles" for "sam".
110        encoder_checkpoint: Path to the pretrained encoder weights for `backbone`.
111            By default they are fetched for `backbone` and `model_type`.
112        aug_dict: The augmentations for the teacher and the student, with keys "intensity" and "geometrical".
113            Defaults to Gaussian blur and noise, plus random flips and 90-degree rotations.
114            The noise is scaled with `clip_max` to match the range of `raw_transform`.
115        separate_backward: Whether to backpropagate each loss term separately to reduce peak memory.
116        check: Whether to check the training and validation loaders instead of running training.
117    """
118    assert (supervised_train_paths is None) == (supervised_val_paths is None)
119    _check_backbone(backbone, model_type, patch_shape)
120    is_2d, _ = _determine_ndim(patch_shape)
121
122    if source_checkpoint is None:
123        # training from scratch only makes sense if we have supervised training data
124        # that's why we have the assertion here.
125        assert supervised_train_paths is not None
126        print("Mean teacher training from scratch (AdaMT).")
127        model = _get_model(
128            patch_shape, out_channels=2, backbone=backbone,
129            model_type=model_type, encoder_checkpoint=encoder_checkpoint,
130        )
131        reinit_teacher = True
132    else:
133        print("Mean teacher training initialized from source model:", source_checkpoint)
134        if os.path.isdir(source_checkpoint):
135            model = torch_em.util.load_model(source_checkpoint)
136        else:
137            model = _restore_legacy_attributes(torch.load(source_checkpoint, weights_only=False))
138        reinit_teacher = False
139
140    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
141    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=5)
142
143    # self training functionality
144    pseudo_labeler = self_training.DefaultPseudoLabeler(confidence_threshold=confidence_threshold)
145    loss = self_training.SelfTrainingLossWithInvertibleAugmentations()
146    loss_and_metric = self_training.SelfTrainingLossAndMetricWithInvertibleAugmentations()
147
148    raw_transform, clip_max = get_raw_transform(backbone)
149
150    ndim = 2 if is_2d else 3
151    if aug_dict is None:
152        aug_dict = {
153            "intensity": {
154                "RandomGaussianBlur": {"kernel_size": (19, 19), "sigma": (0.1, 3.0)},
155                "RandomGaussianNoise": {"mean": 0.0, "std": 0.1 if clip_max is None else 0.015 * clip_max},
156            },
157            "geometrical": DEFAULT_WEAK_AUGMENTATIONS["geometrical"],
158        }
159    augmenters = MeanTeacherAugmenters(ndim=ndim, clip_max=clip_max, aug_dict=aug_dict)
160
161    unsupervised_train_loader = get_unsupervised_loader(
162        data_paths=unsupervised_train_paths,
163        raw_key=raw_key,
164        patch_shape=patch_shape,
165        batch_size=batch_size,
166        n_samples=n_samples_train,
167        sample_mask_paths=train_mask_paths,
168        sample_mask_key=sample_mask_key,
169        sampler=unsupervised_sampler,
170        raw_transform=raw_transform,
171    )
172    unsupervised_val_loader = get_unsupervised_loader(
173        data_paths=unsupervised_val_paths,
174        raw_key=raw_key,
175        patch_shape=patch_shape,
176        batch_size=batch_size,
177        n_samples=n_samples_val,
178        sample_mask_paths=val_mask_paths,
179        sample_mask_key=sample_mask_key,
180        sampler=unsupervised_sampler,
181        raw_transform=raw_transform,
182    )
183
184    if supervised_train_paths is not None:
185        assert label_key is not None
186        supervised_train_loader = get_supervised_loader(
187            supervised_train_paths, raw_key_supervised, label_key,
188            patch_shape, batch_size, n_samples=n_samples_train,
189            sampler=supervised_sampler, raw_transform=raw_transform,
190        )
191        supervised_val_loader = get_supervised_loader(
192            supervised_val_paths, raw_key_supervised, label_key,
193            patch_shape, batch_size, n_samples=n_samples_val,
194            sampler=supervised_sampler, raw_transform=raw_transform,
195        )
196    else:
197        supervised_train_loader = None
198        supervised_val_loader = None
199
200    if check:
201        from torch_em.util.debug import check_loader
202        check_loader(unsupervised_train_loader, n_samples=4)
203        check_loader(unsupervised_val_loader, n_samples=4)
204        if supervised_train_loader is not None:
205            check_loader(supervised_train_loader, n_samples=4)
206            check_loader(supervised_val_loader, n_samples=4)
207        return
208
209    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
210    trainer = self_training.MeanTeacherTrainerWithInvertibleAugmentations(
211        name=name,
212        model=model,
213        optimizer=optimizer,
214        lr_scheduler=scheduler,
215        pseudo_labeler=pseudo_labeler,
216        unsupervised_loss=loss,
217        unsupervised_loss_and_metric=loss_and_metric,
218        supervised_train_loader=supervised_train_loader,
219        unsupervised_train_loader=unsupervised_train_loader,
220        supervised_val_loader=supervised_val_loader,
221        unsupervised_val_loader=unsupervised_val_loader,
222        supervised_loss=loss,
223        supervised_loss_and_metric=loss_and_metric,
224        logger=self_training.SelfTrainingTensorboardLogger,
225        # On the CPU, mixed precision uses bfloat16. It is very slow without hardware support,
226        # and the kornia augmentations do not accept it.
227        mixed_precision=device.type == "cuda",
228        log_image_interval=100,
229        compile_model=False,
230        device=device,
231        reinit_teacher=reinit_teacher,
232        save_root=save_root,
233        augmenter=augmenters,
234        separate_backward=separate_backward,
235    )
236    trainer.fit(n_iterations)

Run domain adaptation to transfer a network trained on a source domain for a supervised segmentation task to perform this task on a different target domain.

We support different domain adaptation settings:

  • unsupervised domain adaptation: the default mode when 'supervised_train_paths' and 'supervised_val_paths' are not given.
  • semi-supervised domain adaptation: domain adaptation on unlabeled and labeled data, when 'supervised_train_paths' and 'supervised_val_paths' are given.
Arguments:
  • name: The name for the checkpoint to be trained.
  • unsupervised_train_paths: Filepaths to the hdf5 files or similar file formats for the training data in the target domain. This training data is used for unsupervised learning, so it does not require labels.
  • unsupervised_val_paths: Filepaths to the hdf5 files or similar file formats for the validation data in the target domain. This validation data is used for unsupervised learning, so it does not require labels.
  • patch_shape: The patch shape used for a training example. In order to run 2d training pass a patch shape with a singleton in the z-axis, e.g. 'patch_shape = [1, 512, 512]'.
  • save_root: Folder where the checkpoint will be saved.
  • source_checkpoint: Checkpoint to the initial model trained on the source domain. This is used to initialize the teacher model. If the checkpoint is not given, then both student and teacher model are initialized from scratch. In this case supervised_train_paths and supervised_val_paths have to be given in order to provide training data from the source domain.
  • supervised_train_paths: Filepaths to the hdf5 files for the training data in the source domain. This training data is optional. If given, it is used for supervised learning and requires labels.
  • supervised_val_paths: Filepaths to the hdf5 files for the validation data in the source domain. This validation data is optional. If given, it is used for supervised learning and requires labels.
  • confidence_threshold: The threshold for filtering data in the unsupervised loss. The label filtering is done based on the uncertainty of network predictions, and only the data with higher certainty than this threshold is used for training.
  • raw_key: The key that holds the raw data inside of the hdf5 or similar files.
  • raw_key_supervised: The key that holds the raw data inside of the hdf5 files for supervised learning.
  • label_key: The key that holds the labels inside of the hdf5 files for supervised learning. This is only required if supervised_train_paths and supervised_val_paths are given.
  • batch_size: The batch size for training.
  • lr: The initial learning rate.
  • n_iterations: The number of iterations to train for.
  • n_samples_train: The number of train samples per epoch. By default this will be estimated based on the patch_shape and size of the volumes used for training.
  • n_samples_val: The number of val samples per epoch. By default this will be estimated based on the patch_shape and size of the volumes used for validation.
  • train_mask_paths: Sample masks used by the unsupervised sampler to accept or reject patches for training.
  • val_mask_paths: Sample masks used by the unsupervised sampler to accept or reject patches for validation.
  • sample_mask_key: The key to the sample mask dataset inside each file.
  • unsupervised_sampler: Sampler to accept or reject patches for the unsupervised data stream.
  • supervised_sampler: Sampler to accept or reject patches for the supervised data stream. Pass False to disable.
  • backbone: The pretrained encoder. Options: "sam", "sam2", "dinov2", "dinov3" (ViT encoders of a UNETR), or "cryosiam" (3D CNN encoder of a CryoZooUNet). The ViT backbones must be set together with model_type.
  • model_type: Model type for the selected ViT backbone model family, for example "vit_b" or "vit_b_em_organelles" for "sam".
  • encoder_checkpoint: Path to the pretrained encoder weights for backbone. By default they are fetched for backbone and model_type.
  • aug_dict: The augmentations for the teacher and the student, with keys "intensity" and "geometrical". Defaults to Gaussian blur and noise, plus random flips and 90-degree rotations. The noise is scaled with clip_max to match the range of raw_transform.
  • separate_backward: Whether to backpropagate each loss term separately to reduce peak memory.
  • check: Whether to check the training and validation loaders instead of running training.