def make_dataset(
    root: str | Path,
    split: str | Split | None = None,
    extensions: Sequence[str] = IMG_EXTENSIONS,
) -> DataFrame:
    """Create MVTec LOCO AD samples by parsing the original MVTec LOCO AD data file structure.

    The files are expected to follow the structure:
        path/to/dataset/split/category/image_filename.png
        path/to/dataset/ground_truth/category/image_filename/000.png

    where there can be multiple ground-truth masks for the corresponding anomalous images.

    This function creates a dataframe to store the parsed information based on the following format:

    +---+---------------+-------+---------+-------------------------+-----------------------------+-------------+
    |   | path          | split | label   | image_path              | mask_path                  | label_index |
    +===+===============+=======+=========+===============+=======================================+=============+
    | 0 | datasets/name | test  | defect  | path/to/image/file.png  | [path/to/masks/file.png]    | 1           |
    +---+---------------+-------+---------+-------------------------+-----------------------------+-------------+

    Args:
        root (str | Path): Path to dataset
        split (str | Split | None): Dataset split (ie., either train or test).
            Defaults to ``None``.
        extensions (Sequence[str]): List of file extensions to be included in the dataset.
            Defaults to ``None``.

    Returns:
        DataFrame: an output dataframe containing the samples of the dataset.

    Examples:
        The following example shows how to get test samples from MVTec LOCO AD pushpins category:

        >>> root = Path('./MVTec_LOCO')
        >>> category = 'pushpins'
        >>> path = root / category
        >>> samples = make_mvtec_loco_dataset(path, split='test')
        >>> samples.head()
           path                split label image_path           mask_path label_index
        0  datasets/MVTec_LOCO/pushpins test good  [...]/good/105.png           0
        1  datasets/MVTec_LOCO/pushpins test good  [...]/good/017.png           0

    Raises:
        RuntimeError: If no valid images are found
        MisMatchError: If anomalous images and masks don't match
    """
    root = validate_path(root)

    # Retrieve the image and mask files
    samples_list = []
    for f in root.glob("**/*"):
        if f.suffix in extensions:
            parts = f.parts
            # 'ground_truth' and non 'ground_truth' path have a different structure
            if "ground_truth" not in parts:
                split_folder, label_folder, image_file = parts[-3:]
                image_path = f"{root}/{split_folder}/{label_folder}/{image_file}"
                samples_list.append((str(root), split_folder, label_folder, "", image_path))
            else:
                split_folder, label_folder, image_folder, image_file = parts[-4:]
                image_path = f"{root}/{split_folder}/{label_folder}/{image_folder}/{image_file}"
                samples_list.append((str(root), split_folder, label_folder, image_folder, image_path))

    if not samples_list:
        msg = f"Found 0 images in {root}"
        raise RuntimeError(msg)

    samples = DataFrame(samples_list, columns=["path", "split", "label", "image_folder", "image_path"])

    # Replace validation to Split.VAL.value in the split column
    samples["split"] = samples["split"].replace("validation", Split.VAL.value)

    # Create label index for normal (0) and anomalous (1) images.
    samples.loc[(samples.label == "good"), "label_index"] = LabelName.NORMAL
    samples.loc[(samples.label != "good"), "label_index"] = LabelName.ABNORMAL
    samples.label_index = samples.label_index.astype(int)

    # separate ground-truth masks from samples
    mask_samples = samples.loc[samples.split == "ground_truth"].sort_values(by="image_path", ignore_index=True)
    samples = samples[samples.split != "ground_truth"].sort_values(by="image_path", ignore_index=True)

    # Group masks and aggregate the path into a list
    mask_samples = (
        mask_samples.groupby(["path", "split", "label", "image_folder"])["image_path"]
        .agg(list)
        .reset_index()
        .rename(columns={"image_path": "mask_path"})
    )

    # assign mask paths to anomalous test images
    samples["mask_path"] = ""
    samples.loc[
        (samples.split == "test") & (samples.label_index == LabelName.ABNORMAL),
        "mask_path",
    ] = mask_samples.mask_path.to_numpy()

    # validate that the right mask files are associated with the right test images
    if len(samples.loc[samples.label_index == LabelName.ABNORMAL]):
        image_stems = samples.loc[samples.label_index == LabelName.ABNORMAL]["image_path"].apply(lambda x: Path(x).stem)
        mask_parent_stems = samples.loc[samples.label_index == LabelName.ABNORMAL]["mask_path"].apply(
            lambda x: {Path(mask_path).parent.stem for mask_path in x},
        )

        if not all(
            next(iter(mask_stems)) == image_stem
            for image_stem, mask_stems in zip(image_stems, mask_parent_stems, strict=True)
        ):
            msg = (
                "Mismatch between anomalous images and ground truth masks. "
                "Make sure the parent folder of the mask files in 'ground_truth' folder "
                "follows the same naming convention as the anomalous images in the dataset "
                "(e.g., image: '005.png', mask: '005/000.png')."
            )
            raise MisMatchError(msg)

    # infer the task type
    samples.attrs["task"] = "classification" if (samples["mask_path"] == "").all() else "segmentation"

    if split:
        samples = samples[samples.split == split].reset_index(drop=True)

    return samples
Create MVTec LOCO AD samples by parsing the original MVTec LOCO AD data file structure.

    The files are expected to follow the structure:
        path/to/dataset/split/category/image_filename.png
        path/to/dataset/ground_truth/category/image_filename/000.png

    where there can be multiple ground-truth masks for the corresponding anomalous images.

    This function creates a dataframe to store the parsed information based on the following format:

    +---+---------------+-------+---------+-------------------------+-----------------------------+-------------+
    |   | path          | split | label   | image_path              | mask_path                  | label_index |
    +===+===============+=======+=========+===============+=======================================+=============+
    | 0 | datasets/name | test  | defect  | path/to/image/file.png  | [path/to/masks/file.png]    | 1           |
    +---+---------------+-------+---------+-------------------------+-----------------------------+-------------+

    Args:
        root (str | Path): Path to dataset
        split (str | Split | None): Dataset split (ie., either train or test).
            Defaults to ``None``.
        extensions (Sequence[str]): List of file extensions to be included in the dataset.
            Defaults to ``None``.

    Returns:
        DataFrame: an output dataframe containing the samples of the dataset.

    Examples:
        The following example shows how to get test samples from MVTec LOCO AD pushpins category:

        >>> root = Path('./MVTec_LOCO')
        >>> category = 'pushpins'
        >>> path = root / category
        >>> samples = make_mvtec_loco_dataset(path, split='test')
        >>> samples.head()
           path                split label image_path           mask_path label_index
        0  datasets/MVTec_LOCO/pushpins test good  [...]/good/105.png           0
        1  datasets/MVTec_LOCO/pushpins test good  [...]/good/017.png           0

    Raises:
        RuntimeError: If no valid images are found
        MisMatchError: If anomalous images and masks don't match
    
src/anomalib/data/datasets/image/mvtec_loco.py