SB4FZEB6ZLUHQNM3M76OGNNJY6THOF55S6JO6Q7IGXWE7OA7INFAC PNAXYHCI5CUWXLZPF5SCODY5X6OIMSHR45O2LJIF6ICSBANTUXZQC TTJZWSWFENRMFQGSEEC4DKCLPFTDWJR5XHUGKEE34HT2RDGJV52AC TKGASXMX2K7TH7H4PECN7LX2AYAGHYDHOVY7WFBEHFUTNKPX3MWAC DBOROCRFD6A5SJBMFYFEJI5S5M77X4EFEK6KDQWA5QDMQJKIHRWQC GQ2FYLG2VGDDKQ7TJTCFM2XPLJOAX2N737OZWPRCNKSFIH7YJ6EQC 7SMHQHQGGCPR44NNBHAELM46EOREVGRF32YB66FYZALQSDRC3CJQC GQNMVJQBC6DRV5XGK3K5L7YWG2GJUXR7EQE3OHNW72XK6BFY3AHQC 5QRZQIBFN5KJZ4PZO22POEKLUVYYQ436NAVKGNQOMPIWKQX7CZFAC 2TDG53JBZHZA6ZPYONPINKVDV4UXLP4T4CI5C2MEZIIYO7DQE5RAC 5KIKDA72HM6JFIPKOWGLM2EO7D5PTSK7WEVYV3YZWGMG3M34PJXQC {Species: "Kiwi", Filter: "model-1.0", CallType: "Duet"},{Species: "Morepork", Filter: "model-2.0", CallType: ""},
{Species: "Kiwi", Filter: "model-1.0", CallType: "Duet", Certainty: 70},{Species: "Morepork", Filter: "model-2.0", CallType: "", Certainty: 100},
{"no filters", "", "", "", true},{"filter only match", "model-1.0", "", "", true},{"filter only no match", "model-3.0", "", "", false},{"species only match", "", "Kiwi", "", true},{"species only no match", "", "Tomtit", "", false},{"calltype only match", "", "", "Duet", true},{"calltype only no match", "", "", "Male", false},{"filter+species match", "model-1.0", "Kiwi", "", true},{"filter+species+calltype match", "model-1.0", "Kiwi", "Duet", true},{"filter match species miss", "model-1.0", "Morepork", "", false},{"all miss", "model-3.0", "Tomtit", "Male", false},
{"no filters", "", "", "", -1, true},{"filter only match", "model-1.0", "", "", -1, true},{"filter only no match", "model-3.0", "", "", -1, false},{"species only match", "", "Kiwi", "", -1, true},{"species only no match", "", "Tomtit", "", -1, false},{"calltype only match", "", "", "Duet", -1, true},{"calltype only no match", "", "", "Male", -1, false},{"certainty match", "", "", "", 70, true},{"certainty no match", "", "", "", 80, false},{"certainty 100 match", "", "", "", 100, true},{"filter+species match", "model-1.0", "Kiwi", "", -1, true},{"filter+species+calltype match", "model-1.0", "Kiwi", "Duet", -1, true},{"filter+species+calltype+certainty match", "model-1.0", "Kiwi", "Duet", 70, true},{"filter+species+calltype certainty miss", "model-1.0", "Kiwi", "Duet", 100, false},{"filter match species miss", "model-1.0", "Morepork", "", -1, false},{"all miss", "model-3.0", "Tomtit", "Male", -1, false},
// All non-empty parameters must match for a label to be considered a match.func (s *Segment) SegmentMatchesFilters(filter, species, callType string) bool {if filter == "" && species == "" && callType == "" {
// All non-empty/non-negative parameters must match for a label to be considered a match.// Use certainty=-1 to indicate no certainty filtering (since 0 is a valid certainty value).func (s *Segment) SegmentMatchesFilters(filter, species, callType string, certainty int) bool {if filter == "" && species == "" && callType == "" && certainty < 0 {
File string `json:"file" jsonschema:"Path to .data file (required if no folder)"`Folder string `json:"folder" jsonschema:"Path to folder containing .data files (required if no file)"`Output string `json:"output" jsonschema:"required,Output folder for generated clips"`Prefix string `json:"prefix" jsonschema:"required,Prefix for output filenames"`Filter string `json:"filter" jsonschema:"Filter by ML model name"`Species string `json:"species" jsonschema:"Filter by species, optionally with calltype (e.g. Kiwi, Kiwi+Duet)"`Size int `json:"size" jsonschema:"Spectrogram image size in pixels (224-896, default 224)"`Color bool `json:"color" jsonschema:"Apply L4 colormap to spectrogram (default: grayscale)"`
File string `json:"file" jsonschema:"Path to .data file (required if no folder)"`Folder string `json:"folder" jsonschema:"Path to folder containing .data files (required if no file)"`Output string `json:"output" jsonschema:"required,Output folder for generated clips"`Prefix string `json:"prefix" jsonschema:"required,Prefix for output filenames"`Filter string `json:"filter" jsonschema:"Filter by ML model name"`Species string `json:"species" jsonschema:"Filter by species, optionally with calltype (e.g. Kiwi, Kiwi+Duet)"`Certainty int `json:"certainty" jsonschema:"Filter by certainty value (0-100, -1 for no filter)"`Size int `json:"size" jsonschema:"Spectrogram image size in pixels (224-896, default 224)"`Color bool `json:"color" jsonschema:"Apply L4 colormap to spectrogram (default: grayscale)"`
clips, errs := processFile(dataPath, input.Output, input.Prefix, input.Filter, speciesName, callType, imgSize, input.Color)
clips, errs := processFile(dataPath, input.Output, input.Prefix, input.Filter, speciesName, callType, input.Certainty, imgSize, input.Color)
func processFile(dataPath, outputDir, prefix, filter, speciesName, callType string, imgSize int, color bool) ([]string, []string) {
func processFile(dataPath, outputDir, prefix, filter, speciesName, callType string, certainty, imgSize int, color bool) ([]string, []string) {
}}func TestCertaintyFiltering(t *testing.T) {// Create test data files with different certainty levelsdf := &utils.DataFile{FilePath: "/test/file1.data",Segments: []*utils.Segment{{StartTime: 0,EndTime: 10,Labels: []*utils.Label{{Species: "Kiwi", Filter: "model-1.0", Certainty: 70},},},{StartTime: 10,EndTime: 20,Labels: []*utils.Label{{Species: "Kiwi", Filter: "model-1.0", Certainty: 100},},},{StartTime: 20,EndTime: 30,Labels: []*utils.Label{{Species: "Tomtit", Filter: "model-1.0", Certainty: 70},},},},}// Test 1: Filter by certainty 70 - should get 2 segmentsstate1 := &ClassifyState{Config: ClassifyConfig{Certainty: 70},DataFiles: []*utils.DataFile{df},}if got := state1.TotalSegments(); got != 2 {t.Errorf("Certainty=70: expected 2 segments, got %d", got)}// Test 2: Filter by certainty 100 - should get 1 segmentstate2 := &ClassifyState{Config: ClassifyConfig{Certainty: 100},DataFiles: []*utils.DataFile{df},}if got := state2.TotalSegments(); got != 1 {t.Errorf("Certainty=100: expected 1 segment, got %d", got)}// Test 3: Filter by certainty 0 - should get 0 segmentsstate3 := &ClassifyState{Config: ClassifyConfig{Certainty: 0},DataFiles: []*utils.DataFile{df},}if got := state3.TotalSegments(); got != 0 {t.Errorf("Certainty=0: expected 0 segments, got %d", got)}// Test 4: Combined species + certaintystate4 := &ClassifyState{Config: ClassifyConfig{Species: "Kiwi", Certainty: 70},DataFiles: []*utils.DataFile{df},
i += 2case "--certainty":if i+1 >= len(args) {fmt.Fprintf(os.Stderr, "Error: --certainty requires a value\n")os.Exit(1)}v, err := strconv.Atoi(args[i+1])if err != nil {fmt.Fprintf(os.Stderr, "Error: --certainty must be an integer\n")os.Exit(1)}if v < 0 || v > 100 {fmt.Fprintf(os.Stderr, "Error: --certainty must be between 0 and 100\n")os.Exit(1)}certainty = v
File: file,Folder: folder,Output: output,Prefix: prefix,Filter: filter,Species: species,Size: size,Color: color,
File: file,Folder: folder,Output: output,Prefix: prefix,Filter: filter,Species: species,Certainty: certainty,Size: size,Color: color,
i += 2case "--certainty":if i+1 >= len(args) {fmt.Fprintf(os.Stderr, "Error: --certainty requires a value\n")os.Exit(1)}v, err := strconv.Atoi(args[i+1])if err != nil {fmt.Fprintf(os.Stderr, "Error: --certainty must be an integer\n")os.Exit(1)}if v < 0 || v > 100 {fmt.Fprintf(os.Stderr, "Error: --certainty must be between 0 and 100\n")os.Exit(1)}certainty = v