Skip to content

Commit

Permalink
feat: details about capture counts
Browse files Browse the repository at this point in the history
  • Loading branch information
mihow committed Oct 22, 2024
1 parent 83ee975 commit 9b8d8c9
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 11 deletions.
3 changes: 3 additions & 0 deletions ami/main/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,10 @@ class Meta:
"width",
"height",
"size",
"size_display",
"detections_count",
"occurrences_count",
"taxa_count",
"detections",
]

Expand Down
11 changes: 10 additions & 1 deletion ami/main/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,12 @@ class SourceImageViewSet(DefaultViewSet):
GET /captures/1/
"""

queryset = SourceImage.objects.all()
queryset = (
SourceImage.objects.all()
.with_occurrences_count() # type: ignore
.with_taxa_count()
# .with_detections_count()
)

serializer_class = SourceImageSerializer
filterset_fields = ["event", "deployment", "deployment__project", "collections"]
Expand All @@ -385,6 +390,8 @@ class SourceImageViewSet(DefaultViewSet):
"timestamp",
"size",
"detections_count",
"occurrences_count",
"taxa_count",
"deployment__name",
"event__start",
]
Expand Down Expand Up @@ -550,6 +557,7 @@ class SourceImageCollectionViewSet(DefaultViewSet):
"method",
"source_images_count",
"source_images_with_detections_count",
"occurrences_count",
]

@action(detail=True, methods=["post"], name="populate")
Expand Down Expand Up @@ -773,6 +781,7 @@ class OccurrenceViewSet(DefaultViewSet):
"deployment",
"project",
"determination__rank",
"detections__source_image",
]
ordering_fields = [
"created_at",
Expand Down
51 changes: 51 additions & 0 deletions ami/main/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,35 @@ def delete_source_image(sender, instance, **kwargs):
instance.deployment.save()


class SourceImageQuerySet(models.QuerySet):
def with_occurrences_count(self):
return self.annotate(
occurrences_count=models.Count(
"detections__occurrence",
filter=models.Q(
detections__occurrence__determination_score__gte=settings.DEFAULT_CONFIDENCE_THRESHOLD
),
distinct=True,
)
)

def with_taxa_count(self):
return self.annotate(
taxa_count=models.Count(
"detections__occurrence__determination",
filter=models.Q(
detections__occurrence__determination_score__gte=settings.DEFAULT_CONFIDENCE_THRESHOLD
),
distinct=True,
)
)


class SourceImageManager(models.Manager):
def get_queryset(self) -> SourceImageQuerySet:
return SourceImageQuerySet(self.model, using=self._db)


@final
class SourceImage(BaseModel):
"""A single image captured during a monitoring session"""
Expand Down Expand Up @@ -1177,6 +1206,8 @@ class SourceImage(BaseModel):
detections: models.QuerySet["Detection"]
collections: models.QuerySet["SourceImageCollection"]

objects = SourceImageManager()

def __str__(self) -> str:
return f"{self.__class__.__name__} #{self.pk} {self.path}"

Expand Down Expand Up @@ -1224,6 +1255,15 @@ def public_url(self, raise_errors=False) -> str | None:
# backwards compatibility
url = public_url

def size_display(self) -> str:
"""
Return the size of the image in human-readable format.
"""
if self.size is None:
return filesizeformat(0)
else:
return filesizeformat(self.size)

def get_detections_count(self) -> int:
return self.detections.distinct().count()

Expand Down Expand Up @@ -1318,6 +1358,14 @@ def get_dimensions(self) -> tuple[int | None, int | None]:
return self.width, self.height
return None, None

def occurrences_count(self) -> int | None:
# This should always be pre-populated using queryset annotations
return None

def taxa_count(self) -> int | None:
# This should always be pre-populated using queryset annotations
return None

def update_calculated_fields(self, save=False):
if self.path and not self.timestamp:
self.timestamp = self.extract_timestamp()
Expand Down Expand Up @@ -2619,6 +2667,9 @@ def with_occurrences_count(self):
return self.annotate(
occurrences_count=models.Count(
"images__detections__occurrence",
filter=models.Q(
images__detections__occurrence__determination_score__gte=settings.DEFAULT_CONFIDENCE_THRESHOLD
),
distinct=True,
)
)
Expand Down
2 changes: 1 addition & 1 deletion ui/src/data-services/models/capture-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class CaptureDetails extends Capture {
}

get sizeLabel(): string {
return `${this._capture.size} B`
return `${this._capture.size_display}`
}

get totalCaptures(): number | undefined {
Expand Down
8 changes: 8 additions & 0 deletions ui/src/data-services/models/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ export class Capture {
return this._capture.detections_count ?? 0
}

get numOccurrences(): number {
return this._capture.occurrences_count ?? 0
}

get numTaxa(): number {
return this._capture.taxa_count ?? 0
}

get sessionId(): string | undefined {
return this._capture.event?.id
}
Expand Down
28 changes: 25 additions & 3 deletions ui/src/pages/collection-details/capture-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,42 @@ export const columns: (projectId: string) => TableColumn<Capture>[] = (
},
{
id: 'detections',
name: translate(STRING.FIELD_LABEL_DETECTIONS),
name: 'Detections',
sortField: 'detections_count',
styles: {
textAlign: TextAlign.Right,
},
renderCell: (item: Capture) => (
<BasicTableCell value={item.numDetections} />
),
},
{
id: 'occurrences',
name: 'Occurrences',
sortField: 'occurrences_count',
styles: {
textAlign: TextAlign.Right,
},
renderCell: (item: Capture) => (
<Link
to={getAppRoute({
to: APP_ROUTES.OCCURRENCES({ projectId }),
filters: { capture: item.id},
filters: { detections__source_image: item.id},
})}
>
<BasicTableCell value={item.numDetections} theme={CellTheme.Bubble} />
<BasicTableCell value={item.numOccurrences} theme={CellTheme.Bubble} />
</Link>
),
},
{
id: 'taxa',
name: 'Taxa',
sortField: 'taxa_count',
styles: {
textAlign: TextAlign.Right,
},
renderCell: (item: Capture) => (
<BasicTableCell value={item.numTaxa} />
),
},
]
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ export const CaptureDetails = ({
</span>
<span className={styles.value}>{capture.dateTimeLabel}</span>
</div>
<div>
<span className={styles.label}>
{translate(STRING.FIELD_LABEL_DETECTIONS)}
</span>
<span className={styles.value}>{capture.numDetections}</span>
</div>
<div>
<span className={styles.label}>
{translate(STRING.FIELD_LABEL_SIZE)}
Expand All @@ -75,6 +69,24 @@ export const CaptureDetails = ({
<JobControls capture={capture} />
</div>
)}
<div>
<span className={styles.label}>
{translate(STRING.FIELD_LABEL_DETECTIONS)}
</span>
<span className={styles.value}>{capture.numDetections}</span>
</div>
<div>
<span className={styles.label}>
{translate(STRING.FIELD_LABEL_OCCURRENCES)}
</span>
<span className={styles.value}>{capture.numOccurrences}</span>
</div>
<div>
<span className={styles.label}>
{translate(STRING.FIELD_LABEL_TAXA)}
</span>
<span className={styles.value}>{capture.numTaxa}</span>
</div>
</div>
</>
)
Expand Down
1 change: 1 addition & 0 deletions ui/src/utils/getAppRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type FilterType =
| 'occurrences__event'
| 'occurrence'
| 'capture'
| 'detections__source_image'
| 'taxon'
| 'timestamp'
| 'collection'
Expand Down
2 changes: 2 additions & 0 deletions ui/src/utils/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export enum STRING {
FIELD_LABEL_STARTED_AT,
FIELD_LABEL_STATUS,
FIELD_LABEL_TAXON,
FIELD_LABEL_TAXA,
FIELD_LABEL_THUMBNAIL,
FIELD_LABEL_TIME,
FIELD_LABEL_TIME_OBSERVED,
Expand Down Expand Up @@ -325,6 +326,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.FIELD_LABEL_STARTED_AT]: 'Started at',
[STRING.FIELD_LABEL_STATUS]: 'Status',
[STRING.FIELD_LABEL_TAXON]: 'Taxon',
[STRING.FIELD_LABEL_TAXA]: 'Taxa',
[STRING.FIELD_LABEL_THUMBNAIL]: 'Thumbnail',
[STRING.FIELD_LABEL_TIME]: 'Local time',
[STRING.FIELD_LABEL_TIME_OBSERVED]: 'Local time observed',
Expand Down
4 changes: 4 additions & 0 deletions ui/src/utils/useFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const AVAILABLE_FILTERS = [
label: 'Capture collection',
field: 'collection',
},
{
label: 'Capture',
field: 'detections__source_image',
},
]

export const useFilters = () => {
Expand Down

0 comments on commit 9b8d8c9

Please sign in to comment.