split_dataset
split_dataset(
root,
out_dir,
*,
mode=DataMode.NESTED_DIRS,
subdirs=('images', 'masks'),
column_sep=';',
fractions=(0.7, 0.15, 0.15),
group_by=None,
seed=0,
strict=True,
relative=True,
label_column='masks',
)Split one folder of data into three CSVs a specification can point at.
The whole point of the function: a researcher has one directory and needs train_path, validation_path and test_path. Returns the paths it wrote plus the counts, so the caller can see what happened without opening the files.
label_column names the second column, and it has to match what the specification will look for - masks for segmentation, annotations for detection. Not derived from subdirs, although that would read well: someone whose directories are called img and lbl has always got a masks column out of this, and deriving it would quietly write a file their existing configuration could no longer read.
The other way to divide one folder is the specification’s own split block, which partitions the samples in memory and writes nothing. Use this one when the CSVs are the point - to keep, to hand to a colleague, or to cite in a paper.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| root | str | Path | The folder holding the data. | required |
| out_dir | str | Path | Where the three CSVs go. | required |
| mode | DataMode | str | nested_dirs for one directory per sample, config_file for a CSV. |
DataMode.NESTED_DIRS |
| subdirs | tuple[str, str] | For nested_dirs, the image and label subdirectories. |
('images', 'masks') |
| column_sep | str | What separates several paths inside one cell - a sample with one mask file per object, or one file per channel. Not the CSV’s own delimiter, which is a comma: that is the distinction this argument’s name does not make. | ';' |
| fractions | Sequence[float] | Mapping[str, float] | Two or three, as for split_samples. |
(0.7, 0.15, 0.15) |
| group_by | str | None | A pattern picking the group out of each sample’s key - a patient, usually. As for split_samples, and for the same reason. |
None |
| seed | int | Which shuffle. | 0 |
| strict | bool | Whether a sample that cannot be read is an error rather than a skip. | True |
| relative | bool | Write paths relative to out_dir, so the CSVs survive being moved together with the data. |
True |
| label_column | str | The name of the second column. | 'masks' |
Returns
| Name | Type | Description |
|---|---|---|
| dict[str, object] | paths - the CSV written for each split that got any; samples and groups - |
|
| dict[str, object] | how many of each went where, test included and zero when there are two fractions; | |
| dict[str, object] | and skipped, which is not zero when strict is False and something could not be |
|
| dict[str, object] | read. |
import pathlib, tempfile root = pathlib.Path(tempfile.mkdtemp()) for patient in range(1, 5): … for slice_no in range(2): … case = root / f”patient{patient:02d}slice{slice_no}” … (case / “images”).mkdir(parents=True) … (case / “masks”).mkdir(parents=True) … = (case / “images” / “scan.png”).write_bytes(b”“) … _ = (case /”masks” / “mask.png”).write_bytes(b”“) out = split_dataset( … root, … root /”splits”, … fractions=(0.5, 0.5), … group_by=r”^(patient)_“, … ) sorted(out) [‘groups’, ‘paths’, ‘samples’, ‘skipped’] out[“samples”] {‘train’: 4, ‘validation’: 4, ‘test’: 0}
Two groups each side, because a patient is never divided:
out[“groups”] {‘train’: 2, ‘validation’: 2, ‘test’: 0}
And the CSVs are what a specification points at. Note the header: the file is comma-separated and carries the key and the group it was assigned, while column_sep separates several paths within a single cell.
written = pathlib.Path(out[“paths”][“train”]).read_text().splitlines() written[0] ‘key,group,images,masks’ len(written) - 1 4