Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added rudimentary feature extraction #74

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/birdnetlib/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(
custom_species_list=None,
classifier_model_path=None,
classifier_labels_path=None,
fetch_embeddings=None,
):
self.name = "Analyzer"
self.model_name = "BirdNET-Analyzer"
Expand All @@ -71,7 +72,12 @@ def __init__(
self.labels = []
self.results = []
self.custom_species_list = []


if fetch_embeddings:
self.fetch_embeddings = True
else:
self.fetch_embeddings = False

self.classifier_model_path = classifier_model_path
self.classifier_labels_path = classifier_labels_path
self.use_custom_classifier = (
Expand Down Expand Up @@ -200,10 +206,15 @@ def analyze_recording(self, recording):
start = 0
end = recording.sample_secs
results = {}
features = []
for c in recording.chunks:

if self.use_custom_classifier:
pred = self.predict_with_custom_classifier(c)[0]
elif self.fetch_embeddings == True:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MJWeldy From looking at this it appears that custom classifier functionality and the embeddings functionality are not possible at the same time. Is that correct?

I haven't used embeddings myself, so not sure if that is even relevant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll try to get to adding a test case in the next few weeks.

The licensing is fine with me. At this point I need to get my current project (which uses the older 320 length embeddings) over the line before I switch to the lib library. The way I added the feature extraction does not allow for the custom classifier and embedding extraction in the same run. But it would be fairly trivial to allow it to return both. I can think of a few niche case where someone might want both (like someone trying to visualize the embeddings for a few feature classes) while using a custom classifier head), but I think it would be pretty rare.

I did not add the embeddings to the older tf.lite model. Also, it would be nice to adapt the multiprocessing functions to allow for embeddings. My current scripts embed 1000 audio files in ~5-6 seconds.

feature = self.predict_with_custom_classifier(c)
features.append(feature)
continue
else:
pred = self.predict(c)[0]

Expand All @@ -226,7 +237,9 @@ def analyze_recording(self, recording):
end = start + recording.sample_secs

self.results = results
self.features_list = features
recording.detection_list = self.detections
recording.features_list = self.features_list

def load_model(self):
print("load model", not self.use_custom_classifier)
Expand All @@ -246,7 +259,7 @@ def load_model(self):
self.input_layer_index = self.input_details[0]["index"]

# Get classification output or feature embeddings
if self.use_custom_classifier:
if self.use_custom_classifier or self.fetch_embeddings:
self.output_layer_index = self.output_details[0]["index"] - 1
else:
self.output_layer_index = self.output_details[0]["index"]
Expand Down Expand Up @@ -299,7 +312,9 @@ def predict_with_custom_classifier(self, sample):
features = INTERPRETER.get_tensor(OUTPUT_LAYER_INDEX)

feature_vector = features

if self.fetch_embeddings:
return feature_vector

C_INTERPRETER = self.custom_interpreter
C_INPUT_LAYER_INDEX = self.custom_input_layer_index
C_OUTPUT_LAYER_INDEX = self.custom_output_layer_index
Expand Down
6 changes: 5 additions & 1 deletion src/birdnetlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
self.analyzer = analyzer
self.detections_dict = {} # Old format
self.detection_list = []
self.features_list = []
self.analyzed = False
self.week_48 = week_48
self.date = date
Expand Down Expand Up @@ -94,6 +95,9 @@ def detections(self):
qualified_detections.append(detection)

return qualified_detections
@property
def features(self):
return self.features_list

@property
def as_dict(self):
Expand Down Expand Up @@ -299,4 +303,4 @@ def as_dict(self):


if __name__ == "__main__":
pass
pass