Skip to content

datasets

AnomalyDataset(transform, samples, task='segmentation', valid_area_mask=None, crop_area=None)

Bases: Dataset

Anomaly Dataset.

Parameters:

  • transform (Compose) –

    Albumentations compose.

  • task (str, default: 'segmentation' ) –

    classification or segmentation

  • samples (DataFrame) –

    Pandas dataframe containing samples following the same structure created by make_anomaly_dataset

  • valid_area_mask (str | None, default: None ) –

    Optional path to the mask to use to filter out the valid area of the image. If None, the whole image is considered valid.

  • crop_area (tuple[int, int, int, int] | None, default: None ) –

    Optional tuple of 4 integers (x1, y1, x2, y2) to crop the image to the specified area. If None, the whole image is considered valid.

Source code in quadra/datasets/anomaly.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def __init__(
    self,
    transform: alb.Compose,
    samples: DataFrame,
    task: str = "segmentation",
    valid_area_mask: str | None = None,
    crop_area: tuple[int, int, int, int] | None = None,
) -> None:
    self.task = task
    self.transform = transform

    self.samples = samples
    self.samples = self.samples.reset_index(drop=True)
    self.split = self.samples.split.unique()[0]

    self.crop_area = crop_area
    self.valid_area_mask: np.ndarray | None = None

    if valid_area_mask is not None:
        if not os.path.exists(valid_area_mask):
            raise RuntimeError(f"Valid area mask {valid_area_mask} does not exist.")

        self.valid_area_mask = cv2.imread(valid_area_mask, 0) > 0  # type: ignore[operator]

__getitem__(index)

Get dataset item for the index index.

Parameters:

  • index (int) –

    Index to get the item.

Returns:

  • dict[str, str | Tensor]

    Dict of image tensor during training.

  • dict[str, str | Tensor]

    Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box.

Source code in quadra/datasets/anomaly.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
def __getitem__(self, index: int) -> dict[str, str | Tensor]:
    """Get dataset item for the index ``index``.

    Args:
        index: Index to get the item.

    Returns:
        Dict of image tensor during training.
        Otherwise, Dict containing image path, target path, image tensor, label and transformed bounding box.
    """
    item: dict[str, str | Tensor] = {}

    image_path = self.samples.samples.iloc[index]
    image = cv2.imread(image_path)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    original_image_shape = image.shape
    if self.valid_area_mask is not None:
        image = image * self.valid_area_mask[:, :, np.newaxis]

    if self.crop_area is not None:
        image = image[self.crop_area[1] : self.crop_area[3], self.crop_area[0] : self.crop_area[2]]

    label_index = self.samples.label_index[index]

    if self.split == "train":
        pre_processed = self.transform(image=image)
        item = {"image": pre_processed["image"], "label": label_index}
    elif self.split in ["val", "test"]:
        item["image_path"] = image_path
        item["label"] = label_index

        if self.task == "segmentation":
            mask_path = self.samples.mask_path[index]

            # If good images have no associated mask create an empty one
            if label_index == 0:
                mask = np.zeros(shape=original_image_shape[:2])
            elif os.path.isfile(mask_path):
                mask = cv2.imread(mask_path, flags=0) / 255.0  # type: ignore[operator]
            else:
                # We need ones in the mask to compute correctly at least image level f1 score
                mask = np.ones(shape=original_image_shape[:2])

            if self.valid_area_mask is not None:
                mask = mask * self.valid_area_mask

            if self.crop_area is not None:
                mask = mask[self.crop_area[1] : self.crop_area[3], self.crop_area[0] : self.crop_area[2]]

            pre_processed = self.transform(image=image, mask=mask)

            item["mask_path"] = mask_path
            item["mask"] = pre_processed["mask"]
        else:
            pre_processed = self.transform(image=image)

        item["image"] = pre_processed["image"]
    return item

__len__()

Get length of the dataset.

Source code in quadra/datasets/anomaly.py
225
226
227
def __len__(self) -> int:
    """Get length of the dataset."""
    return len(self.samples)

