Skip to content

dataset

birdnet_stm32.data.dataset

Dataset loading, file path discovery, and class balancing utilities.

Functions for walking a class-structured audio directory, collecting file paths, and performing minority-class upsampling.

load_classes_file(path)

Read an explicitly ordered, one-label-per-line class schema.

Source code in birdnet_stm32/data/dataset.py
def load_classes_file(path: str) -> list[str]:
    """Read an explicitly ordered, one-label-per-line class schema."""
    with open(path, encoding="utf-8") as handle:
        classes = [line.strip() for line in handle if line.strip() and not line.lstrip().startswith("#")]
    if not classes:
        raise ValueError(f"Classes file is empty: {path}")
    if len(classes) != len(set(classes)):
        raise ValueError(f"Classes file contains duplicate labels: {path}")
    if any(label.lower() in NOISE_CLASSES for label in classes):
        raise ValueError("Noise-like folders produce all-zero targets and must not appear in the output class list")
    return classes

get_classes_with_most_samples(directory, n_classes=25, include_noise=False, exts=SUPPORTED_AUDIO_EXTS)

Collect the most frequent class labels from a dataset root.

Parameters:

Name Type Description Default
directory str

Root dataset directory (class-subfolders).

required
n_classes int

Number of top classes to return (upper bound).

25
include_noise bool

If False, exclude noise-like labels.

False
exts tuple

Accepted audio file extensions (case-insensitive).

SUPPORTED_AUDIO_EXTS

Returns:

Type Description
list[str]

Up to n_classes class names, sorted by descending sample count.

Source code in birdnet_stm32/data/dataset.py
def get_classes_with_most_samples(
    directory: str,
    n_classes: int = 25,
    include_noise: bool = False,
    exts: tuple = SUPPORTED_AUDIO_EXTS,
) -> list[str]:
    """Collect the most frequent class labels from a dataset root.

    Args:
        directory: Root dataset directory (class-subfolders).
        n_classes: Number of top classes to return (upper bound).
        include_noise: If False, exclude noise-like labels.
        exts: Accepted audio file extensions (case-insensitive).

    Returns:
        Up to n_classes class names, sorted by descending sample count.
    """
    classes: dict[str, int] = {}
    for root, _dirs, files in os.walk(directory):
        for fname in files:
            if not fname.lower().endswith(exts):
                continue
            class_name = os.path.basename(root)
            if not include_noise and class_name.lower() in NOISE_CLASSES:
                continue
            classes[class_name] = classes.get(class_name, 0) + 1

    sorted_classes = sorted(classes.items(), key=lambda x: x[1], reverse=True)
    return [cls for cls, _ in sorted_classes[:n_classes]]

load_file_paths_from_directory(directory, classes=None, max_samples=None, exts=SUPPORTED_AUDIO_EXTS)

Recursively gather audio files from a class-structured directory.

Expected layout::

root/
  class_a/*.(wav|mp3|flac|ogg|m4a)
  class_b/*.(wav|mp3|flac|ogg|m4a)

Parameters:

Name Type Description Default
directory str

Dataset root directory.

required
classes list[str] | None

If given, restrict to these class names only.

None
max_samples int | None

Cap the number of files per class (uniform random).

None
exts tuple

Accepted audio file extensions (case-insensitive).

SUPPORTED_AUDIO_EXTS

Returns:

Type Description
list[str]

Tuple of (shuffled file paths, sorted class names). Noise-like names

list[str]

are excluded from the class list but their files are still included.

Source code in birdnet_stm32/data/dataset.py
def load_file_paths_from_directory(
    directory: str,
    classes: list[str] | None = None,
    max_samples: int | None = None,
    exts: tuple = SUPPORTED_AUDIO_EXTS,
) -> tuple[list[str], list[str]]:
    """Recursively gather audio files from a class-structured directory.

    Expected layout::

        root/
          class_a/*.(wav|mp3|flac|ogg|m4a)
          class_b/*.(wav|mp3|flac|ogg|m4a)

    Args:
        directory: Dataset root directory.
        classes: If given, restrict to these class names only.
        max_samples: Cap the number of files per class (uniform random).
        exts: Accepted audio file extensions (case-insensitive).

    Returns:
        Tuple of (shuffled file paths, sorted class names). Noise-like names
        are excluded from the class list but their files are still included.
    """
    per_class: dict[str, list[str]] = {}
    wanted = None if classes is None else set(classes)
    # Remote URLs keep gfile. A local tree walks with _walk_local, which does not stat files: training
    # trees hold millions of symlinks, and following each to ask "directory?" took ~8 min on 2.96 M files.
    if "://" in str(directory):
        walk, join = tf.io.gfile.walk, tf.io.gfile.join
    else:
        walk, join = _walk_local, os.path.join

    for root, _, files in walk(directory):
        parent_class = os.path.basename(os.path.normpath(root))
        if wanted is not None and parent_class not in wanted and parent_class.lower() not in NOISE_CLASSES:
            continue
        for fname in files:
            if fname.lower().endswith(exts):
                per_class.setdefault(parent_class, []).append(join(root, fname))

    all_paths: list[str] = []
    for _cls, paths in per_class.items():
        if max_samples is not None and max_samples > 0 and len(paths) > max_samples:
            idx = np.random.permutation(len(paths))[:max_samples]
            paths = [paths[i] for i in idx]
        all_paths.extend(paths)

    np.random.shuffle(all_paths)

    if classes is None:
        classes_out = sorted(c for c in per_class if c.lower() not in NOISE_CLASSES)
    else:
        classes_out = [class_name for class_name in classes if class_name in per_class]

    return all_paths, classes_out