diff --git a/birdnet_analyzer/analyze/cli.py b/birdnet_analyzer/analyze/cli.py index d786c38c4..1a794c0e3 100644 --- a/birdnet_analyzer/analyze/cli.py +++ b/birdnet_analyzer/analyze/cli.py @@ -43,4 +43,4 @@ def main(): analyze_args.pop("use_perch") # handled via model param analyze_args.pop("load_params") # already applied as defaults - analyze(**analyze_args) + analyze(**analyze_args, save_params=True) diff --git a/birdnet_analyzer/analyze/core.py b/birdnet_analyzer/analyze/core.py index f6f090e15..59b359540 100644 --- a/birdnet_analyzer/analyze/core.py +++ b/birdnet_analyzer/analyze/core.py @@ -49,70 +49,96 @@ def analyze( split_tables: bool = False, strict_species_list: bool = False, save_params: bool = False, - show_progress: bool = False, _return_only=False, ): """ Analyzes audio files for bird species detection using the BirdNET-Analyzer. Args: - audio_input (str): Path to the input directory or file containing audio data. - output (str | None, optional): Path to the output directory for results. - Defaults to None. - min_conf (float, optional): Minimum confidence threshold for detections. + audio_input (str): Path to an audio file, or to a folder that is searched + recursively for audio files. + output (str | None, optional): Folder for the result files. Defaults to None, + which is the folder of an input file or the input folder itself. + model (str, optional): "birdnet" or "perch". Defaults to "birdnet". + birdnet (Literal["2.4", "3.0"], optional): BirdNET version used when + ``model`` is "birdnet". Defaults to "3.0". + min_conf (float, optional): Minimum confidence of a detection, in [0, 1). Defaults to 0.25. - classifier (str | None, optional): Path to a custom classifier file. - Defaults to None. - lat (float | None, optional): Latitude for location-based filtering. - Defaults to None. - lon (float | None, optional): Longitude for location-based filtering. - Defaults to None. - week (int, optional): Week of the year for seasonal filtering. Defaults to -1. - slist (str | None, optional): Path to a species list file for filtering. + classifier (str | None, optional): Path to a custom classifier (.tflite), + loaded instead of the BirdNET model; its BirdNET version is read from the + file. Cannot be combined with ``model`` "perch". Defaults to None. + cc_species_list (str | None, optional): Labels file of ``classifier``. + Defaults to None, which is the classifier path with ".tflite" replaced + by "_Labels.txt". + lat (float | None, optional): Latitude of the recording location. Together + with ``lon``, the detections are limited to the species the geo model + predicts there. Defaults to None. + lon (float | None, optional): Longitude of the recording location. Defaults to None. + week (int | None, optional): Week of the year (1-48) for the geo model. + Defaults to None, the year-round list. + slist (str | Path | Collection[str] | None, optional): Species to limit the + detections to: a species list file, a folder holding "species_list.txt", + or the labels themselves. Defaults to None. sensitivity (float, optional): Sensitivity of the detection algorithm; applies to every BirdNET model and custom classifier, ignored (with a warning) for Perch. Defaults to 1.0. - overlap (float, optional): Overlap between analysis windows in seconds. + overlap (float, optional): Overlap of consecutive segments in seconds. Defaults to 0. - fmin (int, optional): Minimum frequency for analysis in Hz. Defaults to 0. - fmax (int, optional): Maximum frequency for analysis in Hz. Defaults to 15000. - audio_speed (float, optional): Speed factor for audio playback during analysis. - Defaults to 1.0. - batch_size (int, optional): Batch size for processing. Defaults to 1. - rtype (Literal["table", "audacity", "kaleidoscope", "csv", "parquet"] | - List[Literal["table", "audacity", "kaleidoscope", "csv", "parquet"]], optional): - Output format(s) for results. Defaults to "table". - sf_thresh (float, optional): Threshold for species filtering. Defaults to 0.03. - top_n (int | None, optional): Limit the number of top detections per file. - Defaults to None. - merge_consecutive (int, optional): Merge consecutive detections within this time - window in seconds. Defaults to 1. - threads (int, optional): Number of CPU threads to use for analysis. - Defaults to 8. - locale (str, optional): Locale for species names and output. Defaults to "en". - additional_columns (list[str] | None, optional): Additional columns to include - in the output. Defaults to None. - use_perch (bool, optional): Whether to use the Perch model for analysis. - Defaults to False. - split_tables (bool, optional): Whether to split output tables by input files. - Defaults to False. + fmin (int, optional): Lower bandpass frequency in Hz. Defaults to 0. + fmax (int, optional): Upper bandpass frequency in Hz. Defaults to 15000. + audio_speed (float, optional): Speed factor applied to the audio before the + analysis. Defaults to 1.0. + batch_size (int, optional): Segments per inference batch; a classifier + trained on BirdNET 3.0 always takes 1. Defaults to 1. + n_workers (int | None, optional): Number of inference worker processes. + Defaults to None, one per physical CPU core. + n_producers (int, optional): Number of processes reading the audio files. + Defaults to 1. + rtype (str | list[str], optional): Result file format(s): "table" (Raven + selection table), "audacity", "kaleidoscope", "csv" or "parquet". + Defaults to "table". + sf_thresh (float, optional): Minimum occurrence frequency of a species in the + geo model's prediction. Defaults to 0.03. + top_n (int | None, optional): Keep at most this many detections per segment, + the highest-scoring ones; ``min_conf`` still applies. Defaults to None. + merge_consecutive (int, optional): Maximum number of consecutive detections + of a species merged into one; 0 or 1 disables merging. Defaults to 1. + locale (str, optional): Language of the species names of a BirdNET model; + one its version lacks falls back to "en_us" with a warning. + Defaults to "en_us". + additional_columns (list[str] | None, optional): Extra columns of the "csv" + and "parquet" results, any of "lat", "lon", "week", "overlap", + "sensitivity", "min_conf", "species_list" and "model". Defaults to None. + on_update (Callable[[AcousticProgressStats], None] | None, optional): Called + with the progress of the inference. Defaults to None. + split_tables (bool, optional): Write the results of each input file to files + of their own. Defaults to False. strict_species_list (bool, optional): If True, raise when a species in ``slist`` is not in the model. If False (default), such species are matched to the model by scientific/common name where possible and any that remain unknown - are skipped with a warning. Only affects a user-provided ``slist``. + are skipped with a warning. Only affects an ``slist`` file. + save_params (bool, optional): Write the parameters to + "birdnet.analyze-params.csv" in ``output``. Defaults to False. + _return_only (bool, optional): Only return the predictions, without writing + result files or a resume journal. Defaults to False. Returns: - None + The predictions, a birdnet ``AcousticFilePredictionResult``. A resumed run + returns those of the files it analyzed, or None if none were left. Raises: - ValueError: If input path is invalid or required parameters are missing. + ValueError: If only one of ``lat`` and ``lon`` is given, they are combined + with ``slist``, ``classifier`` is combined with ``model`` "perch", + ``lat``, ``lon``, ``week`` or ``min_conf`` is out of range, ``model`` is + unknown, ``audio_input`` does not exist or holds no supported audio file, + or none of the species in an ``slist`` file (with + ``strict_species_list``, not all of them) are in the model. + FileNotFoundError: If the ``slist`` file or ``classifier`` does not exist. + RuntimeError: If the resume state in ``output`` is incomplete, or the + analysis is cancelled or one of its processes dies. Notes: - - The function ensures the BirdNET model is available before analysis. - - Analysis parameters are saved to a file in the output directory. - - Directory analyses are resumable: per-file results are journaled to - ``/.birdnet-resume/`` as they complete, an interrupted run - picks up where it left off (if the analysis parameters match), and - the journal is removed after outputs are written. When resuming a - run that had already completed every file, no new inference happens - and ``None`` is returned instead of a prediction result. + Directory analyses are resumable: per-file results are journaled to + ``/.birdnet-resume/`` as they complete, an interrupted run picks up + where it left off (if the analysis parameters match), and the journal is + removed after the outputs are written. """ import birdnet_analyzer.config as cfg from birdnet_analyzer.analyze.resume import ResumeJournal, RunMetadata @@ -124,6 +150,14 @@ def analyze( ) from birdnet_analyzer.utils import save_params_file + if classifier and model == "perch": + raise ValueError("Select either Perch or a custom classifier, not both.") + + if (lat is None) != (lon is None): + raise ValueError( + "Set both lat and lon to limit the species to a location, or neither." + ) + # Settled before the params file, result columns and resume fingerprint see it. sensitivity = effective_sensitivity(sensitivity, model) validate_min_conf(min_conf) diff --git a/birdnet_analyzer/cli.py b/birdnet_analyzer/cli.py index e66fbe988..ff24e70d6 100644 --- a/birdnet_analyzer/cli.py +++ b/birdnet_analyzer/cli.py @@ -12,9 +12,15 @@ from birdnet_analyzer.config import ( ALL_MODEL_LANGUAGES, AUTOTUNE_METRICS, + CROP_MODES, DEFAULT_ACOUSTIC_MODEL_VERSION, GEO_MODEL_LANGUAGES, + RESULT_TYPES, + SAMPLE_CROP_MODES, + SCORE_FUNCTIONS, TRAINED_MODEL_OUTPUT_FORMATS, + TRAINED_MODEL_SAVE_MODES, + UPSAMPLING_MODES, ) from birdnet_analyzer.logs import setup_logging @@ -467,9 +473,9 @@ def __call__(self, parser, namespace, values, option_string=None): parser.add_argument( "--rtype", default={"table"}, - choices=["table", "audacity", "kaleidoscope", "csv", "parquet"], + choices=get_args(RESULT_TYPES), nargs="+", - help="Specifies output format. Values in `['table', 'audacity', 'kaleidoscope', 'csv', 'parquet']`.", + help="Specifies output format.", action=UniqueSetAction, ) parser.add_argument( @@ -482,7 +488,7 @@ def __call__(self, parser, namespace, values, option_string=None): parser.add_argument( "-c", "--classifier", - help="Path to custom trained classifier. If set, --lat, --lon and --locale are ignored.", + help="Path to custom trained classifier. Its species names come from its labels file, so --locale is ignored. --lat and --lon keep only its species that the geo model predicts there, or, with a warning, all of them if it predicts none.", ) parser.add_argument( "--cc_species_list", @@ -491,7 +497,7 @@ def __call__(self, parser, namespace, values, option_string=None): parser.add_argument( "--top_n", type=lambda a: max(1, int(a)), - help="Saves only the top N predictions for each segment independent of their score. Threshold will be ignored.", + help="Saves at most the top N predictions for each segment. --min_conf still applies.", ) parser.add_argument( "--merge_consecutive", @@ -579,14 +585,14 @@ def search_parser(): parser.add_argument( "--score_function", default="cosine", - choices=["cosine", "euclidean", "dot"], - help="Scoring function to use. Choose 'cosine', 'euclidean' or 'dot'.", + choices=get_args(SCORE_FUNCTIONS), + help="Scoring function to use.", ) parser.add_argument( "--crop_mode", default="center", - choices=["center", "first", "segments"], - help="Crop mode for the query sample. Can be 'center', 'first' or 'segments'.", + choices=get_args(CROP_MODES), + help="Crop mode for the query sample.", ) parser.add_argument( @@ -659,12 +665,12 @@ def segments_parser(): def species_parser(): - """Build the argument parser for retrieving a species list for a location.""" + """Build the argument parser for retrieving a species list.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, parents=[ species_list_args(), - # The species list comes from the geo model, so only its languages apply. + # The geo model's languages, which the default acoustic model shares. locale_args(languages=GEO_MODEL_LANGUAGES), verbosity_args(), ], @@ -711,8 +717,8 @@ def train_parser(): parser.add_argument( "--crop_mode", default="center", - choices=["center", "first", "segments", "smart"], - help="Crop mode for training data. Can be 'center', 'first', 'segments' or 'smart'.", + choices=get_args(SAMPLE_CROP_MODES), + help="Crop mode for training data.", ) parser.add_argument( "--birdnet", @@ -793,7 +799,7 @@ def train_parser(): parser.add_argument( "--upsampling_mode", default="repeat", - choices=["repeat", "linear", "mean", "smote"], + choices=get_args(UPSAMPLING_MODES), help="Upsampling mode.", ) parser.add_argument( @@ -801,12 +807,12 @@ def train_parser(): nargs="+", default=["tflite"], choices=get_args(TRAINED_MODEL_OUTPUT_FORMATS), - help="Model output format(s). One or more of 'tflite', 'raven', 'detached'.", + help="Model output format(s).", ) parser.add_argument( "--model_save_mode", default="replace", - choices=["replace", "append"], + choices=get_args(TRAINED_MODEL_SAVE_MODES), help="Model save mode. 'replace' will overwrite the original classification layer and 'append' will combine the original classification layer with the new one.", ) parser.add_argument( diff --git a/birdnet_analyzer/embeddings/core.py b/birdnet_analyzer/embeddings/core.py index 3783ce1f3..204b033ee 100644 --- a/birdnet_analyzer/embeddings/core.py +++ b/birdnet_analyzer/embeddings/core.py @@ -40,7 +40,8 @@ def embeddings( Args: audio_input (str): Path to the input audio file or directory containing audio files. - database (str): Path to the database where embeddings will be stored. + database (str): Path to the database where embeddings will be stored. It is + created if it does not exist. overlap (float, optional): Overlap between consecutive audio segments in seconds. Defaults to 0.0. audio_speed (float, optional): Speed factor for audio processing. @@ -53,15 +54,16 @@ def embeddings( batch. Defaults to 1. file_output (str | None, optional): Path to save the output embeddings. If None, embeddings are not saved to a file. Defaults to None. - n_workers (int | None, optional): Number of worker threads to use for - processing. Defaults to None. - n_producers (int, optional): Number of producer threads to use for processing. - Defaults to 1. + n_workers (int | None, optional): Number of worker processes to use for + processing. Defaults to None, one per physical CPU core. + n_producers (int, optional): Number of producer processes to use for + processing. Defaults to 1. on_update (Callable[[AcousticProgressStats], None] | None, optional): Callback function to report progress updates. Defaults to None. Raises: - FileNotFoundError: If the input path or database path does not exist. - ValueError: If any of the parameters are invalid. + ValueError: If ``audio_input`` does not exist or holds no supported audio + file, or if the database was created with a different ``fmin``, ``fmax``, + ``audio_speed`` or parent folder of ``audio_input``. Example: embeddings( "path/to/audio", diff --git a/birdnet_analyzer/gui/analysis.py b/birdnet_analyzer/gui/analysis.py index 1e73af545..1b2a4a206 100644 --- a/birdnet_analyzer/gui/analysis.py +++ b/birdnet_analyzer/gui/analysis.py @@ -16,9 +16,6 @@ def on_progress(update: AcousticProgressStats, progress: gr.Progress): - if progress is None: - return - if update.processed_segments and update.total_segments: progress( (update.processed_segments, update.total_segments), @@ -65,35 +62,50 @@ def run_analysis( """Starts the analysis. Args: - input_path: Either a file or directory. - output_path: The output path for the result, if None the input_path is used + input_path: The audio file of the single file tab. If set, the predictions + are only returned, no result files are written. + output_path: The output folder; if None, the folder of the input. + use_top_n: Whether to keep the ``top_n`` highest-scoring detections per + segment instead of those above ``confidence``. + top_n: The number of detections kept per segment with ``use_top_n``. confidence: The selected minimum confidence. sensitivity: The selected sensitivity. overlap: The selected segment overlap. merge_consecutive: The number of consecutive segments to merge into one. - audio_speed: The selected audio speed. + audio_speed: The value of the audio speed slider. fmin: The selected minimum bandpass frequency. fmax: The selected maximum bandpass frequency. species_list_choice: The choice for the species list. - species_list_file: The selected custom species list file. - lat: The selected latitude. - lon: The selected longitude. + species_list_file: The species list file, used with the custom species list + choice. + lat: The latitude, used with the predicted species list choice. + lon: The longitude, used with the predicted species list choice. week: The selected week of the year. use_yearlong: Use yearlong instead of week. sf_thresh: The threshold for the predicted species list. - custom_classifier_file: Custom classifier to be used. - output_type: The type of result to be generated. + selected_model: The model choice: a BirdNET version, Perch or the custom + classifier. + custom_classifier_file: The classifier file, used with the custom classifier + choice. + output_types: The result type(s) to write. additional_columns: Additional columns to be added to the result. - output_filename: The filename for the combined output. locale: The translation to be used. batch_size: The number of samples in a batch. - n_producers: The number of producer threads to be used. - n_workers: The number of worker threads to be used. - input_dir: The input directory. + input_dir: The folder of the multi file tab, analyzed instead of + ``input_path``. + save_params: Whether to write the parameters file next to the results. + n_producers: The number of producer processes. + n_workers: The number of worker processes. progress: The gradio progress bar. split_tables: Whether to split the output into separate tables per input file. + Defaults to False. + + Returns: + The predictions returned by the analysis. + + Raises: + gr.Error: If the custom species list or custom classifier choice has no file. """ - import birdnet_analyzer.gui.localization as loc from birdnet_analyzer.analyze import analyze if species_list_choice == gu.CUSTOM_SPECIES: diff --git a/birdnet_analyzer/model.py b/birdnet_analyzer/model.py index 65f283770..ab7c2a864 100644 --- a/birdnet_analyzer/model.py +++ b/birdnet_analyzer/model.py @@ -741,10 +741,12 @@ def save_raven_model( labels (list[str]): A list of labels associated with the classifier. mode (str, optional): The mode for saving the model. Can be either "replace" or "append". Defaults to "replace". - version: The BirdNET version the classifier was trained on. - - Raises: - ValueError: If the mode is not "replace" or "append". + sig_fmin (int, optional): Lowest frequency in Hz written for every class to + "classes/classes.csv". Defaults to 0. + sig_fmax (int, optional): Highest frequency in Hz written for every class to + "classes/classes.csv". Defaults to 15000. + version: The BirdNET version the classifier was trained on. Defaults to the + newest version. Returns: None diff --git a/birdnet_analyzer/model_utils.py b/birdnet_analyzer/model_utils.py index 5d26da379..146515ca6 100644 --- a/birdnet_analyzer/model_utils.py +++ b/birdnet_analyzer/model_utils.py @@ -109,7 +109,8 @@ def _reconcile_species_list( For a user file, species the model does not know are reported - skipped with a warning, or raised when ``strict`` - because the user expects every entry to count. A geo-derived collection is broader than the acoustic model on purpose (it - can include non-birds), so its unmatched species are filtered out silently. + can include non-birds), so its unmatched species are filtered out silently, unless + none match: then a warning is logged. """ from pathlib import Path @@ -127,6 +128,12 @@ def _reconcile_species_list( matched, unmatched = match_species_to_model(requested, model_species) + if not is_user_file and not matched: + logger.warning( + "The location or species list names no species of the model, so the " + "analysis is not filtered by species." + ) + if is_user_file and unmatched: listing = "\n ".join(sorted(unmatched)) if not matched: diff --git a/birdnet_analyzer/segments/core.py b/birdnet_analyzer/segments/core.py index 8ebcd8a5f..a5d6b8561 100644 --- a/birdnet_analyzer/segments/core.py +++ b/birdnet_analyzer/segments/core.py @@ -52,18 +52,18 @@ def segments( confidence: Collects the segments with highest confidence. balanced: Collects segments with a balanced distribution of confidence levels. + Defaults to "random". n_bins (int, optional): Number of bins for confidence distribution when using - the "balanced" collection mode. + the "balanced" collection mode. Defaults to 10. + on_update (Callable[[tuple[int, int]], None] | None, optional): Called with + ``(i, n_files)`` as the audio files are processed. Defaults to None. Returns: - list[tuple[str, bool]]: A list of tuples containing the path to the extracted - segment and a boolean indicating whether the extraction was successful. + list[tuple[str, bool]]: A list of tuples containing the path to an audio file + and a boolean indicating whether its segments were extracted successfully. Notes: - The function uses multiprocessing for parallel processing if `threads` is greater than 1. - - On Windows, due to the lack of `fork()` support, configuration items are - passed to each process explicitly. - - It is recommended to use this function on Linux for better performance. """ import concurrent.futures diff --git a/birdnet_analyzer/segments/utils.py b/birdnet_analyzer/segments/utils.py index b4730ad76..68be248b8 100644 --- a/birdnet_analyzer/segments/utils.py +++ b/birdnet_analyzer/segments/utils.py @@ -187,7 +187,7 @@ def parse_files( {"audio": "path/to/audio1.wav", "result": "path/to/result1.csv"}, {"audio": "path/to/audio2.wav", "result": "path/to/result2.csv"} ] - segments = parseFiles(flist, max_segments=50) + segments = parse_files(flist, max_segments=50) """ species_segments: dict[str, list] = {} is_combined_rfile = len(flist) == 1 and flist[0].get("isCombinedFile", False) diff --git a/birdnet_analyzer/species/core.py b/birdnet_analyzer/species/core.py index 348178743..853d34485 100644 --- a/birdnet_analyzer/species/core.py +++ b/birdnet_analyzer/species/core.py @@ -17,37 +17,47 @@ def species( locale: MODEL_LANGUAGES = "en_us", ): """ - Retrieves and processes species data based on the provided parameters. + Writes a species list file, one label per line. Args: - output (str): The output directory or file path where the results will be - stored. - lat (float | None, optional): Latitude of the location for species filtering. - Defaults to None (no filtering by location). - lon (float | None, optional): Longitude of the location for species filtering. - Defaults to None (no filtering by location). - week (int | None, optional): Week of the year for species filtering. - Defaults to None (no filtering by time). - sf_thresh (float, optional): Species frequency threshold for filtering. - Defaults to 0.03. - locale (MODEL_LANGUAGES, optional): Locale for species names. + output (str): Path of the list file, or a folder to write "species_list.txt" + into. + lat (float | None, optional): Latitude of the location. Together with + ``lon``, the list holds the species the geo + model predicts there; with neither, every + species of the default acoustic model. + Defaults to None. + lon (float | None, optional): Longitude of the location. Defaults to None. + week (int | None, optional): Week of the year (1-48) for the geo model, + ignored without a location. Defaults to None, + the year-round list. + sf_thresh (float, optional): Minimum occurrence frequency of a species in the + geo model's prediction, ignored without a + location. Defaults to 0.03. + locale (MODEL_LANGUAGES, optional): Language of the species names. Defaults to "en_us". Raises: - FileNotFoundError: If the required model files are not found. - ValueError: If invalid parameters are provided. + ValueError: If only one of ``lat`` and ``lon`` is given, or ``lat``, ``lon`` + or ``week`` is out of range. Notes: - This function ensures that the required model files exist before processing. - It delegates the main processing to the `run` function - from `birdnet_analyzer.species.utils`. + The geo model, or without a location the acoustic model, is downloaded on + first use. """ - from birdnet_analyzer.species.utils import get_species_list - - species_list = get_species_list( - lat=-1 if lat is None else lat, - lon=-1 if lon is None else lon, - week=week, - threshold=sf_thresh, - lang=locale, - ) + if lat is None and lon is None: + from birdnet_analyzer.config import DEFAULT_ACOUSTIC_MODEL_VERSION + from birdnet_analyzer.model_utils import acoustic_species_list + + species_list = acoustic_species_list(DEFAULT_ACOUSTIC_MODEL_VERSION, locale) + elif lat is None or lon is None: + raise ValueError( + "Set both lat and lon for the species list of a location, or neither for " + "every species of the model." + ) + else: + from birdnet_analyzer.species.utils import get_species_list + + species_list = get_species_list( + lat=lat, lon=lon, week=week, threshold=sf_thresh, lang=locale + ) if os.path.isdir(output): output = os.path.join(output, "species_list.txt") diff --git a/birdnet_analyzer/species/utils.py b/birdnet_analyzer/species/utils.py index 3260e58e0..7270148e7 100644 --- a/birdnet_analyzer/species/utils.py +++ b/birdnet_analyzer/species/utils.py @@ -17,20 +17,19 @@ def get_species_list( lat: float, lon: float, week: int | None, threshold: float, lang: MODEL_LANGUAGES ) -> list[str]: """ - Generates a species list for a given location and time, and saves it to the - specified output path. + Generates a species list for a given location and time. Args: lat (float): Latitude of the location. lon (float): Longitude of the location. - week (int | None): Week of the year (1-52) for which the species list is + week (int | None): Week of the year (1-48) for which the species list is generated. If None, all weeks are considered. threshold (float): Threshold for location filtering. lang (MODEL_LANGUAGES): Language code for species names. Returns: - list[str]: Species list as numpy strings. + list[str]: The species labels. """ result = model_utils.run_geomodel( lat, lon, week, threshold=threshold, language=lang ) - return [str(species) for species, prob in result.to_structured_array()] + return [str(species) for species, _ in result.to_structured_array()] diff --git a/birdnet_analyzer/train/core.py b/birdnet_analyzer/train/core.py index 79e3c3e63..5b1ccbe10 100644 --- a/birdnet_analyzer/train/core.py +++ b/birdnet_analyzer/train/core.py @@ -66,7 +66,8 @@ def train( Defaults to "checkpoints/custom/Custom_Classifier". crop_mode (Literal["center", "first", "segments", "smart"], optional): Mode for cropping audio samples. Defaults to "center". - overlap (float, optional): Overlap ratio for audio segments. Defaults to 0.0. + overlap (float, optional): Overlap of the segments in seconds for the + "segments" and "smart" crop modes. Defaults to 0.0. epochs (int, optional): Number of training epochs. Defaults to 50. batch_size (int, optional): Batch size for training. Defaults to 32. val_split (float, optional): Fraction of data to use for validation. @@ -90,7 +91,8 @@ def train( upsampling_mode (Literal["repeat", "linear", "mean", "smote"], optional): Mode for upsampling. Defaults to "repeat". model_formats (list[Literal["tflite", "raven", "detached"]] | str, optional): - One or more formats to save the trained model. Defaults to "tflite". + One or more formats to save the trained model; "detached" is the + classifier alone, taking embeddings. Defaults to "tflite". model_save_mode (Literal["replace", "append"], optional): Save mode for the model. Defaults to "replace". save_cache_to (str | None, optional): Path to save the cache file. @@ -112,8 +114,6 @@ def train( autotune_metric (Literal["val_AUPRC", "val_AUROC"], optional): Metric to optimize during hyperparameter tuning. Training also stops early on it and keeps the weights of its best epoch. Defaults to "val_AUPRC". - save_detached_classifier (bool, optional): Whether to additionally save a - detached version of the trained classifier. Defaults to False. Returns: None """ diff --git a/birdnet_analyzer/train/utils.py b/birdnet_analyzer/train/utils.py index c24f8a4ef..c4ab649ab 100644 --- a/birdnet_analyzer/train/utils.py +++ b/birdnet_analyzer/train/utils.py @@ -636,18 +636,61 @@ def train_model( """Trains a custom classifier. Args: + audio_input: Training data folder, whose subfolder names are the labels, or a + cache file. + output: Path of the trained classifier. Defaults to + "checkpoints/custom/Custom_Classifier". + test_data: Test data folder, only used to evaluate the trained classifier. + Defaults to None. + crop_mode: "center", "first", "segments" or "smart". Defaults to "center". + overlap: Overlap of the segments in seconds for the "segments" and "smart" + crop modes. Defaults to 0.0. + epochs: Maximum number of epochs. Defaults to 50. + batch_size: Training batch size. Defaults to 32. + val_split: Fraction of the training data used for validation, above 0. + Defaults to 0.2. + learning_rate: Learning rate. Defaults to 0.0001. + weight_decay: AdamW weight decay. Defaults to 0.004. + use_focal_loss: Whether to use focal loss. Defaults to False. + focal_loss_gamma: Focal loss gamma. Defaults to 2.0. + focal_loss_alpha: Focal loss alpha. Defaults to 0.25. + hidden_units: Hidden units; above 0 the classifier has two layers. + Defaults to 0. + dropout: Dropout rate. Defaults to 0.0. + label_smoothing: Whether to use label smoothing. Defaults to False. + mixup: Whether to use mixup. Defaults to False. + upsampling_ratio: Ratio for upsampling minority classes. Defaults to 0.0. + upsampling_mode: "repeat", "linear", "mean" or "smote". Defaults to "repeat". + model_formats: One or more of "tflite", "raven" and "detached" (the + classifier alone, taking embeddings). Defaults to "tflite". + model_save_mode: "replace" the base model's classes or "append" to them. + Defaults to "replace". + save_cache_to: Path of a cache file to save the training data to. + Defaults to None. + threads: Number of threads decoding the training audio. Defaults to 1. + fmin: Lower bandpass frequency in Hz. Defaults to 0.0. + fmax: Upper bandpass frequency in Hz. Defaults to 15000.0. + audio_speed: Speed factor applied to the audio. Defaults to 1.0. + autotune: Whether to tune the hyperparameters first. Defaults to False. + autotune_trials: Number of tuning trials. Defaults to 50. + autotune_n_splits: Cross-validation folds per trial; 1 uses the validation + split. Defaults to 1. + autotune_n_repeats: Repetitions of the folds per trial. Defaults to 1. + autotune_metric: "val_AUPRC" or "val_AUROC", optimized by the tuning and the + early stopping. Defaults to "val_AUPRC". birdnet: The BirdNET version to train on. Its embeddings are the classifier's input, so the classifier only works with that version. A cache file keeps - the version it was embedded with. - on_epoch_end: A callback function that takes two arguments `epoch`, `logs`. - on_trial_result: A callback function for hyperparameter tuning. - on_data_load_end: A callback function for data loading progress. - autotune_directory: Directory for autotune results. - save_detached_classifier: Whether to additionally save a detached version of - the trained classifier. + the version it was embedded with. Defaults to the newest version. + on_epoch_end: Called with ``(epoch, logs)`` after each epoch of the final + training. Defaults to None. + on_trial_result: Called with the number of finished tuning trials, 0 before + the first. Defaults to None. + on_data_load_end: Called with ``(files_done, files_total, folder)`` for each + loaded file of a training or test data folder. Defaults to None. Returns: - A keras `History` object, whose `history` property contains all the metrics. + A tuple ``(history, metrics)``: the keras ``History`` of the final training, + and a dict of metrics on ``test_data``, or None without test data. """ if autotune_metric not in get_args(AUTOTUNE_METRICS): raise ValueError( diff --git a/docs/breaking-changes.rst b/docs/breaking-changes.rst index fba11adbc..2661a2233 100644 --- a/docs/breaking-changes.rst +++ b/docs/breaking-changes.rst @@ -15,21 +15,22 @@ release (**v2.4.0**) and what to do about it. Default model is now BirdNET 3.0 -------------------------------- -**What changed.** Analyses use the BirdNET 3.0 acoustic model, and location-based -species lists use the 3.0 geolocation model, by default. v2.4 was the previous default. +**What changed.** Analyses use the BirdNET 3.0 acoustic model by default, and +location-based species lists always use the 3.0 geolocation model. v2.4 was the +previous default for both. **Consequence.** Results are not directly comparable to v2.4 output: 3.0 is a different model with different confidence scores, a larger species set (11,560 labels) and an updated taxonomy. Do not mix or compare 3.0 result files with v2.4 ones. -**What to do.** To keep the previous behaviour, select the 2.4 model explicitly: +**What to do.** To keep the previous acoustic model, select the 2.4 model explicitly: .. code-block:: bash birdnet-analyze INPUT --birdnet 2.4 In the GUI, choose *BirdNET 2.4* under model selection. The 2.4 model remains fully -supported. +supported. Location-based species lists come from the 3.0 geolocation model either way. Inference runs on ONNX, not TensorFlow, on the default path @@ -68,11 +69,11 @@ automatically. **Consequence.** * Species that still cannot be matched (genuinely absent from the model, or a typo) - are **skipped with a warning**, and the analysis proceeds with the rest. -* If **none** of the list's species can be matched, the analysis **errors** instead of - silently analysing every species. -* Previously, an unknown entry in a ``--slist`` file aborted the run with a library - error; most such lists now succeed after reconciliation. + are **skipped with a warning**; v2.4.0 skipped them silently. +* If **none** of the list's species can be matched, the analysis **errors**; v2.4.0 + produced no detections. +* ``--slist`` together with ``--lat`` and ``--lon`` is an error; v2.4.0 ignored the + list. **What to do.** Legacy lists generally work unchanged. To require that every entry match — turning any unmatched species into a hard error — use the new ``--strict`` @@ -86,26 +87,27 @@ In the GUI, selecting a species-list file shows a warning if it contains species chosen model does not have (only once that model has been downloaded). -Label languages differ between 2.4 and 3.0 ------------------------------------------- +Label languages +--------------- -**What changed.** The set of label languages (``--locale``) is not identical across -model versions. Nine languages available in 2.4 are **not** in 3.0: +**What changed.** The ``--locale`` codes depend on the model version. Compared with +v2.4.0: - ``af``, ``ar``, ``en_uk``, ``hu``, ``it``, ``ko``, ``ro``, ``sl``, ``th`` +* ``en``, ``el``, ``he``, ``in``, ``is``, ``ml`` and ``pt_BR`` are no longer accepted. + English is ``en_us``, the new default. +* The 3.0 model has no ``af``, ``ar``, ``en_uk``, ``hu``, ``it``, ``ko``, ``ro``, + ``sl`` or ``th``. +* ``bg``, ``ca``, ``hr``, ``lt``, ``pt_pt`` and ``sr`` are only available with the 3.0 + model, no longer with 2.4. +* New with 3.0: ``cy``, ``es_ec``, ``es_es``, ``es_mx`` and ``fa``. -Eleven languages are **new** in 3.0: +**Consequence.** The command line rejects a code it does not know. A known code the +selected model version lacks falls back to ``en_us``, with a warning. For example, +``--locale it`` with the default 3.0 model produces English labels. - ``bg``, ``ca``, ``cy``, ``es_ec``, ``es_es``, ``es_mx``, ``fa``, ``hr``, ``lt``, - ``pt_pt``, ``sr`` - -**Consequence.** Requesting a locale the selected model version does not support falls -back to ``en_us``, with a warning. For example, ``--locale it`` with the default 3.0 -model produces English labels. - -**What to do.** Use a locale the model version supports, or switch to the 2.4 model if -you need one of its dropped languages. In the GUI the locale dropdown offers only the -selected model's languages. +**What to do.** Use a locale the model version supports, or switch model versions for +one only the other has. In the GUI the locale dropdown offers only the selected model's +languages. Training defaults to the 3.0 base @@ -131,22 +133,200 @@ explicit ``-o/--output`` to pin a location. The new default is writable on a pip-installed package and survives upgrades. -Resuming an interrupted analysis re-checks changed files --------------------------------------------------------- +Training validates on the training data +--------------------------------------- + +**What changed.** ``--test_data`` now only evaluates the trained classifier. A +validation split (``--val_split``, which must be above 0) is always taken from the +training data, and early stopping watches ``--autotune_metric`` (AUPRC by default) +instead of the validation loss. The AUPRC and AUROC reported during training no longer +apply the sigmoid twice, and ``--label_smoothing`` uses the standard formula. + +**Consequence.** With ``--test_data``, the classifier trains on the training data minus +the validation split; v2.4.0 trained on all of it and validated on the test data. The +same command trains a different classifier than v2.4.0, and its validation scores are +not comparable. + +**What to do.** Choose ``--val_split`` for the share of the training data to hold out; +``--val_split 0`` is rejected. Compare scores only between runs of this version. + + +Locations and species lists +--------------------------- + +**What changed.** + +* ``-1`` no longer means "not set". ``--lat -1 --lon -1`` is a location in the Gulf of + Guinea, and ``--week -1`` with a location is an error. Leave an option out instead. +* ``analyze`` and ``species`` reject only one of ``--lat`` and ``--lon``; v2.4.0 used + -1 for the missing one. +* ``birdnet_analyzer.species`` without a location writes every species of the default + acoustic model (11,560 for BirdNET 3.0), in the model's order. v2.4.0 wrote the few + species the geo model predicted for latitude and longitude -1. +* Lists for a location come from the 3.0 geolocation model and include species the + acoustic model does not have, such as insects. They are sorted alphabetically; v2.4.0 + used the acoustic model's labels and sorted them by occurrence frequency. + +**Consequence.** Scripts that pass -1 get a filter for the wrong place or an error. +Using a location list as ``--slist`` logs the species the model lacks as skipped, and +fails with ``--strict``. Lists without a location are much longer, and the first such +run downloads the acoustic model. + +**What to do.** Omit ``--lat``, ``--lon`` and ``--week`` rather than passing -1, and +drop ``--strict`` for lists made by ``birdnet_analyzer.species``. + + +Top-N results keep the confidence threshold +------------------------------------------- + +**What changed.** ``--top_n`` keeps at most N detections per segment among those above +``--min_conf``. v2.4.0 ignored the threshold when ``--top_n`` was set. + +**Consequence.** The same command returns fewer detections than in v2.4.0. + +**What to do.** Pass a low threshold, such as ``--min_conf 0.00001``, to get the top N +regardless of their score. + + +Results are written to combined files by default +------------------------------------------------ + +**What changed.** An analysis writes one combined result file per result type, such as +``BirdNET_SelectionTable.txt``. v2.4.0 wrote one file per audio file and added +combined files only with ``--combine_results``. + +**Consequence.** Scripts that expect a ``