ClassificationDataset(samples, targets, class_to_idx=None, resize=None, roi=None, transform=None, rgb=True, channel=3, random_padding=False, circular_crop=False)

Bases: ImageClassificationListDataset

Custom Classification Dataset.

Parameters:

  • samples (list[str]) –

    List of paths to images

  • targets (list[str | int]) –

    List of targets

  • class_to_idx (dict | None, default: None ) –

    Defaults to None.

  • resize (int | None, default: None ) –

    Resize image to this size. Defaults to None.

  • roi (tuple[int, int, int, int] | None, default: None ) –

    Region of interest. Defaults to None.

  • transform (Callable | None, default: None ) –

    transform function. Defaults to None.

  • rgb (bool, default: True ) –

    Use RGB space

  • channel (int, default: 3 ) –

    Number of channels. Defaults to 3.

  • random_padding (bool, default: False ) –

    Random padding. Defaults to False.

  • circular_crop (bool, default: False ) –

    Circular crop. Defaults to False.

Source code in quadra/datasets/classification.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def __init__(
    self,
    samples: list[str],
    targets: list[str | int],
    class_to_idx: dict | None = None,
    resize: int | None = None,
    roi: tuple[int, int, int, int] | None = None,
    transform: Callable | None = None,
    rgb: bool = True,
    channel: int = 3,
    random_padding: bool = False,
    circular_crop: bool = False,
):
    super().__init__(samples, targets, class_to_idx, resize, roi, transform, rgb, channel)
    if transform is None:
        self.transform = None

    self.random_padding = random_padding
    self.circular_crop = circular_crop

ImageClassificationListDataset(samples, targets, class_to_idx=None, resize=None, roi=None, transform=None, rgb=True, channel=3, allow_missing_label=False)

Bases: Dataset

Standard classification dataset.

Parameters:

  • samples (list[str]) –

    List of paths to images to be read

  • targets (list[str | int]) –

    List of labels, one for every image in samples

  • class_to_idx (dict | None, default: None ) –

    mapping from classes to unique indexes. Defaults to None.

  • resize (int | None, default: None ) –

    Integer specifying the size of a first optional resize keeping the aspect ratio: the smaller side of the image will be resized to resize, while the longer will be resized keeping the aspect ratio. Defaults to None.

  • roi (tuple[int, int, int, int] | None, default: None ) –

    Optional ROI, with (x_upper_left, y_upper_left, x_bottom_right, y_bottom_right). Defaults to None.

  • transform (Callable | None, default: None ) –

    Optional Albumentations transform. Defaults to None.

  • rgb (bool, default: True ) –

    if False, image will be converted in grayscale

  • channel (int, default: 3 ) –

    1 or 3. If rgb is True, then channel will be set at 3.

  • allow_missing_label (bool | None, default: False ) –

    If set to false warn the user if the dataset contains missing labels

Source code in quadra/datasets/classification.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def __init__(
    self,
    samples: list[str],
    targets: list[str | int],
    class_to_idx: dict | None = None,
    resize: int | None = None,
    roi: tuple[int, int, int, int] | None = None,
    transform: Callable | None = None,
    rgb: bool = True,
    channel: int = 3,
    allow_missing_label: bool | None = False,
):
    super().__init__()
    assert len(samples) == len(
        targets
    ), f"Samples ({len(samples)}) and targets ({len(targets)}) must have the same length"
    # Setting the ROI
    self.roi = roi

    # Keep-Aspect-Ratio resize
    self.resize = resize

    if not allow_missing_label and None in targets:
        warnings.warn(
            (
                "Dataset contains empty targets but allow_missing_label is set to False, "
                "be careful because None labels will not work inside Dataloaders"
            ),
            UserWarning,
            stacklevel=2,
        )

    targets = [-1 if target is None else target for target in targets]
    # Data
    self.x = np.array(samples)
    self.y = np.array(targets)

    if class_to_idx is None:
        unique_targets = np.unique(targets)
        class_to_idx = {c: i for i, c in enumerate(unique_targets)}

    self.class_to_idx = class_to_idx
    self.idx_to_class = {v: k for k, v in class_to_idx.items()}
    self.samples = [
        (path, self.class_to_idx[self.y[i]] if (self.y[i] != -1 and self.y[i] != "-1") else -1)
        for i, path in enumerate(self.x)
    ]

    self.rgb = rgb
    self.channel = 3 if rgb else channel

    self.transform = transform

