-
Notifications
You must be signed in to change notification settings - Fork 5
Support re-processing detections and skipping localizer #815
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
Changes from 41 commits
8aad275
61b45a4
4a03c7e
09d7dfb
996674e
ce973fc
bf7178d
41efa42
78babeb
8d28d01
bb22514
1dbc5f0
fe1a9f4
7747f3a
1978cbe
82ac82d
7d733f9
07d61d9
d129029
76ce2d8
035b952
1230386
45dbacf
7361fb2
3f722c8
075a7ec
85c676d
cbd7ae0
7d15ffb
c2881b4
14396ba
6613366
2cf0c0a
1dbf3b1
c82c076
4643910
52f6964
635e671
d89f428
fb874c4
f2ef5ff
b6ce90f
8fe8b1d
d0f4f26
fc8470d
3d3b820
5c7af56
e7e579e
cb74eac
0a80074
015cb57
57de883
5ebe896
789305c
9dab8a5
63e1517
da35e16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -38,7 +38,9 @@ | |||||||||||||||||||||||||
from ami.ml.models.algorithm import Algorithm, AlgorithmCategoryMap | ||||||||||||||||||||||||||
from ami.ml.schemas import ( | ||||||||||||||||||||||||||
AlgorithmConfigResponse, | ||||||||||||||||||||||||||
AlgorithmReference, | ||||||||||||||||||||||||||
ClassificationResponse, | ||||||||||||||||||||||||||
DetectionRequest, | ||||||||||||||||||||||||||
DetectionResponse, | ||||||||||||||||||||||||||
PipelineRequest, | ||||||||||||||||||||||||||
PipelineRequestConfigParameters, | ||||||||||||||||||||||||||
|
@@ -61,6 +63,7 @@ def filter_processed_images( | |||||||||||||||||||||||||
Return only images that need to be processed by a given pipeline. | ||||||||||||||||||||||||||
An image needs processing if: | ||||||||||||||||||||||||||
1. It has no detections from the pipeline's detection algorithm | ||||||||||||||||||||||||||
or | ||||||||||||||||||||||||||
2. It has detections but they don't have classifications from all the pipeline's classification algorithms | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
pipeline_algorithms = pipeline.algorithms.all() | ||||||||||||||||||||||||||
|
@@ -191,25 +194,46 @@ def process_images( | |||||||||||||||||||||||||
task_logger.info(f"Sending {len(images)} images to Pipeline {pipeline}") | ||||||||||||||||||||||||||
urls = [source_image.public_url() for source_image in images if source_image.public_url()] | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
source_images = [ | ||||||||||||||||||||||||||
SourceImageRequest( | ||||||||||||||||||||||||||
id=str(source_image.pk), | ||||||||||||||||||||||||||
url=url, | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
for source_image, url in zip(images, urls) | ||||||||||||||||||||||||||
if url | ||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||
source_images: list[SourceImageRequest] = [] | ||||||||||||||||||||||||||
detection_requests: list[DetectionRequest] = [] | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
for source_image, url in zip(images, urls): | ||||||||||||||||||||||||||
if url: | ||||||||||||||||||||||||||
source_images.append( | ||||||||||||||||||||||||||
SourceImageRequest( | ||||||||||||||||||||||||||
id=str(source_image.pk), | ||||||||||||||||||||||||||
url=url, | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
# Only re-process detections created by the pipeline's detector | ||||||||||||||||||||||||||
vanessavmac marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
for detection in source_image.detections.all(): | ||||||||||||||||||||||||||
bbox = detection.get_bbox() | ||||||||||||||||||||||||||
if bbox and detection.detection_algorithm: | ||||||||||||||||||||||||||
detection_requests.append( | ||||||||||||||||||||||||||
DetectionRequest( | ||||||||||||||||||||||||||
source_image=source_images[-1], | ||||||||||||||||||||||||||
|
source_image_request = SourceImageRequest( | |
id=str(source_image.pk), | |
url=url, | |
) | |
source_image_requests.append(source_image_request) | |
# Re-process all existing detections if they exist | |
for detection in source_image.detections.all(): | |
bbox = detection.get_bbox() | |
if bbox and detection.detection_algorithm: | |
detection_requests.append( | |
DetectionRequest( | |
source_image=source_image_request, |
vanessavmac marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The query for existing detections no longer filters by detection_algorithm, which could cause incorrect detection matching when the same bbox exists from different algorithms.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is intentional, we want to classify detections from any previous detection algorithm, unless the user disables it for the pipeline run.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,7 +158,7 @@ def test_created_category_maps(self): | |
|
||
def test_alignment_of_predictions_and_category_map(self): | ||
# Ensure that the scores and labels are aligned | ||
pipeline = self.processing_service_instance.pipelines.all().get(slug="random") | ||
pipeline = self.processing_service_instance.pipelines.all().get(slug="random-detection-random-species") | ||
pipeline_response = pipeline.process_images(self.test_images, project_id=self.project.pk) | ||
results = save_results(pipeline_response, return_created=True) | ||
assert results is not None, "Expected results to be returned in a PipelineSaveResults object" | ||
|
@@ -172,7 +172,7 @@ def test_alignment_of_predictions_and_category_map(self): | |
|
||
def test_top_n_alignment(self): | ||
# Ensure that the top_n parameter works | ||
pipeline = self.processing_service_instance.pipelines.all().get(slug="random") | ||
pipeline = self.processing_service_instance.pipelines.all().get(slug="random-detection-random-species") | ||
pipeline_response = pipeline.process_images(self.test_images, project_id=self.project.pk) | ||
results = save_results(pipeline_response, return_created=True) | ||
assert results is not None, "Expecected results to be returned in a PipelineSaveResults object" | ||
|
@@ -182,6 +182,52 @@ def test_top_n_alignment(self): | |
assert classification.score == top_n[0]["score"] | ||
assert classification.taxon == top_n[0]["taxon"] | ||
|
||
def test_pipeline_reprocessing(self): | ||
""" | ||
Test that reprocessing the same images with differet pipelines does not create duplicate | ||
detections. The 2 pipelines used are a random detection + random species classifier, and a | ||
constant species classifier. | ||
""" | ||
# Process the images once | ||
pipeline = self.processing_service_instance.pipelines.all().get(slug="random-detection-random-species") | ||
pipeline_response = pipeline.process_images(self.test_images, project_id=self.project.pk) | ||
results = save_results(pipeline_response, return_created=True) | ||
assert results is not None, "Expected results to be returned in a PipelineSaveResults object" | ||
assert results.detections, "Expected detections to be returned in the results" | ||
|
||
# This particular pipeline produces 2 classifications per detection | ||
for det in results.detections: | ||
num_classifications = det.classifications.count() | ||
assert ( | ||
num_classifications == 2 | ||
), "Expected 2 classifications per detection (random species and random binary classifier)." | ||
|
||
source_images = SourceImage.objects.filter(pk__in=[image.id for image in pipeline_response.source_images]) | ||
detections = Detection.objects.filter(source_image__in=source_images).select_related( | ||
"detection_algorithm", | ||
"detection_algorithm__category_map", | ||
) | ||
assert detections.count() > 0 | ||
initial_num_detections = detections.count() | ||
|
||
# Reprocess the same images | ||
pipeline = self.processing_service_instance.pipelines.all().get(slug="constant") | ||
pipeline_response = pipeline.process_images(self.test_images, project_id=self.project.pk) | ||
|
||
reprocessed_results = save_results(pipeline_response, return_created=True) | ||
assert reprocessed_results is not None, "Expected results to be returned in a PipelineSaveResults object" | ||
assert reprocessed_results.detections, "Expected detections to be returned in the results" | ||
|
||
source_images = SourceImage.objects.filter(pk__in=[image.id for image in pipeline_response.source_images]) | ||
detections = Detection.objects.filter(source_image__in=source_images).select_related( | ||
"detection_algorithm", | ||
"detection_algorithm__category_map", | ||
) | ||
assert initial_num_detections == detections.count(), "Expected no new detections to be created." | ||
vanessavmac marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
for detection in detections: | ||
assert ( | ||
detection.classifications.count() == 3 | ||
vanessavmac marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
), "Expected 3 classifications per detection (2 random classifiers + constant classifier)." | ||
|
||
|
||
class TestPipeline(TestCase): | ||
def setUp(self): | ||
|
Uh oh!
There was an error while loading. Please reload this page.