page detection and segmentation results instead of returning all of them

/detection/runs/{id}/detections and its GeoJSON sibling returned every
persisted detection, as did the segmentation equivalents. A regional run holds
tens of thousands, and these are the endpoints the results table and the map
overlay call after every run.

They now take limit and offset, default to 2.000, and report total, limit,
offset and truncated so the complete population stays visible while what is
transferred does not. The GeoJSON responses carry the same window in a
geointel_result_window foreign member.

Rows are ordered by confidence, so a capped overlay draws the strongest
detections rather than an arbitrary slice, and the lab says how many of how
many are being shown rather than silently presenting a page as the whole run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jens
2026-08-22 15:24:46 +02:00
co-authored by Claude Opus 5
parent 5278fcd361
commit 5b3839dc89
12 changed files with 271 additions and 8 deletions
+33 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.db.session import get_db from app.db.session import get_db
@@ -118,6 +118,13 @@ def list_detection_run_detections(
dataset_id: UUID | None = None, dataset_id: UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
@@ -127,6 +134,8 @@ def list_detection_run_detections(
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
limit=limit,
offset=offset,
).model_dump() ).model_dump()
) )
@@ -140,6 +149,13 @@ def list_dataset_detections(
analysis_run_id: UUID | None = None, analysis_run_id: UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
@@ -149,6 +165,8 @@ def list_dataset_detections(
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
limit=limit,
offset=offset,
).model_dump() ).model_dump()
) )
@@ -166,11 +184,18 @@ def get_detection_run_geojson(
analysis_run_id: UUID, analysis_run_id: UUID,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
DetectionService.detections_to_geojson( DetectionService.detections_to_geojson(
db, db,
limit=limit,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
@@ -187,11 +212,18 @@ def get_dataset_detection_geojson(
analysis_run_id: UUID | None = None, analysis_run_id: UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
DetectionService.detections_to_geojson( DetectionService.detections_to_geojson(
db, db,
limit=limit,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
+34 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.db.session import get_db from app.db.session import get_db
@@ -21,6 +21,7 @@ from app.schemas import (
SegmentationRunResponse, SegmentationRunResponse,
) )
from app.services.model_registry_service import ModelRegistryService from app.services.model_registry_service import ModelRegistryService
from app.services.detection_service import DetectionService
from app.services.segmentation_service import SegmentationService from app.services.segmentation_service import SegmentationService
from app.utils.response import envelope from app.utils.response import envelope
@@ -91,11 +92,20 @@ def list_segmentation_run_outputs(
dataset_id: UUID | None = None, dataset_id: UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
SegmentationService.list_segmentations( SegmentationService.list_segmentations(
db, db,
limit=limit,
offset=offset,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
@@ -113,11 +123,20 @@ def list_dataset_segmentations(
analysis_run_id: UUID | None = None, analysis_run_id: UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
offset: int = Query(default=0, ge=0),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
SegmentationService.list_segmentations( SegmentationService.list_segmentations(
db, db,
limit=limit,
offset=offset,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
@@ -139,11 +158,18 @@ def get_segmentation_run_geojson(
analysis_run_id: UUID, analysis_run_id: UUID,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
SegmentationService.segmentations_to_geojson( SegmentationService.segmentations_to_geojson(
db, db,
limit=limit,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
@@ -160,11 +186,18 @@ def get_dataset_segmentation_geojson(
analysis_run_id: UUID | None = None, analysis_run_id: UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int = Query(
default=DetectionService.DEFAULT_RESULT_LIMIT,
ge=0,
le=50_000,
description="Maximum results to return; 0 returns everything. Highest confidence first.",
),
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> dict: ) -> dict:
return envelope( return envelope(
SegmentationService.segmentations_to_geojson( SegmentationService.segmentations_to_geojson(
db, db,
limit=limit,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
+4
View File
@@ -133,7 +133,11 @@ class DetectionRead(BaseModel):
class DetectionListResponse(BaseModel): class DetectionListResponse(BaseModel):
items: list[DetectionRead] items: list[DetectionRead]
# ``total`` is the complete population; ``items`` is one page of it.
total: int total: int
limit: int | None = None
offset: int = 0
truncated: bool = False
class YoloPreflightChecks(BaseModel): class YoloPreflightChecks(BaseModel):
+43 -3
View File
@@ -297,6 +297,8 @@ class DetectionService:
dataset_id: uuid.UUID | None = None, dataset_id: uuid.UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int | None = None,
offset: int = 0,
) -> DetectionListResponse: ) -> DetectionListResponse:
if analysis_run_id is not None: if analysis_run_id is not None:
run = db.get(AnalysisRun, analysis_run_id) run = db.get(AnalysisRun, analysis_run_id)
@@ -309,8 +311,15 @@ class DetectionService:
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
) )
items = [DetectionRead.model_validate(row) for row in rows] resolved_limit = DetectionService.DEFAULT_RESULT_LIMIT if limit is None else int(limit)
return DetectionListResponse(items=items, total=len(items)) page, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=offset)
return DetectionListResponse(
items=[DetectionRead.model_validate(row) for row in page],
total=total,
limit=resolved_limit,
offset=max(0, int(offset)),
truncated=truncated,
)
@staticmethod @staticmethod
def get_detection(db, detection_id: uuid.UUID) -> DetectionRead: def get_detection(db, detection_id: uuid.UUID) -> DetectionRead:
@@ -327,16 +336,27 @@ class DetectionService:
dataset_id: uuid.UUID | None = None, dataset_id: uuid.UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
detections = DetectionService._query_detection_rows( rows = DetectionService._query_detection_rows(
db, db,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
) )
resolved_limit = DetectionService.DEFAULT_RESULT_LIMIT if limit is None else int(limit)
# Rows arrive ranked by confidence, so a capped overlay draws the
# strongest detections rather than an arbitrary slice.
detections, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=0)
return { return {
"type": "FeatureCollection", "type": "FeatureCollection",
"geointel_result_window": {
"feature_count": len(detections),
"total_feature_count": total,
"limit": resolved_limit,
"truncated": truncated,
},
"features": [ "features": [
{ {
"type": "Feature", "type": "Feature",
@@ -738,6 +758,26 @@ class DetectionService:
) )
return dataset return dataset
# A regional run holds tens of thousands of detections; the results table
# and the map overlay both read them after every run.
DEFAULT_RESULT_LIMIT = 2_000
@staticmethod
def paginate(rows: list[Any], *, limit: int, offset: int) -> tuple[list[Any], int, bool]:
"""Slice a result population, keeping the total intact.
``limit <= 0`` means "everything", for callers that genuinely need the
whole population and know what they are asking for.
"""
total = len(rows)
start = max(0, int(offset))
if limit <= 0:
return rows[start:], total, False
page = rows[start : start + int(limit)]
# Truncated means: this page is not the whole population.
return page, total, len(page) < total
@staticmethod @staticmethod
def _query_detection_rows( def _query_detection_rows(
db, db,
+10 -1
View File
@@ -290,16 +290,25 @@ class SegmentationService:
dataset_id: uuid.UUID | None = None, dataset_id: uuid.UUID | None = None,
class_name: str | None = None, class_name: str | None = None,
min_confidence: float | None = None, min_confidence: float | None = None,
limit: int | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
segmentations = SegmentationService._query_segmentation_rows( rows = SegmentationService._query_segmentation_rows(
db, db,
analysis_run_id=analysis_run_id, analysis_run_id=analysis_run_id,
dataset_id=dataset_id, dataset_id=dataset_id,
class_name=class_name, class_name=class_name,
min_confidence=min_confidence, min_confidence=min_confidence,
) )
resolved_limit = DetectionService.DEFAULT_RESULT_LIMIT if limit is None else int(limit)
segmentations, total, truncated = DetectionService.paginate(rows, limit=resolved_limit, offset=0)
return { return {
"type": "FeatureCollection", "type": "FeatureCollection",
"geointel_result_window": {
"feature_count": len(segmentations),
"total_feature_count": total,
"limit": resolved_limit,
"truncated": truncated,
},
"features": [ "features": [
{ {
"type": "Feature", "type": "Feature",
@@ -0,0 +1,86 @@
"""Loading a run's results must not depend on the run being small.
``/detection/runs/{id}/detections`` and its GeoJSON sibling returned every
persisted detection. A regional run holds tens of thousands, so the endpoints
the map and the results table call after every run grew without bound. The
counts stay complete; what is transferred does not.
"""
from __future__ import annotations
import uuid
import pytest
from app.services.detection_service import DetectionService
class _Detection:
def __init__(self, index: int) -> None:
self.id = uuid.uuid4()
self.index = index
def _rows(count: int) -> list[_Detection]:
return [_Detection(index) for index in range(count)]
def test_a_page_is_returned_with_the_complete_total() -> None:
page, total, truncated = DetectionService.paginate(_rows(1_000), limit=100, offset=0)
assert len(page) == 100
assert total == 1_000
assert truncated is True
def test_the_offset_walks_the_population() -> None:
page, total, _ = DetectionService.paginate(_rows(10), limit=3, offset=6)
assert [row.index for row in page] == [6, 7, 8]
assert total == 10
def test_an_offset_past_the_end_yields_an_empty_page_not_an_error() -> None:
page, total, truncated = DetectionService.paginate(_rows(5), limit=10, offset=50)
assert page == []
assert total == 5
assert truncated is True
def test_a_population_inside_one_page_is_not_reported_as_truncated() -> None:
page, total, truncated = DetectionService.paginate(_rows(7), limit=100, offset=0)
assert len(page) == 7
assert total == 7
assert truncated is False
def test_a_zero_limit_returns_everything_for_callers_that_need_it() -> None:
page, total, truncated = DetectionService.paginate(_rows(2_500), limit=0, offset=0)
assert len(page) == 2_500
assert total == 2_500
assert truncated is False
def test_a_negative_offset_is_treated_as_the_start() -> None:
page, _, _ = DetectionService.paginate(_rows(4), limit=2, offset=-5)
assert [row.index for row in page] == [0, 1]
@pytest.mark.parametrize("limit", [1, 2, 3])
def test_paging_covers_the_population_exactly_once(limit: int) -> None:
rows = _rows(7)
seen: list[int] = []
offset = 0
while True:
page, total, _ = DetectionService.paginate(rows, limit=limit, offset=offset)
if not page:
break
seen.extend(row.index for row in page)
offset += limit
assert seen == list(range(7))
assert total == 7
+16
View File
@@ -1081,6 +1081,22 @@ Return vector stats (feature counts and geometry summary).
Return vector bounds and feature count. Return vector bounds and feature count.
### Detection and segmentation result windows
`GET /detection/runs/{id}/detections`, `/detection/datasets/{id}/detections`
and their segmentation equivalents accept `limit` (default 2.000, `0` for
everything) and `offset`, and return `total`, `limit`, `offset` and
`truncated`. `total` always describes the complete population; `items` is one
page of it.
The `/geojson` siblings accept `limit` and report the window in a
`geointel_result_window` foreign member. Rows are ordered by confidence, so a
capped overlay draws the strongest detections rather than an arbitrary slice.
A regional run holds tens of thousands of detections, and these are the
endpoints the results table and the map overlay call after every run; they
previously returned all of them.
### GET `/api/v1/projects/{project_id}/quality-checks/{id}/evidence/geojson` ### GET `/api/v1/projects/{project_id}/quality-checks/{id}/evidence/geojson`
Returns the reviewable geometry behind one quality check: the objects the model Returns the reviewable geometry behind one quality check: the objects the model
+4
View File
@@ -350,6 +350,8 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
detectionRuns, detectionRuns,
selectedDetectionRunId, selectedDetectionRunId,
detectionItems, detectionItems,
detectionTotal,
detectionTruncated,
detectionGeoJson, detectionGeoJson,
detectionClassFilter, detectionClassFilter,
detectionMinConfidenceFilter, detectionMinConfidenceFilter,
@@ -1233,6 +1235,8 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
detectionWorkflowStage={detectionWorkflowStage} detectionWorkflowStage={detectionWorkflowStage}
selectedDetectionRunId={selectedDetectionRunId} selectedDetectionRunId={selectedDetectionRunId}
detectionItems={detectionItems} detectionItems={detectionItems}
detectionTotal={detectionTotal}
detectionTruncated={detectionTruncated}
detectionClassFilter={detectionClassFilter} detectionClassFilter={detectionClassFilter}
detectionMinConfidenceFilter={detectionMinConfidenceFilter} detectionMinConfidenceFilter={detectionMinConfidenceFilter}
loadingDetectionResults={loadingDetectionResults} loadingDetectionResults={loadingDetectionResults}
@@ -98,6 +98,9 @@ interface DetectionLabProps {
detectionWorkflowStage: DetectionWorkflowStage detectionWorkflowStage: DetectionWorkflowStage
selectedDetectionRunId: string selectedDetectionRunId: string
detectionItems: DetectionRead[] detectionItems: DetectionRead[]
/** Complete population behind the returned page. */
detectionTotal?: number
detectionTruncated?: boolean
detectionClassFilter: string detectionClassFilter: string
detectionMinConfidenceFilter: number detectionMinConfidenceFilter: number
loadingDetectionResults: boolean loadingDetectionResults: boolean
@@ -159,6 +162,8 @@ export function DetectionLab({
detectionWorkflowStage, detectionWorkflowStage,
selectedDetectionRunId, selectedDetectionRunId,
detectionItems, detectionItems,
detectionTotal,
detectionTruncated = false,
detectionClassFilter, detectionClassFilter,
detectionMinConfidenceFilter, detectionMinConfidenceFilter,
loadingDetectionResults, loadingDetectionResults,
@@ -679,6 +684,13 @@ export function DetectionLab({
</details> : null} </details> : null}
<div className="ai-lab-results-surface" aria-label="Resultaten van de beeldanalyse"> <div className="ai-lab-results-surface" aria-label="Resultaten van de beeldanalyse">
{detectionTruncated ? (
<p className="geo-data-notice">
Deze run leverde {(detectionTotal ?? detectionItems.length).toLocaleString('nl-BE')} detecties op;
hieronder en op de kaart staan de {detectionItems.length.toLocaleString('nl-BE')} met de hoogste
zekerheid. De tellingen in de kwaliteitscontrole gebruiken de volledige run.
</p>
) : null}
<div className="panel-title-row"> <div className="panel-title-row">
<div> <div>
<h3>Gevonden objecten</h3> <h3>Gevonden objecten</h3>
@@ -109,6 +109,9 @@ export function useDetectionWorkflow({
const [detectionRuns, setDetectionRuns] = useState<DetectionRunRead[]>([]) const [detectionRuns, setDetectionRuns] = useState<DetectionRunRead[]>([])
const [selectedDetectionRunId, setSelectedDetectionRunId] = useState('') const [selectedDetectionRunId, setSelectedDetectionRunId] = useState('')
const [detectionItems, setDetectionItems] = useState<DetectionRead[]>([]) const [detectionItems, setDetectionItems] = useState<DetectionRead[]>([])
// The server returns one page; a regional run holds far more than it draws.
const [detectionTotal, setDetectionTotal] = useState(0)
const [detectionTruncated, setDetectionTruncated] = useState(false)
const [detectionGeoJson, setDetectionGeoJson] = useState<GeoJSON.FeatureCollection | null>(null) const [detectionGeoJson, setDetectionGeoJson] = useState<GeoJSON.FeatureCollection | null>(null)
const [detectionClassFilter, setDetectionClassFilter] = useState('') const [detectionClassFilter, setDetectionClassFilter] = useState('')
const [detectionMinConfidenceFilter, setDetectionMinConfidenceFilter] = useState(0) const [detectionMinConfidenceFilter, setDetectionMinConfidenceFilter] = useState(0)
@@ -200,6 +203,10 @@ export function useDetectionWorkflow({
const loadDetectionResults = async (analysisRunId = selectedDetectionRunId) => { const loadDetectionResults = async (analysisRunId = selectedDetectionRunId) => {
if (!analysisRunId) { if (!analysisRunId) {
setDetectionItems([]) setDetectionItems([])
setDetectionTotal(0)
setDetectionTruncated(false)
setDetectionTotal(0)
setDetectionTruncated(false)
setDetectionGeoJson(null) setDetectionGeoJson(null)
return return
} }
@@ -216,6 +223,8 @@ export function useDetectionWorkflow({
detectionApi.getRunGeoJson(analysisRunId, params), detectionApi.getRunGeoJson(analysisRunId, params),
]) ])
setDetectionItems(detectionsResponse.items) setDetectionItems(detectionsResponse.items)
setDetectionTotal(detectionsResponse.total)
setDetectionTruncated(Boolean(detectionsResponse.truncated))
setDetectionGeoJson(geoJsonResponse) setDetectionGeoJson(geoJsonResponse)
} catch (error) { } catch (error) {
setDetectionRunError(formatError(error, 'Failed to load detection results')) setDetectionRunError(formatError(error, 'Failed to load detection results'))
@@ -563,6 +572,8 @@ export function useDetectionWorkflow({
detectionRuns, detectionRuns,
selectedDetectionRunId, selectedDetectionRunId,
detectionItems, detectionItems,
detectionTotal,
detectionTruncated,
detectionGeoJson, detectionGeoJson,
detectionClassFilter, detectionClassFilter,
detectionMinConfidenceFilter, detectionMinConfidenceFilter,
+10 -2
View File
@@ -36,12 +36,20 @@ export const detectionApi = {
apiGet<DetectionRunRead>(`/api/v1/detection/runs/${analysisRunId}`), apiGet<DetectionRunRead>(`/api/v1/detection/runs/${analysisRunId}`),
listDetections: ( listDetections: (
analysisRunId: string, analysisRunId: string,
params: { project_id: string; dataset_id?: string | null; class_name?: string | null; min_confidence?: number | null }, params: {
project_id: string
dataset_id?: string | null
class_name?: string | null
min_confidence?: number | null
// A run holds tens of thousands of detections; the response is one page.
limit?: number | null
offset?: number | null
},
): Promise<DetectionListResponse> => ): Promise<DetectionListResponse> =>
apiGet<DetectionListResponse>(`/api/v1/detection/runs/${analysisRunId}/detections${queryString(params)}`), apiGet<DetectionListResponse>(`/api/v1/detection/runs/${analysisRunId}/detections${queryString(params)}`),
getRunGeoJson: ( getRunGeoJson: (
analysisRunId: string, analysisRunId: string,
params: { project_id: string; class_name?: string | null; min_confidence?: number | null }, params: { project_id: string; class_name?: string | null; min_confidence?: number | null; limit?: number | null },
): Promise<GeoJSON.FeatureCollection> => ): Promise<GeoJSON.FeatureCollection> =>
apiGet<GeoJSON.FeatureCollection>(`/api/v1/detection/runs/${analysisRunId}/geojson${queryString(params)}`), apiGet<GeoJSON.FeatureCollection>(`/api/v1/detection/runs/${analysisRunId}/geojson${queryString(params)}`),
compareWithReference: (analysisRunId: string, projectId: string, payload: DetectionQaRequest): Promise<DetectionQaResult> => compareWithReference: (analysisRunId: string, projectId: string, payload: DetectionQaRequest): Promise<DetectionQaResult> =>
+8
View File
@@ -1418,8 +1418,12 @@ export interface DetectionRead {
} }
export interface DetectionListResponse { export interface DetectionListResponse {
/** One page of results; `total` is the complete population. */
items: DetectionRead[] items: DetectionRead[]
total: number total: number
limit?: number | null
offset?: number
truncated?: boolean
} }
export interface DetectionQaRequest { export interface DetectionQaRequest {
@@ -1583,8 +1587,12 @@ export interface SegmentationRead {
} }
export interface SegmentationListResponse { export interface SegmentationListResponse {
/** One page of results; `total` is the complete population. */
items: SegmentationRead[] items: SegmentationRead[]
total: number total: number
limit?: number | null
offset?: number
truncated?: boolean
} }
export interface SegmentationQaRequest { export interface SegmentationQaRequest {