MultilabelClassificationDataset(samples, targets, class_to_idx=None, transform=None, rgb=True)

Bases: Dataset

Custom MultilabelClassification Dataset.

Parameters:

  • samples (list[str]) –

    list of paths to images.

  • targets (ndarray) –

    array of multiple targets per sample. The array must be a one-hot enoding. It must have a shape of (n_samples, n_targets).

  • class_to_idx (dict | None, default: None ) –

    Defaults to None.

  • transform (Callable | None, default: None ) –

    transform function. Defaults to None.

  • rgb (bool, default: True ) –

    Use RGB space

Source code in quadra/datasets/classification.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def __init__(
    self,
    samples: list[str],
    targets: np.ndarray,
    class_to_idx: dict | None = None,
    transform: Callable | None = None,
    rgb: bool = True,
):
    super().__init__()
    assert len(samples) == len(
        targets
    ), f"Samples ({len(samples)}) and targets ({len(targets)}) must have the same length"

    # Data
    self.x = samples
    self.y = targets

    # Class to idx and the other way around
    if class_to_idx is None:
        unique_targets = targets.shape[1]
        class_to_idx = {c: i for i, c in enumerate(range(unique_targets))}
    self.class_to_idx = class_to_idx
    self.idx_to_class = {v: k for k, v in class_to_idx.items()}
    self.samples = list(zip(self.x, self.y))
    self.rgb = rgb
    self.transform = transform

PatchSklearnClassificationTrainDataset(data_path, samples, targets, class_to_idx=None, resize=None, transform=None, rgb=True, channel=3, balance_classes=False)

Bases: Dataset

Dataset used for patch sampling, it expects samples to be paths to h5 files containing all the required information for patch sampling from images.

Parameters:

  • data_path (str) –

    base path to the dataset

  • samples (list[str]) –

    Paths to h5 files

  • targets (list[str | int]) –

    Labels associated with each sample

  • class_to_idx (dict | None, default: None ) –

    Mapping between class and corresponding index

  • resize (int | None, default: None ) –

    Whether to perform an aspect ratio resize of the patch before the transformations

  • transform (Callable | None, default: None ) –

    Optional function applied to the image

  • rgb (bool, default: True ) –

    if False, image will be converted in grayscale

  • channel (int, default: 3 ) –

    1 or 3. If rgb is True, then channel will be set at 3.

  • balance_classes (bool, default: False ) –

    if True, the dataset will be balanced by duplicating samples of the minority class

Source code in quadra/datasets/patch.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def __init__(
    self,
    data_path: str,
    samples: list[str],
    targets: list[str | int],
    class_to_idx: dict | None = None,
    resize: int | None = None,
    transform: Callable | None = None,
    rgb: bool = True,
    channel: int = 3,
    balance_classes: bool = False,
):
    super().__init__()

    # Keep-Aspect-Ratio resize
    self.resize = resize
    self.data_path = data_path

    if balance_classes:
        samples_array = np.array(samples)
        targets_array = np.array(targets)
        samples_to_use: list[str] = []
        targets_to_use: list[str | int] = []

        cls, counts = np.unique(targets_array, return_counts=True)
        max_count = np.max(counts)
        for cl, count in zip(cls, counts):
            idx_to_pick = list(np.where(targets_array == cl)[0])

            if count < max_count:
                idx_to_pick += random.choices(idx_to_pick, k=max_count - count)

            samples_to_use.extend(samples_array[idx_to_pick])
            targets_to_use.extend(targets_array[idx_to_pick])
    else:
        samples_to_use = samples
        targets_to_use = targets

    # Data
    self.x = np.array(samples_to_use)
    self.y = np.array(targets_to_use)

    if class_to_idx is None:
        unique_targets = np.unique(targets_to_use)
        class_to_idx = {c: i for i, c in enumerate(unique_targets)}

    self.class_to_idx = class_to_idx
    self.idx_to_class = {v: k for k, v in class_to_idx.items()}

    self.samples = [
        (path, self.class_to_idx[self.y[i]] if self.y[i] is not None else None) for i, path in enumerate(self.x)
    ]

    self.rgb = rgb
    self.channel = 3 if rgb else channel

    self.transform = transform

