mirror of
https://github.com/macaodha/batdetect2.git
synced 2026-04-04 15:20:19 +02:00
Better cli docs
This commit is contained in:
parent
7277151f33
commit
e4bbde9995
@ -41,6 +41,8 @@ html_theme = "sphinx_book_theme"
|
||||
html_static_path = ["_static"]
|
||||
html_theme_options = {
|
||||
"home_page_in_toc": True,
|
||||
"show_navbar_depth": 2,
|
||||
"show_toc_level": 2,
|
||||
}
|
||||
|
||||
intersphinx_mapping = {
|
||||
@ -58,7 +60,7 @@ intersphinx_mapping = {
|
||||
}
|
||||
|
||||
# -- Options for autodoc ------------------------------------------------------
|
||||
autosummary_generate = True
|
||||
autosummary_generate = False
|
||||
autosummary_imported_members = True
|
||||
|
||||
autodoc_default_options = {
|
||||
@ -70,3 +72,7 @@ autodoc_default_options = {
|
||||
"show-inheritance": True,
|
||||
"module-first": True,
|
||||
}
|
||||
|
||||
numpydoc_show_class_members = False
|
||||
numpydoc_show_inherited_class_members = False
|
||||
numpydoc_class_members_toctree = False
|
||||
|
||||
@ -80,4 +80,4 @@ pip install batdetect2
|
||||
- Run your first detection workflow:
|
||||
{doc}`tutorials/run-inference-on-folder`
|
||||
- For practical task recipes, go to {doc}`how_to/index`
|
||||
- For command and option details, go to {doc}`reference/cli`
|
||||
- For command and option details, go to {doc}`reference/cli/index`
|
||||
|
||||
@ -27,4 +27,4 @@ batdetect2 predict file_list \
|
||||
- `--workers` to set data-loading parallelism.
|
||||
- `--format` to select output format.
|
||||
|
||||
For complete option details, see {doc}`../reference/cli`.
|
||||
For complete option details, see {doc}`../reference/cli/index`.
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
CLI reference
|
||||
=============
|
||||
|
||||
.. click:: batdetect2.cli:cli
|
||||
:prog: batdetect2
|
||||
:nested: full
|
||||
|
||||
8
docs/source/reference/cli/base.rst
Normal file
8
docs/source/reference/cli/base.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Base command
|
||||
============
|
||||
|
||||
The options on this page apply to all subcommands.
|
||||
|
||||
.. click:: batdetect2.cli:cli
|
||||
:prog: batdetect2
|
||||
:nested: none
|
||||
8
docs/source/reference/cli/data.rst
Normal file
8
docs/source/reference/cli/data.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Data command
|
||||
============
|
||||
|
||||
Inspect and convert dataset config files.
|
||||
|
||||
.. click:: batdetect2.cli.data:data
|
||||
:prog: batdetect2 data
|
||||
:nested: full
|
||||
17
docs/source/reference/cli/detect_legacy.rst
Normal file
17
docs/source/reference/cli/detect_legacy.rst
Normal file
@ -0,0 +1,17 @@
|
||||
Legacy detect command
|
||||
=====================
|
||||
|
||||
.. warning::
|
||||
|
||||
``batdetect2 detect`` is a legacy compatibility command.
|
||||
Prefer ``batdetect2 predict directory`` for new workflows.
|
||||
|
||||
Migration at a glance
|
||||
---------------------
|
||||
|
||||
- Legacy: ``batdetect2 detect AUDIO_DIR ANN_DIR DETECTION_THRESHOLD``
|
||||
- Current: ``batdetect2 predict directory MODEL_PATH AUDIO_DIR OUTPUT_PATH``
|
||||
|
||||
.. click:: batdetect2.cli.compat:detect
|
||||
:prog: batdetect2 detect
|
||||
:nested: none
|
||||
8
docs/source/reference/cli/evaluate.rst
Normal file
8
docs/source/reference/cli/evaluate.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Evaluate command
|
||||
================
|
||||
|
||||
Evaluate a checkpoint against a configured test dataset.
|
||||
|
||||
.. click:: batdetect2.cli.evaluate:evaluate_command
|
||||
:prog: batdetect2 evaluate
|
||||
:nested: none
|
||||
18
docs/source/reference/cli/index.md
Normal file
18
docs/source/reference/cli/index.md
Normal file
@ -0,0 +1,18 @@
|
||||
# CLI reference
|
||||
|
||||
Use this section like this:
|
||||
|
||||
1. Check `base` for global options shared by all commands.
|
||||
2. Pick the command page you need (`predict`, `data`, `train`, or `evaluate`).
|
||||
3. Use `detect_legacy` only if you are maintaining old workflows.
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 1
|
||||
|
||||
base
|
||||
predict
|
||||
data
|
||||
train
|
||||
evaluate
|
||||
detect_legacy
|
||||
```
|
||||
8
docs/source/reference/cli/predict.rst
Normal file
8
docs/source/reference/cli/predict.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Predict command
|
||||
===============
|
||||
|
||||
Run model inference from a directory, a file list, or a dataset.
|
||||
|
||||
.. click:: batdetect2.cli.inference:predict
|
||||
:prog: batdetect2 predict
|
||||
:nested: full
|
||||
8
docs/source/reference/cli/train.rst
Normal file
8
docs/source/reference/cli/train.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Train command
|
||||
=============
|
||||
|
||||
Train a model from dataset configs or fine-tune from a checkpoint.
|
||||
|
||||
.. click:: batdetect2.cli.train:train_command
|
||||
:prog: batdetect2 train
|
||||
:nested: none
|
||||
@ -1,7 +0,0 @@
|
||||
# Config Reference
|
||||
|
||||
```{eval-rst}
|
||||
.. automodule:: batdetect2.config
|
||||
:members:
|
||||
:inherited-members: pydantic.BaseModel
|
||||
```
|
||||
5
docs/source/reference/configs.rst
Normal file
5
docs/source/reference/configs.rst
Normal file
@ -0,0 +1,5 @@
|
||||
Config reference
|
||||
================
|
||||
|
||||
.. automodule:: batdetect2.config
|
||||
:members:
|
||||
@ -6,7 +6,7 @@ configuration, and data structures.
|
||||
```{toctree}
|
||||
:maxdepth: 1
|
||||
|
||||
cli
|
||||
cli/index
|
||||
configs
|
||||
targets
|
||||
```
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
# Targets Reference
|
||||
|
||||
```{eval-rst}
|
||||
.. automodule:: batdetect2.targets
|
||||
:members:
|
||||
```
|
||||
5
docs/source/reference/targets.rst
Normal file
5
docs/source/reference/targets.rst
Normal file
@ -0,0 +1,5 @@
|
||||
Targets reference
|
||||
=================
|
||||
|
||||
.. automodule:: batdetect2.targets
|
||||
:members:
|
||||
@ -1,6 +1,6 @@
|
||||
## Defining Target Geometry: Mapping Sound Event Regions
|
||||
# Defining Target Geometry: Mapping Sound Event Regions
|
||||
|
||||
### Introduction
|
||||
## Introduction
|
||||
|
||||
In the previous steps of defining targets, we focused on determining _which_ sound events are relevant (`filtering`), _what_ descriptive tags they should have (`transform`), and _which category_ they belong to (`classes`).
|
||||
However, for the model to learn effectively, it also needs to know **where** in the spectrogram each sound event is located and approximately **how large** it is.
|
||||
@ -10,7 +10,7 @@ This ROI contains detailed spatial information (start/end time, low/high frequen
|
||||
|
||||
This section explains how BatDetect2 converts the geometric ROI from your annotations into the specific positional and size information used as targets during model training.
|
||||
|
||||
### From ROI to Model Targets: Position & Size
|
||||
## From ROI to Model Targets: Position & Size
|
||||
|
||||
BatDetect2 does not directly predict a full bounding box.
|
||||
Instead, it is trained to predict:
|
||||
@ -21,7 +21,7 @@ Instead, it is trained to predict:
|
||||
This step defines _how_ BatDetect2 calculates this specific reference point and these numerical size values from the original annotation's bounding box.
|
||||
It also handles the reverse process – converting predicted positions and sizes back into bounding boxes for visualization or analysis.
|
||||
|
||||
### Configuring the ROI Mapping
|
||||
## Configuring the ROI Mapping
|
||||
|
||||
You can control how this conversion happens through settings in your configuration file (e.g., your main `.yaml` file).
|
||||
These settings are usually placed within the main `targets:` configuration block, under a specific `roi:` key.
|
||||
@ -74,12 +74,12 @@ targets: # Top-level key for target definition
|
||||
frequency_scale: 0.00116 # e.g., Model predicts height relative to ~860Hz (or other model-specific scaling)
|
||||
```
|
||||
|
||||
### Decoding Size Predictions
|
||||
## Decoding Size Predictions
|
||||
|
||||
These scaling factors (`time_scale`, `frequency_scale`) are also essential for interpreting the model's output correctly.
|
||||
When the model predicts numerical values for width and height, BatDetect2 uses these same scales (in reverse) to convert those numbers back into physically meaningful durations (seconds) and bandwidths (Hz/kHz) when reconstructing bounding boxes from predictions.
|
||||
|
||||
### Outcome
|
||||
## Outcome
|
||||
|
||||
By configuring the `roi` settings, you ensure that BatDetect2 consistently translates the geometric information from your annotations into the specific reference points and scaled size values required for training the model.
|
||||
Using consistent scales that are appropriate for your data and potentially beneficial for training stability allows the model to effectively learn not just _what_ sound is present, but also _where_ it is located and _how large_ it is, and enables meaningful interpretation of the model's spatial and size predictions.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
## Bringing It All Together: The `Targets` Object
|
||||
# Bringing It All Together: The `Targets` Object
|
||||
|
||||
### Recap: Defining Your Target Strategy
|
||||
## Recap: Defining Your Target Strategy
|
||||
|
||||
In the previous sections, we covered the sequential steps to precisely define what your BatDetect2 model should learn, specified within your configuration file:
|
||||
|
||||
@ -12,7 +12,7 @@ In the previous sections, we covered the sequential steps to precisely define wh
|
||||
|
||||
You define all these aspects within your configuration file (e.g., YAML), which holds the complete specification for your target definition strategy, typically under a main `targets:` key.
|
||||
|
||||
### What is the `Targets` Object?
|
||||
## What is the `Targets` Object?
|
||||
|
||||
While the configuration file specifies _what_ you want to happen, BatDetect2 needs an active component to actually _perform_ these steps.
|
||||
This is the role of the `Targets` object.
|
||||
@ -21,7 +21,7 @@ The `Targets` is an organized container that holds all the specific functions an
|
||||
It's created directly from your configuration and provides methods to apply the **filtering**, **transformation**, **ROI mapping** (geometry to position/size and back), **class encoding**, and **class decoding** steps you defined.
|
||||
It effectively bundles together all the target definition logic determined by your settings into a single, usable object.
|
||||
|
||||
### How is it Created and Used?
|
||||
## How is it Created and Used?
|
||||
|
||||
For most standard training workflows, you typically won't need to create or interact with the `Targets` object directly in Python code.
|
||||
BatDetect2 usually handles its creation automatically when you provide your main configuration file during training setup.
|
||||
@ -50,7 +50,7 @@ targets_processor: TargetProtocol = load_targets(target_config_file)
|
||||
# to be used internally by the training pipeline or for prediction processing.
|
||||
```
|
||||
|
||||
### What Does the `Targets` Object Do? (Its Role)
|
||||
## What Does the `Targets` Object Do? (Its Role)
|
||||
|
||||
Once created, the `targets_processor` object plays several vital roles within the BatDetect2 system:
|
||||
|
||||
@ -71,7 +71,7 @@ Once created, the `targets_processor` object plays several vital roles within th
|
||||
- `targets_processor.generic_class_tags`: The tags representing the generic class.
|
||||
- `targets_processor.dimension_names`: The names used for the size dimensions (e.g., `['width', 'height']`).
|
||||
|
||||
### Why is Understanding This Important?
|
||||
## Why is Understanding This Important?
|
||||
|
||||
As a researcher using BatDetect2, your primary interaction is typically through the **configuration file**.
|
||||
The `Targets` object is the component that materializes your configurations.
|
||||
@ -84,7 +84,7 @@ Understanding its role can be important:
|
||||
While standard training runs handle this object internally, the underlying functions for filtering, transforming, encoding, decoding, and ROI mapping are accessible or can be built individually.
|
||||
This modular design provides the **flexibility to use or customize specific parts of the target definition workflow programmatically** for advanced analyses, integration tasks, or specialized data processing pipelines, should you need to go beyond the standard configuration-driven approach.
|
||||
|
||||
### Summary
|
||||
## Summary
|
||||
|
||||
The `Targets` object encapsulates the entire configured target definition logic specified in your `TargetConfig` file.
|
||||
It acts as the central component within BatDetect2 for applying filtering, tag transformation, ROI mapping (geometry to/from position/size), class encoding (for training preparation), and class/ROI decoding (for interpreting predictions).
|
||||
|
||||
@ -25,7 +25,7 @@ batdetect2 predict directory \
|
||||
## What to do next
|
||||
|
||||
- Use {doc}`../how_to/tune-detection-threshold` to tune sensitivity.
|
||||
- Use {doc}`../reference/cli` for full command options.
|
||||
- Use {doc}`../reference/cli/index` for full command options.
|
||||
|
||||
Note: this is the initial Phase 1 scaffold and will be expanded with a full,
|
||||
validated end-to-end walkthrough.
|
||||
|
||||
@ -27,7 +27,11 @@ BatDetect2 - Detection and Classification
|
||||
help="Increase verbosity. -v for INFO, -vv for DEBUG.",
|
||||
)
|
||||
def cli(verbose: int = 0):
|
||||
"""BatDetect2 - Bat Call Detection and Classification."""
|
||||
"""Run the BatDetect2 CLI.
|
||||
|
||||
This command initializes logging and exposes subcommands for prediction,
|
||||
training, evaluation, and dataset utilities.
|
||||
"""
|
||||
click.echo(INFO_STR)
|
||||
|
||||
enable_logging(verbose)
|
||||
|
||||
@ -12,7 +12,13 @@ DEFAULT_MODEL_PATH = os.path.join(
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@cli.command(
|
||||
short_help="Legacy detection command.",
|
||||
epilog=(
|
||||
"Deprecated workflow. Prefer `batdetect2 predict directory` for "
|
||||
"new analyses."
|
||||
),
|
||||
)
|
||||
@click.argument(
|
||||
"audio_dir",
|
||||
type=click.Path(exists=True),
|
||||
@ -68,7 +74,10 @@ def detect(
|
||||
time_expansion_factor: int,
|
||||
**args,
|
||||
):
|
||||
"""Detect bat calls in files in AUDIO_DIR and save predictions to ANN_DIR.
|
||||
"""Legacy detection command for directory-based inference.
|
||||
|
||||
Detect bat calls in files in `AUDIO_DIR` and save predictions to
|
||||
`ANN_DIR`.
|
||||
|
||||
DETECTION_THRESHOLD is the detection threshold. All predictions with a
|
||||
score below this threshold will be discarded. Values between 0 and 1.
|
||||
@ -78,6 +87,11 @@ def detect(
|
||||
Spaces in the input paths will throw an error. Wrap in quotes.
|
||||
|
||||
Input files should be short in duration e.g. < 30 seconds.
|
||||
|
||||
Note
|
||||
----
|
||||
This command is kept for backwards compatibility. Prefer
|
||||
`batdetect2 predict directory` for new workflows.
|
||||
"""
|
||||
from batdetect2 import api
|
||||
from batdetect2.utils.detector_utils import save_results_to_file
|
||||
@ -132,7 +146,7 @@ def detect(
|
||||
|
||||
|
||||
def print_config(config):
|
||||
"""Print the processing configuration."""
|
||||
"""Print the processing configuration values."""
|
||||
click.echo("\nProcessing Configuration:")
|
||||
click.echo(f"Time Expansion Factor: {config.get('time_expansion')}")
|
||||
click.echo(f"Detection Threshold: {config.get('detection_threshold')}")
|
||||
|
||||
@ -7,12 +7,12 @@ from batdetect2.cli.base import cli
|
||||
__all__ = ["data"]
|
||||
|
||||
|
||||
@cli.group()
|
||||
@cli.group(short_help="Inspect and convert datasets.")
|
||||
def data():
|
||||
"""Inspect and convert dataset configuration files."""
|
||||
|
||||
|
||||
@data.command()
|
||||
@data.command(short_help="Print dataset summary information.")
|
||||
@click.argument(
|
||||
"dataset_config",
|
||||
type=click.Path(exists=True),
|
||||
@ -20,17 +20,27 @@ def data():
|
||||
@click.option(
|
||||
"--field",
|
||||
type=str,
|
||||
help="If the dataset info is in a nested field please specify here.",
|
||||
help=(
|
||||
"Nested field name that contains dataset configuration. "
|
||||
"Use this when the config is wrapped in a larger file."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--targets",
|
||||
"targets_path",
|
||||
type=click.Path(exists=True),
|
||||
help=(
|
||||
"Path to targets config file. If provided, a per-class summary "
|
||||
"table is printed."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--base-dir",
|
||||
type=click.Path(exists=True),
|
||||
help="The base directory to which all recording and annotations paths are relative to.",
|
||||
help=(
|
||||
"Base directory used to resolve relative recording and annotation "
|
||||
"paths in the dataset config."
|
||||
),
|
||||
)
|
||||
def summary(
|
||||
dataset_config: Path,
|
||||
@ -38,7 +48,11 @@ def summary(
|
||||
targets_path: Path | None = None,
|
||||
base_dir: Path | None = None,
|
||||
):
|
||||
"""Show annotation counts and optional class summary."""
|
||||
"""Show dataset size and optional class summary.
|
||||
|
||||
Prints the number of annotated clips. If `--targets` is provided, it also
|
||||
prints a per-class summary table based on the configured targets.
|
||||
"""
|
||||
from batdetect2.data import compute_class_summary, load_dataset_from_config
|
||||
from batdetect2.targets import load_targets
|
||||
|
||||
@ -62,7 +76,7 @@ def summary(
|
||||
print(summary.to_markdown())
|
||||
|
||||
|
||||
@data.command()
|
||||
@data.command(short_help="Convert dataset config to annotation set.")
|
||||
@click.argument(
|
||||
"dataset_config",
|
||||
type=click.Path(exists=True),
|
||||
@ -70,7 +84,10 @@ def summary(
|
||||
@click.option(
|
||||
"--field",
|
||||
type=str,
|
||||
help="If the dataset info is in a nested field please specify here.",
|
||||
help=(
|
||||
"Nested field name that contains dataset configuration. "
|
||||
"Use this when the config is wrapped in a larger file."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
@ -80,12 +97,18 @@ def summary(
|
||||
@click.option(
|
||||
"--base-dir",
|
||||
type=click.Path(exists=True),
|
||||
help="The base directory to which all recording and annotations paths are relative to.",
|
||||
help=(
|
||||
"Base directory used to resolve relative recording and annotation "
|
||||
"paths in the dataset config."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--audio-dir",
|
||||
type=click.Path(exists=True),
|
||||
help="The directory containing the audio files. All paths will be relative to this directory.",
|
||||
help=(
|
||||
"Directory containing audio files. Output annotation paths are "
|
||||
"made relative to this directory."
|
||||
),
|
||||
)
|
||||
def convert(
|
||||
dataset_config: Path,
|
||||
@ -94,7 +117,11 @@ def convert(
|
||||
base_dir: Path | None = None,
|
||||
audio_dir: Path | None = None,
|
||||
):
|
||||
"""Convert a dataset config file to soundevent format."""
|
||||
"""Convert a dataset config into soundevent annotation-set format.
|
||||
|
||||
Writes a single annotation-set file that can be used by downstream tools.
|
||||
Use `--audio-dir` to control relative audio path handling in the output.
|
||||
"""
|
||||
from soundevent import data, io
|
||||
|
||||
from batdetect2.data import load_dataset, load_dataset_config
|
||||
|
||||
@ -11,20 +11,73 @@ __all__ = ["evaluate_command"]
|
||||
DEFAULT_OUTPUT_DIR = Path("outputs") / "evaluation"
|
||||
|
||||
|
||||
@cli.command(name="evaluate")
|
||||
@cli.command(name="evaluate", short_help="Evaluate a model checkpoint.")
|
||||
@click.argument("model_path", type=click.Path(exists=True))
|
||||
@click.argument("test_dataset", type=click.Path(exists=True))
|
||||
@click.option("--targets", "targets_config", type=click.Path(exists=True))
|
||||
@click.option("--audio-config", type=click.Path(exists=True))
|
||||
@click.option("--evaluation-config", type=click.Path(exists=True))
|
||||
@click.option("--inference-config", type=click.Path(exists=True))
|
||||
@click.option("--outputs-config", type=click.Path(exists=True))
|
||||
@click.option("--logging-config", type=click.Path(exists=True))
|
||||
@click.option("--base-dir", type=click.Path(), default=Path.cwd())
|
||||
@click.option("--output-dir", type=click.Path(), default=DEFAULT_OUTPUT_DIR)
|
||||
@click.option("--experiment-name", type=str)
|
||||
@click.option("--run-name", type=str)
|
||||
@click.option("--workers", "num_workers", type=int)
|
||||
@click.option(
|
||||
"--targets",
|
||||
"targets_config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to targets config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--audio-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to audio config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--evaluation-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to evaluation config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--inference-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to inference config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--outputs-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to outputs config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--logging-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to logging config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--base-dir",
|
||||
type=click.Path(),
|
||||
default=Path.cwd(),
|
||||
show_default=True,
|
||||
help=(
|
||||
"Base directory used to resolve relative paths in the dataset "
|
||||
"configuration."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--output-dir",
|
||||
type=click.Path(),
|
||||
default=DEFAULT_OUTPUT_DIR,
|
||||
show_default=True,
|
||||
help="Directory where evaluation outputs are written.",
|
||||
)
|
||||
@click.option(
|
||||
"--experiment-name",
|
||||
type=str,
|
||||
help="Experiment name used for logging backends.",
|
||||
)
|
||||
@click.option(
|
||||
"--run-name",
|
||||
type=str,
|
||||
help="Run name used for logging backends.",
|
||||
)
|
||||
@click.option(
|
||||
"--workers",
|
||||
"num_workers",
|
||||
type=int,
|
||||
help="Number of worker processes for dataset loading.",
|
||||
)
|
||||
def evaluate_command(
|
||||
model_path: Path,
|
||||
test_dataset: Path,
|
||||
@ -40,7 +93,11 @@ def evaluate_command(
|
||||
experiment_name: str | None = None,
|
||||
run_name: str | None = None,
|
||||
):
|
||||
"""Evaluate a checkpoint against a configured test dataset."""
|
||||
"""Evaluate a checkpoint against a test dataset.
|
||||
|
||||
Loads model and optional override configs, runs evaluation on
|
||||
`test_dataset`, and writes metrics/artifacts to `output_dir`.
|
||||
"""
|
||||
from batdetect2.api_v2 import BatDetect2API
|
||||
from batdetect2.audio import AudioConfig
|
||||
from batdetect2.data import load_dataset_from_config
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import click
|
||||
from loguru import logger
|
||||
@ -7,12 +9,89 @@ from soundevent.audio.files import get_audio_files
|
||||
|
||||
from batdetect2.cli.base import cli
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from batdetect2.api_v2 import BatDetect2API
|
||||
from batdetect2.audio import AudioConfig
|
||||
from batdetect2.inference import InferenceConfig
|
||||
from batdetect2.outputs import OutputsConfig
|
||||
|
||||
__all__ = ["predict"]
|
||||
|
||||
|
||||
@cli.group(name="predict")
|
||||
@cli.group(name="predict", short_help="Run prediction workflows.")
|
||||
def predict() -> None:
|
||||
"""Run model inference on audio using API v2."""
|
||||
"""Run model inference on audio files.
|
||||
|
||||
Use one of the subcommands to select inputs from a directory, a text file
|
||||
list, or an annotation dataset.
|
||||
"""
|
||||
|
||||
|
||||
def common_predict_options(func):
|
||||
"""Attach options shared by all `predict` subcommands."""
|
||||
|
||||
@click.option(
|
||||
"--audio-config",
|
||||
type=click.Path(exists=True),
|
||||
help=(
|
||||
"Path to an audio config file. Use this to override audio "
|
||||
"loading and preprocessing-related settings."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--inference-config",
|
||||
type=click.Path(exists=True),
|
||||
help=(
|
||||
"Path to an inference config file. Use this to override "
|
||||
"prediction-time thresholds and behavior."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--outputs-config",
|
||||
type=click.Path(exists=True),
|
||||
help=(
|
||||
"Path to an outputs config file. Use this to control the "
|
||||
"prediction fields written to disk."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--logging-config",
|
||||
type=click.Path(exists=True),
|
||||
help=(
|
||||
"Path to a logging config file. Use this to customize logging "
|
||||
"format and levels."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--batch-size",
|
||||
type=int,
|
||||
help=(
|
||||
"Batch size for inference. If omitted, the value from the "
|
||||
"loaded config is used."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--workers",
|
||||
"num_workers",
|
||||
type=int,
|
||||
default=0,
|
||||
show_default=True,
|
||||
help="Number of worker processes for audio loading.",
|
||||
)
|
||||
@click.option(
|
||||
"--format",
|
||||
"format_name",
|
||||
type=str,
|
||||
help=(
|
||||
"Output format name used by the prediction writer. If omitted, "
|
||||
"the default output format is used."
|
||||
),
|
||||
)
|
||||
@wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _build_api(
|
||||
@ -21,7 +100,7 @@ def _build_api(
|
||||
inference_config: Path | None,
|
||||
outputs_config: Path | None,
|
||||
logging_config: Path | None,
|
||||
):
|
||||
) -> "tuple[BatDetect2API, AudioConfig | None, InferenceConfig | None, OutputsConfig | None]":
|
||||
from batdetect2.api_v2 import BatDetect2API
|
||||
from batdetect2.audio import AudioConfig
|
||||
from batdetect2.inference import InferenceConfig
|
||||
@ -103,17 +182,14 @@ def _run_prediction(
|
||||
)
|
||||
|
||||
|
||||
@predict.command(name="directory")
|
||||
@predict.command(
|
||||
name="directory",
|
||||
short_help="Predict on audio files in a directory.",
|
||||
)
|
||||
@click.argument("model_path", type=click.Path(exists=True))
|
||||
@click.argument("audio_dir", type=click.Path(exists=True))
|
||||
@click.argument("output_path", type=click.Path())
|
||||
@click.option("--audio-config", type=click.Path(exists=True))
|
||||
@click.option("--inference-config", type=click.Path(exists=True))
|
||||
@click.option("--outputs-config", type=click.Path(exists=True))
|
||||
@click.option("--logging-config", type=click.Path(exists=True))
|
||||
@click.option("--batch-size", type=int)
|
||||
@click.option("--workers", "num_workers", type=int, default=0)
|
||||
@click.option("--format", "format_name", type=str)
|
||||
@common_predict_options
|
||||
def predict_directory_command(
|
||||
model_path: Path,
|
||||
audio_dir: Path,
|
||||
@ -126,7 +202,11 @@ def predict_directory_command(
|
||||
num_workers: int,
|
||||
format_name: str | None,
|
||||
) -> None:
|
||||
"""Predict on all audio files in a directory."""
|
||||
"""Predict on all audio files in a directory.
|
||||
|
||||
Loads a checkpoint, scans `audio_dir` for supported audio files, runs
|
||||
inference, and saves predictions to `output_path`.
|
||||
"""
|
||||
audio_files = list(get_audio_files(audio_dir))
|
||||
_run_prediction(
|
||||
model_path=model_path,
|
||||
@ -142,17 +222,14 @@ def predict_directory_command(
|
||||
)
|
||||
|
||||
|
||||
@predict.command(name="file_list")
|
||||
@predict.command(
|
||||
name="file_list",
|
||||
short_help="Predict on paths listed in a text file.",
|
||||
)
|
||||
@click.argument("model_path", type=click.Path(exists=True))
|
||||
@click.argument("file_list", type=click.Path(exists=True))
|
||||
@click.argument("output_path", type=click.Path())
|
||||
@click.option("--audio-config", type=click.Path(exists=True))
|
||||
@click.option("--inference-config", type=click.Path(exists=True))
|
||||
@click.option("--outputs-config", type=click.Path(exists=True))
|
||||
@click.option("--logging-config", type=click.Path(exists=True))
|
||||
@click.option("--batch-size", type=int)
|
||||
@click.option("--workers", "num_workers", type=int, default=0)
|
||||
@click.option("--format", "format_name", type=str)
|
||||
@common_predict_options
|
||||
def predict_file_list_command(
|
||||
model_path: Path,
|
||||
file_list: Path,
|
||||
@ -165,7 +242,11 @@ def predict_file_list_command(
|
||||
num_workers: int,
|
||||
format_name: str | None,
|
||||
) -> None:
|
||||
"""Predict on audio files listed in a text file."""
|
||||
"""Predict on audio files listed in a text file.
|
||||
|
||||
The list file should contain one audio path per line. Empty lines are
|
||||
ignored.
|
||||
"""
|
||||
file_list = Path(file_list)
|
||||
audio_files = [
|
||||
Path(line.strip())
|
||||
@ -187,17 +268,14 @@ def predict_file_list_command(
|
||||
)
|
||||
|
||||
|
||||
@predict.command(name="dataset")
|
||||
@predict.command(
|
||||
name="dataset",
|
||||
short_help="Predict on recordings from a dataset config.",
|
||||
)
|
||||
@click.argument("model_path", type=click.Path(exists=True))
|
||||
@click.argument("dataset_path", type=click.Path(exists=True))
|
||||
@click.argument("output_path", type=click.Path())
|
||||
@click.option("--audio-config", type=click.Path(exists=True))
|
||||
@click.option("--inference-config", type=click.Path(exists=True))
|
||||
@click.option("--outputs-config", type=click.Path(exists=True))
|
||||
@click.option("--logging-config", type=click.Path(exists=True))
|
||||
@click.option("--batch-size", type=int)
|
||||
@click.option("--workers", "num_workers", type=int, default=0)
|
||||
@click.option("--format", "format_name", type=str)
|
||||
@common_predict_options
|
||||
def predict_dataset_command(
|
||||
model_path: Path,
|
||||
dataset_path: Path,
|
||||
@ -210,7 +288,11 @@ def predict_dataset_command(
|
||||
num_workers: int,
|
||||
format_name: str | None,
|
||||
) -> None:
|
||||
"""Predict on recordings referenced in an annotation dataset."""
|
||||
"""Predict on recordings referenced in an annotation dataset.
|
||||
|
||||
The dataset is read as a soundevent annotation set and unique recording
|
||||
paths are extracted before inference.
|
||||
"""
|
||||
dataset_path = Path(dataset_path)
|
||||
dataset = io.load(dataset_path, type="annotation_set")
|
||||
audio_files = sorted(
|
||||
|
||||
@ -8,26 +8,103 @@ from batdetect2.cli.base import cli
|
||||
__all__ = ["train_command"]
|
||||
|
||||
|
||||
@cli.command(name="train")
|
||||
@cli.command(name="train", short_help="Train or fine-tune a model.")
|
||||
@click.argument("train_dataset", type=click.Path(exists=True))
|
||||
@click.option("--val-dataset", type=click.Path(exists=True))
|
||||
@click.option("--model", "model_path", type=click.Path(exists=True))
|
||||
@click.option("--targets", "targets_config", type=click.Path(exists=True))
|
||||
@click.option("--model-config", type=click.Path(exists=True))
|
||||
@click.option("--training-config", type=click.Path(exists=True))
|
||||
@click.option("--audio-config", type=click.Path(exists=True))
|
||||
@click.option("--evaluation-config", type=click.Path(exists=True))
|
||||
@click.option("--inference-config", type=click.Path(exists=True))
|
||||
@click.option("--outputs-config", type=click.Path(exists=True))
|
||||
@click.option("--logging-config", type=click.Path(exists=True))
|
||||
@click.option("--ckpt-dir", type=click.Path(exists=True))
|
||||
@click.option("--log-dir", type=click.Path(exists=True))
|
||||
@click.option("--train-workers", type=int)
|
||||
@click.option("--val-workers", type=int)
|
||||
@click.option("--num-epochs", type=int)
|
||||
@click.option("--experiment-name", type=str)
|
||||
@click.option("--run-name", type=str)
|
||||
@click.option("--seed", type=int)
|
||||
@click.option(
|
||||
"--val-dataset",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to validation dataset config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--model",
|
||||
"model_path",
|
||||
type=click.Path(exists=True),
|
||||
help=(
|
||||
"Path to a checkpoint to continue training from. If omitted, "
|
||||
"training starts from a fresh model config."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--targets",
|
||||
"targets_config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to targets config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--model-config",
|
||||
type=click.Path(exists=True),
|
||||
help=("Path to model config file. Cannot be used together with --model."),
|
||||
)
|
||||
@click.option(
|
||||
"--training-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to training config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--audio-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to audio config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--evaluation-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to evaluation config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--inference-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to inference config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--outputs-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to outputs config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--logging-config",
|
||||
type=click.Path(exists=True),
|
||||
help="Path to logging config file.",
|
||||
)
|
||||
@click.option(
|
||||
"--ckpt-dir",
|
||||
type=click.Path(exists=True),
|
||||
help="Directory where checkpoints are saved.",
|
||||
)
|
||||
@click.option(
|
||||
"--log-dir",
|
||||
type=click.Path(exists=True),
|
||||
help="Directory where logs are written.",
|
||||
)
|
||||
@click.option(
|
||||
"--train-workers",
|
||||
type=int,
|
||||
help="Number of worker processes for training data loading.",
|
||||
)
|
||||
@click.option(
|
||||
"--val-workers",
|
||||
type=int,
|
||||
help="Number of worker processes for validation data loading.",
|
||||
)
|
||||
@click.option(
|
||||
"--num-epochs",
|
||||
type=int,
|
||||
help="Maximum number of training epochs.",
|
||||
)
|
||||
@click.option(
|
||||
"--experiment-name",
|
||||
type=str,
|
||||
help="Experiment name used for logging backends.",
|
||||
)
|
||||
@click.option(
|
||||
"--run-name",
|
||||
type=str,
|
||||
help="Run name used for logging backends.",
|
||||
)
|
||||
@click.option(
|
||||
"--seed",
|
||||
type=int,
|
||||
help="Random seed used for reproducibility.",
|
||||
)
|
||||
def train_command(
|
||||
train_dataset: Path,
|
||||
val_dataset: Path | None = None,
|
||||
@ -49,7 +126,12 @@ def train_command(
|
||||
experiment_name: str | None = None,
|
||||
run_name: str | None = None,
|
||||
):
|
||||
"""Train a model from dataset configs or a checkpoint."""
|
||||
"""Train a BatDetect2 model.
|
||||
|
||||
Train either from a fresh config (`--model-config`) or by fine-tuning an
|
||||
existing checkpoint (`--model`). Training data are loaded from
|
||||
`train_dataset`, with optional validation data from `--val-dataset`.
|
||||
"""
|
||||
from batdetect2.api_v2 import BatDetect2API
|
||||
from batdetect2.audio import AudioConfig
|
||||
from batdetect2.config import BatDetect2Config
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
"""Main entry point for the BatDetect2 Postprocessing pipeline."""
|
||||
|
||||
from batdetect2.postprocess.config import PostprocessConfig
|
||||
from batdetect2.postprocess.config import (
|
||||
DEFAULT_CLASSIFICATION_THRESHOLD,
|
||||
DEFAULT_DETECTION_THRESHOLD,
|
||||
PostprocessConfig,
|
||||
)
|
||||
from batdetect2.postprocess.nms import non_max_suppression
|
||||
from batdetect2.postprocess.postprocessor import (
|
||||
Postprocessor,
|
||||
@ -28,4 +32,6 @@ __all__ = [
|
||||
"PostprocessorProtocol",
|
||||
"build_postprocessor",
|
||||
"non_max_suppression",
|
||||
"DEFAULT_CLASSIFICATION_THRESHOLD",
|
||||
"DEFAULT_DETECTION_THRESHOLD",
|
||||
]
|
||||
|
||||
@ -1,23 +1,19 @@
|
||||
"""Handles mapping between geometric ROIs and target representations.
|
||||
"""Map geometric ROIs to target representations and back.
|
||||
|
||||
This module defines a standardized interface (`ROITargetMapper`) for converting
|
||||
a sound event's Region of Interest (ROI) into a target representation suitable
|
||||
for machine learning models, and for decoding model outputs back into geometric
|
||||
ROIs.
|
||||
a sound event ROI into a target representation for model training and decoding
|
||||
model outputs back into approximate geometries.
|
||||
|
||||
The core operations are:
|
||||
1. **Encoding**: A `soundevent.data.SoundEvent` is mapped to a reference
|
||||
`Position` (time, frequency) and a `Size` array. The method for
|
||||
determining the position and size varies by the mapper implementation
|
||||
(e.g., using a bounding box anchor or the point of peak energy).
|
||||
2. **Decoding**: A `Position` and `Size` array are mapped back to an
|
||||
approximate `soundevent.data.Geometry` (typically a `BoundingBox`).
|
||||
Core operations:
|
||||
|
||||
This logic is encapsulated within specific mapper classes. Configuration for
|
||||
each mapper (e.g., anchor point, scaling factors) is managed by a corresponding
|
||||
Pydantic config object. The `ROIMapperConfig` type allows for flexibly
|
||||
selecting and configuring the desired mapper. This module separates the
|
||||
*geometric* aspect of target definition from *semantic* classification.
|
||||
- Encode a `soundevent.data.SoundEvent` into a reference `Position`
|
||||
`(time, frequency)` and a `Size` array.
|
||||
- Decode a `Position` and `Size` array into an approximate
|
||||
`soundevent.data.Geometry` (usually a `BoundingBox`).
|
||||
|
||||
The specific mapping depends on the selected mapper implementation. Config
|
||||
objects provide mapper-specific parameters such as anchor choice and scaling.
|
||||
This module focuses on the geometric part of target definition.
|
||||
"""
|
||||
|
||||
from typing import Annotated, Literal
|
||||
@ -131,12 +127,12 @@ class AnchorBBoxMapper(ROITargetMapper):
|
||||
This class implements the `ROITargetMapper` protocol for `BoundingBox`
|
||||
geometries.
|
||||
|
||||
**Encoding**: The `position` is a fixed anchor point on the bounding box
|
||||
(e.g., "bottom-left"). The `size` is a 2-element array containing the
|
||||
scaled width and height of the box.
|
||||
Encoding uses a fixed anchor point on the bounding box for `position`
|
||||
(for example, ``bottom-left``). The `size` is a 2-element array with
|
||||
scaled width and height.
|
||||
|
||||
**Decoding**: Reconstructs a `BoundingBox` from an anchor point and
|
||||
scaled width/height.
|
||||
Decoding reconstructs a `BoundingBox` from anchor position and scaled
|
||||
width/height.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@ -300,13 +296,12 @@ class PeakEnergyBBoxMapper(ROITargetMapper):
|
||||
|
||||
This class implements the `ROITargetMapper` protocol.
|
||||
|
||||
**Encoding**: The `position` is the (time, frequency) coordinate of the
|
||||
point with the highest energy within the sound event's bounding box. The
|
||||
`size` is a 4-element array representing the scaled distances from this
|
||||
peak energy point to the left, bottom, right, and top edges of the box.
|
||||
Encoding sets `position` to the (time, frequency) coordinate of peak energy
|
||||
inside the sound event bounding box. The `size` is a 4-element array with
|
||||
scaled distances from the peak point to left, bottom, right, and top edges.
|
||||
|
||||
**Decoding**: Reconstructs a `BoundingBox` by adding/subtracting the
|
||||
un-scaled distances from the peak energy point.
|
||||
Decoding reconstructs a `BoundingBox` by applying the unscaled distances to
|
||||
the peak-energy position.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
||||
Loading…
Reference in New Issue
Block a user