1717from .sampler import UniBatchSampler , _build_group_map
1818from ..transforms .raw import (
1919 _identity , _cellpose_raw_trafo , _to_8bit , _normalize_percentile , _resize_raw_to_512 , _resize_to_512 ,
20+ get_random_percentile_normalization ,
2021)
2122from ..transforms .labels import (
2223 _em_cell_label_trafo , _joint_em_cell_label_trafo ,
2324 _plantseg_label_trafo , _axondeepseg_pre_label_transform , _instance_labels ,
2425 _JointLabelTransform ,
2526)
2627
27-
2828# Cap on validation samples drawn per dataset, to keep the per-epoch validation pass cheap.
2929# Each access is a random crop (see UniDataWrapper.max_samples), so this is N random samples.
3030N_SAMPLES_VAL = 50
3434# Sam2Trainer._validate_impl, so the validation metric is comparable across epochs.
3535VALIDATION_SEED = 42
3636
37+ # Train with uniformly sampled symmetric percentiles; validate deterministically with 2nd/98th percentiles
38+ # to match the inference-time normalization in normalize_raw.
39+ TRAIN_LOWER_PERCENTILE_BOUNDS = (0.0 , 5.0 )
40+ VALIDATION_LOWER_PERCENTILE_BOUNDS = (2.0 , 2.0 )
41+
3742
3843def seed_worker (worker_id ):
3944 """DataLoader worker_init_fn that pins per-worker RNG for deterministic validation crops.
@@ -54,6 +59,41 @@ def _ensure_native_byte_order(y):
5459 return y .byteswap ().view (y .dtype .newbyteorder ()) if not y .dtype .isnative else y
5560
5661
62+ def _set_percentile_normalization (dataset , lower_percentile_bounds ):
63+ """Replace fixed normalization in all torch-em leaves of a dataset tree."""
64+ if isinstance (dataset , (list , tuple )):
65+ for ds in dataset :
66+ _set_percentile_normalization (ds , lower_percentile_bounds )
67+ return
68+
69+ if isinstance (dataset , UniDataWrapper ):
70+ _set_percentile_normalization (dataset .ds , lower_percentile_bounds )
71+ return
72+
73+ children = getattr (dataset , "datasets" , None )
74+ if children is not None :
75+ for ds in children :
76+ _set_percentile_normalization (ds , lower_percentile_bounds )
77+ return
78+
79+ if not hasattr (dataset , "raw_transform" ):
80+ raise TypeError (f"Cannot configure raw normalization for dataset of type { type (dataset ).__name__ } ." )
81+
82+ dataset .raw_transform = get_random_percentile_normalization (
83+ dataset .raw_transform , lower_percentile_bounds = lower_percentile_bounds
84+ )
85+
86+
87+ def _configure_training_normalization (train_datasets , val_datasets ):
88+ """Enable random percentile augmentation for training and deterministic 2nd/98th validation."""
89+ _set_percentile_normalization (
90+ train_datasets , lower_percentile_bounds = TRAIN_LOWER_PERCENTILE_BOUNDS ,
91+ )
92+ _set_percentile_normalization (
93+ val_datasets , lower_percentile_bounds = VALIDATION_LOWER_PERCENTILE_BOUNDS ,
94+ )
95+
96+
5797def _prepare_data_loader (dataset , batch_size , shuffle , batch_size_per_group = None , num_workers = 32 , deterministic = False ):
5898 # For deterministic validation, re-seed workers every epoch via worker_init_fn.
5999 # This requires non-persistent workers, since persistent workers run worker_init_fn only once.
@@ -163,7 +203,7 @@ def _get_embedseg_datasets(split_choice, z):
163203 "Mouse-Organoid-Cells-CBG" , "Mouse-Skull-Nuclei-CBG" ,
164204 "Platynereis-ISH-Nuclei-CBG" , "Platynereis-Nuclei-CBG" ,
165205 ]
166- else : # Only two datasets have the test split.
206+ else : # Only two datasets have the test split.
167207 names = ["Mouse-Skull-Nuclei-CBG" , "Platynereis-ISH-Nuclei-CBG" ]
168208
169209 all_embedseg_datasets = [
@@ -397,8 +437,8 @@ def _get_em_datasets(input_path, patch_shape, z_slices, kwargs, label_trafo, _em
397437 """Get all electron microscopy (EM) datasets for generalist training.
398438
399439 Args:
400- _em_label_trafo: EM cell label transform function to use. Defaults to
401- :func:`_em_cell_label_trafo`. Pass :func:`_joint_em_cell_label_trafo`
440+ _em_label_trafo: EM cell label transform function to use. Defaults to
441+ :func:`_em_cell_label_trafo`. Pass :func:`_joint_em_cell_label_trafo`
402442 when building joint interactive+automatic datasets.
403443
404444 Returns:
@@ -705,6 +745,8 @@ def get_dataloaders(
705745 train_ds .extend (em_train )
706746 val_ds .extend (em_val )
707747
748+ _configure_training_normalization (train_ds , val_ds )
749+
708750 # Finally, we prepare a 'ConcatDataset' for all the available datasets.
709751 train_ds = ConcatDataset (* train_ds )
710752 val_ds = ConcatDataset (* val_ds )
@@ -739,7 +781,7 @@ def get_interactive_dataloaders(
739781
740782 Identical dataset composition to :func:`get_dataloaders` but returns raw
741783 integer instance labels (``label_dtype=torch.int64``) instead of distance
742- transforms. Used with :class:`micro_sam.v2.training.ConvertToSam2VideoBatch`.
784+ transforms. Used with :class:`micro_sam.v2.training.ConvertToSam2VideoBatch`.
743785
744786 Args:
745787 input_path: Root path to the generalist training data.
@@ -815,6 +857,7 @@ def _build_automatic_datasets(input_path, z_slices, dataset_choice):
815857 train_ds .extend (em_train )
816858 val_ds .extend (em_val )
817859
860+ _configure_training_normalization (train_ds , val_ds )
818861 return ConcatDataset (* train_ds ), ConcatDataset (* val_ds )
819862
820863
@@ -849,6 +892,8 @@ def _build_interactive_datasets(input_path, z_slices, dataset_choice):
849892 train_ds .extend (em_train )
850893 val_ds .extend (em_val )
851894
895+ _configure_training_normalization (train_ds , val_ds )
896+
852897 # Cap each validation dataset to N_SAMPLES_VAL random samples so the per-epoch
853898 # validation pass stays cheap (train datasets are left at full size).
854899 for w in val_ds :
@@ -897,6 +942,8 @@ def _build_joint_datasets(input_path, z_slices, dataset_choice):
897942 train_ds .extend (em_train )
898943 val_ds .extend (em_val )
899944
945+ _configure_training_normalization (train_ds , val_ds )
946+
900947 # Cap each validation dataset to N_SAMPLES_VAL random samples so the per-epoch
901948 # validation pass stays cheap (matches the interactive builder; train datasets are full size).
902949 for w in val_ds :
0 commit comments