SegmentationDataset(image_paths, mask_paths, batch_size=None, object_masks=None, resize=224, mask_preprocess=None, labels=None, transform=None, mask_smoothing=False, defect_transform=None)

Bases: Dataset

Custom SegmentationDataset class for loading images and masks.

Parameters:

  • image_paths (list[str]) –

    List of paths to images.

  • mask_paths (list[str]) –

    List of paths to masks.

  • batch_size (int | None, default: None ) –

    Batch size.

  • object_masks (list[ndarray | Any] | None, default: None ) –

    List of paths to object masks.

  • resize (int, default: 224 ) –

    Resize image to this size.

  • mask_preprocess (Callable | None, default: None ) –

    Preprocess mask.

  • labels (list[str] | None, default: None ) –

    List of labels.

  • transform (Compose | None, default: None ) –

    Transformations to apply to images and masks.

  • mask_smoothing (bool, default: False ) –

    Smooth mask.

  • defect_transform (Compose | None, default: None ) –

    Transformations to apply to images and masks for defects.

Source code in quadra/datasets/segmentation.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def __init__(
    self,
    image_paths: list[str],
    mask_paths: list[str],
    batch_size: int | None = None,
    object_masks: list[np.ndarray | Any] | None = None,
    resize: int = 224,
    mask_preprocess: Callable | None = None,
    labels: list[str] | None = None,
    transform: albumentations.Compose | None = None,
    mask_smoothing: bool = False,
    defect_transform: albumentations.Compose | None = None,
):
    self.transform = transform
    self.defect_transform = defect_transform
    self.image_paths = image_paths
    self.mask_paths = mask_paths
    self.labels = labels
    self.mask_preprocess = mask_preprocess
    self.resize = resize
    self.object_masks = object_masks
    self.data_len = len(self.image_paths)
    self.batch_size = None if batch_size is None else max(batch_size, self.data_len)
    self.smooth_mask = mask_smoothing

SegmentationDatasetMulticlass(image_paths, mask_paths, idx_to_class, batch_size=None, transform=None, one_hot=False)

Bases: Dataset

Custom SegmentationDataset class for loading images and multilabel masks.

Parameters:

  • image_paths (list[str]) –

    List of paths to images.

  • mask_paths (list[str]) –

    List of paths to masks.

  • idx_to_class (dict) –

    dict with corrispondence btw mask index and classes: {1: class_1, 2: class_2, ..., N: class_N}

  • batch_size (int | None, default: None ) –

    Batch size.

  • transform (Compose | None, default: None ) –

    Transformations to apply to images and masks.

  • one_hot (bool, default: False ) –

    if True return a binary mask (n_classxHxW), otherwise the labelled mask HxW. SMP loss requires the second format.

Source code in quadra/datasets/segmentation.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def __init__(
    self,
    image_paths: list[str],
    mask_paths: list[str],
    idx_to_class: dict,
    batch_size: int | None = None,
    transform: albumentations.Compose | None = None,
    one_hot: bool = False,
):
    self.transform = transform
    self.image_paths = image_paths
    self.mask_paths = mask_paths
    self.idx_to_class = idx_to_class
    self.data_len = len(self.image_paths)
    self.batch_size = None if batch_size is None else max(batch_size, self.data_len)
    self.one_hot = one_hot

__getitem__(index)

Get image and mask.

