-
Notifications
You must be signed in to change notification settings - Fork 11
Implement null detections #1093
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
base: main
Are you sure you want to change the base?
Changes from all commits
ee3bc1b
573dee5
3f06833
342c3d2
fd6d22c
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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,7 +37,7 @@ | |||||||||||||||||||||
| update_occurrence_determination, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| from ami.ml.exceptions import PipelineNotConfigured | ||||||||||||||||||||||
| from ami.ml.models.algorithm import Algorithm, AlgorithmCategoryMap | ||||||||||||||||||||||
| from ami.ml.models.algorithm import Algorithm, AlgorithmCategoryMap, AlgorithmTaskType | ||||||||||||||||||||||
| from ami.ml.schemas import ( | ||||||||||||||||||||||
| AlgorithmConfigResponse, | ||||||||||||||||||||||
| AlgorithmReference, | ||||||||||||||||||||||
|
|
@@ -406,7 +406,10 @@ def get_or_create_detection( | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| :return: A tuple of the Detection object and a boolean indicating whether it was created | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| serialized_bbox = list(detection_resp.bbox.dict().values()) | ||||||||||||||||||||||
| if detection_resp.bbox is not None: | ||||||||||||||||||||||
| serialized_bbox = list(detection_resp.bbox.dict().values()) | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| serialized_bbox = None | ||||||||||||||||||||||
| detection_repr = f"Detection {detection_resp.source_image_id} {serialized_bbox}" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| assert str(detection_resp.source_image_id) == str( | ||||||||||||||||||||||
|
|
@@ -485,6 +488,7 @@ def create_detections( | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| existing_detections: list[Detection] = [] | ||||||||||||||||||||||
| new_detections: list[Detection] = [] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for detection_resp in detections: | ||||||||||||||||||||||
| source_image = source_image_map.get(detection_resp.source_image_id) | ||||||||||||||||||||||
| if not source_image: | ||||||||||||||||||||||
|
|
@@ -810,6 +814,50 @@ class PipelineSaveResults: | |||||||||||||||||||||
| total_time: float | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def create_null_detections_for_undetected_images( | ||||||||||||||||||||||
| results: PipelineResultsResponse, | ||||||||||||||||||||||
| algorithms_known: dict[str, Algorithm], | ||||||||||||||||||||||
| logger: logging.Logger = logger, | ||||||||||||||||||||||
| ) -> list[DetectionResponse]: | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| Create null DetectionResponse objects (empty bbox) for images that have no detections. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :param results: The PipelineResultsResponse from the processing service | ||||||||||||||||||||||
| :param algorithms_known: Dictionary of algorithms keyed by algorithm key | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :return: List of DetectionResponse objects with null bbox | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| source_images_with_detections = {int(detection.source_image_id) for detection in results.detections} | ||||||||||||||||||||||
| null_detections_to_add = [] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for source_img in results.source_images: | ||||||||||||||||||||||
| if int(source_img.id) not in source_images_with_detections: | ||||||||||||||||||||||
|
Comment on lines
+830
to
+834
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Both 🛠️ Proposed fix — compare as strings- source_images_with_detections = {int(detection.source_image_id) for detection in results.detections}
+ source_images_with_detections = {str(detection.source_image_id) for detection in results.detections}
for source_img in results.source_images:
- if int(source_img.id) not in source_images_with_detections:
+ if str(source_img.id) not in source_images_with_detections:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| detector_algorithm_reference = None | ||||||||||||||||||||||
| for known_algorithm in algorithms_known.values(): | ||||||||||||||||||||||
| if known_algorithm.task_type == AlgorithmTaskType.DETECTION: | ||||||||||||||||||||||
| detector_algorithm_reference = AlgorithmReference( | ||||||||||||||||||||||
| name=known_algorithm.name, key=known_algorithm.key | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if detector_algorithm_reference is None: | ||||||||||||||||||||||
| logger.error( | ||||||||||||||||||||||
| f"Could not identify the detector algorithm. " | ||||||||||||||||||||||
| f"A null detection was not created for Source Image {source_img.id}" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| null_detections_to_add.append( | ||||||||||||||||||||||
| DetectionResponse( | ||||||||||||||||||||||
| source_image_id=source_img.id, | ||||||||||||||||||||||
| bbox=None, | ||||||||||||||||||||||
| algorithm=detector_algorithm_reference, | ||||||||||||||||||||||
| timestamp=now(), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return null_detections_to_add | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @celery_app.task(soft_time_limit=60 * 4, time_limit=60 * 5) | ||||||||||||||||||||||
| def save_results( | ||||||||||||||||||||||
| results: PipelineResultsResponse | None = None, | ||||||||||||||||||||||
|
|
@@ -866,6 +914,15 @@ def save_results( | |||||||||||||||||||||
| "Algorithms and category maps must be registered before processing, using /info endpoint." | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Ensure all images have detections | ||||||||||||||||||||||
vanessavmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| # if not, add a NULL detection (empty bbox) to the results | ||||||||||||||||||||||
| null_detections = create_null_detections_for_undetected_images( | ||||||||||||||||||||||
| results=results, | ||||||||||||||||||||||
| algorithms_known=algorithms_known, | ||||||||||||||||||||||
| logger=job_logger, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| results.detections = results.detections + null_detections | ||||||||||||||||||||||
|
Comment on lines
+917
to
+924
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Null detections will cause images to be re-queued on every subsequent pipeline run.
elif existing_detections.filter(classifications__isnull=True).exists():
yield imageA null detection ( 🛠️ Proposed fix in
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| detections = create_detections( | ||||||||||||||||||||||
| detections=results.detections, | ||||||||||||||||||||||
| algorithms_known=algorithms_known, | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -104,6 +104,16 @@ export const columns: (projectId: string) => TableColumn<CaptureSet>[] = ( | |||||||||||
| <BasicTableCell value={item.numImagesWithDetectionsLabel} /> | ||||||||||||
| ), | ||||||||||||
| }, | ||||||||||||
| { | ||||||||||||
| id: 'total-processed-captures', | ||||||||||||
| name: translate(STRING.FIELD_LABEL_TOTAL_PROCESSED_CAPTURES), | ||||||||||||
| styles: { | ||||||||||||
| textAlign: TextAlign.Right, | ||||||||||||
| }, | ||||||||||||
| renderCell: (item: Collection) => ( | ||||||||||||
| <BasicTableCell value={item.numImagesProcessedLabel} /> | ||||||||||||
|
Comment on lines
+113
to
+114
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 113 uses 🐛 Proposed fix- renderCell: (item: Collection) => (
+ renderCell: (item: CaptureSet) => (
<BasicTableCell value={item.numImagesProcessedLabel} />
),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| ), | ||||||||||||
| }, | ||||||||||||
| { | ||||||||||||
| id: 'occurrences', | ||||||||||||
| name: translate(STRING.FIELD_LABEL_OCCURRENCES), | ||||||||||||
|
|
||||||||||||
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.
~Q(images__detections__bbox=None)is redundant — same issue patched elsewhere in this PR.In Django ORM,
Q(bbox__isnull=True)andQ(bbox=None)are identical forJSONField: both emitbbox IS NULL. Lines 3742 and 3743 therefore negate the exact same SQL predicate, so the second negation is a no-op. The same redundancy was already corrected inget_detections_count()as part of this PR, but was carried over to this new method.🛠️ Proposed fix
def with_source_images_with_detections_count(self): return self.annotate( source_images_with_detections_count=models.Count( "images", filter=( - models.Q(images__detections__isnull=False) - & ~models.Q(images__detections__bbox__isnull=True) - & ~models.Q(images__detections__bbox=None) - & ~models.Q(images__detections__bbox=[]) + ~models.Q(images__detections__bbox__isnull=True) + & ~models.Q(images__detections__bbox=[]) ), distinct=True, ) )Q(images__detections__isnull=False)is also redundant once~Q(bbox__isnull=True)is in place (a non-nullbboxalready implies a detection row exists).🤖 Prompt for AI Agents