Source code in quadra/datasets/segmentation.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def __getitem__(self, index):
    """Get image and mask."""
    # This is required to avoid infinite loop when running the dataset outside of a dataloader
    if self.batch_size is not None and self.batch_size == index:
        raise StopIteration
    if self.batch_size is None and self.data_len == index:
        raise StopIteration

    index = index % self.data_len
    image_path = self.image_paths[index]

    image = cv2.imread(str(image_path))
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    if (
        self.mask_paths[index] is np.nan
        or self.mask_paths[index] is None
        or not os.path.isfile(self.mask_paths[index])
    ):
        mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8)
    else:
        mask_path = self.mask_paths[index]
        mask = cv2.imread(str(mask_path), 0)

    # we go back to binary masks avoid transformation errors
    mask = self._preprocess_mask(mask)

    if self.transform is not None:
        masks = list(mask)
        aug = self.transform(image=image, masks=masks)
        image = aug["image"]
        mask = np.stack(aug["masks"])  # C x H x W

    # we compute single channel mask again
    # zero is the background
    if not self.one_hot:  # one hot is done by smp dice loss
        mask_out = np.zeros(mask.shape[1:])
        for i in range(1, mask.shape[0]):
            mask_out[mask[i] == 1] = i
        # mask_out shape -> HxW
    else:
        mask_out = mask
        # mask_out shape -> CxHxW where C is number of classes (included the background)

    return image, mask_out.astype(int), 0

__len__()

Returns the dataset lenght.

Source code in quadra/datasets/segmentation.py
234
235
236
237
238
239
def __len__(self):
    """Returns the dataset lenght."""
    if self.batch_size is None:
        return self.data_len

    return max(self.data_len, self.batch_size)

TwoAugmentationDataset(dataset, transform, strategy=AugmentationStrategy.SAME_IMAGE)

Bases: Dataset

Two Image Augmentation Dataset for using in self-supervised learning.

Parameters:

  • dataset (Dataset) –

    A torch Dataset object

  • transform (Compose | tuple[Compose, Compose]) –

    albumentation transformations for each image. If you use single transformation, it will be applied to both images. If you use tuple, it will be applied to first image and second image separately.

  • strategy (AugmentationStrategy, default: SAME_IMAGE ) –

    Defaults to AugmentationStrategy.SAME_IMAGE.

Source code in quadra/datasets/ssl.py
30
31
32
33
34
35
36
37
38
39
40
def __init__(
    self,
    dataset: Dataset,
    transform: A.Compose | tuple[A.Compose, A.Compose],
    strategy: AugmentationStrategy = AugmentationStrategy.SAME_IMAGE,
):
    self.dataset = dataset
    self.transform = transform
    self.stategy = strategy
    if isinstance(transform, Iterable) and not isinstance(transform, str) and len(set(transform)) != 2:
        raise ValueError("transform must be an Iterable of length 2")

TwoSetAugmentationDataset(dataset, global_transforms, local_transform, num_local_transforms)

Bases: Dataset

Two Set Augmentation Dataset for using in self-supervised learning (DINO).

Parameters:

  • dataset (Dataset) –

    Base dataset

  • global_transforms (tuple[Compose, Compose]) –

    Global transformations for each image.

  • local_transform (Compose) –

    Local transformations for each image.

  • num_local_transforms (int) –

    Number of local transformations to apply. In total you will have two + num_local_transforms transformations for each image. First element of the array will always return the original image.

Example

images[0] = global_transform0 images[1] = global_transform1 images[2:] = local_transform(s)(original_image)

Source code in quadra/datasets/ssl.py
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def __init__(
    self,
    dataset: Dataset,
    global_transforms: tuple[A.Compose, A.Compose],
    local_transform: A.Compose,
    num_local_transforms: int,
):
    self.dataset = dataset
    self.global_transforms = global_transforms
    self.local_transform = local_transform
    self.num_local_transforms = num_local_transforms

    if num_local_transforms < 1:
        raise ValueError("num_local_transforms must be greater than 0")