Skip to content
Merged
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
6 changes: 3 additions & 3 deletions client/platform/desktop/backend/serializers/coco.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ describe('COCO serializer', () => {

// --- annotation fps on videos[] ---

it('writes videos[].fps for video datasets and restores it on re-import', async () => {
it('writes videos[].annotation_fps for video datasets and restores it on re-import', async () => {
const videoMeta = {
...imageMeta,
type: 'video' as const,
Expand All @@ -327,7 +327,7 @@ describe('COCO serializer', () => {
};
await serializeFile('/output/video.coco.json', annotationSchema, videoMeta);
const out = await fs.readJSON('/output/video.coco.json');
expect(out.videos).toEqual([{ id: 1, name: 'clip', fps: 5 }]);
expect(out.videos).toEqual([{ id: 1, name: 'clip', annotation_fps: 5 }]);
expect(out.images.every((image: { video_id?: number }) => image.video_id === 1)).toBe(true);

mockfs({
Expand Down Expand Up @@ -726,7 +726,7 @@ describe('COCO serializer', () => {
});
mockfs({
'/input': {
'video.json': document([{ id: 1, name: 'clip', fps: 5 }]),
'video.json': document([{ id: 1, name: 'clip', annotation_fps: 5 }]),
'image-list.json': document(undefined),
'unusable.json': document([{ id: 1, name: 'clip', fps: 0 }]),
'not-a-number.json': document([{ id: 1, name: 'clip', fps: '5' }]),
Expand Down
6 changes: 3 additions & 3 deletions client/platform/desktop/backend/serializers/coco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ type CocoAnnotation = {
type CocoVideo = {
id: number;
name?: string;
fps?: unknown;
annotation_fps?: unknown;
};

type CocoDocument = {
Expand All @@ -254,7 +254,7 @@ type CocoDocument = {
function frameRateFromDocument(document: CocoDocument): number | undefined {
const videos = Array.isArray(document.videos) ? document.videos : [];
for (let i = 0; i < videos.length; i += 1) {
const rate = videos[i]?.fps;
const rate = videos[i]?.annotation_fps;
if (typeof rate === 'number' && Number.isFinite(rate) && rate > 0) {
return rate;
}
Expand Down Expand Up @@ -631,7 +631,7 @@ async function serializeFile(
annotations,
categories: categoryDocs,
...(emitVideo ? {
videos: [{ id: 1, name: meta.name, fps: meta.fps }],
videos: [{ id: 1, name: meta.name, annotation_fps: meta.fps }],
} : {}),
};
await fs.writeJSON(path, output, { spaces: 2 });
Expand Down
2 changes: 1 addition & 1 deletion client/platform/desktop/backend/serializers/dive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function makeEmptyAnnotationFile(): AnnotationSchema {

/**
* Annotation FPS recorded on a DIVE JSON document, if usable.
* Same rules as the VIAME CSV `fps:` header and COCO `videos[].fps`.
* Same rules as the VIAME CSV `fps:` header and COCO `videos[].annotation_fps`.
*/
function frameRateFromDocument(data: unknown): number | undefined {
if (!data || typeof data !== 'object') {
Expand Down
12 changes: 6 additions & 6 deletions docs/DataFormats.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface AnnotationSchema {
version: 2;
/**
* Annotation frame rate when present. Omitted when absent or unusable.
* Same role as the VIAME CSV `# metadata` `fps` field and COCO `videos[].fps`.
* Same role as the VIAME CSV `# metadata` `fps` field and COCO `videos[].annotation_fps`.
*/
fps?: number;
}
Expand Down Expand Up @@ -120,7 +120,7 @@ The full source [TrackData definition can be found here](https://github.com/Kitw
### Annotation frame rate (`fps`)

Optional top-level `fps` carries the dataset annotation frame rate — the same value
VIAME CSV writes in the `# metadata` header and COCO/KWCOCO records on `videos[].fps`.
VIAME CSV writes in the `# metadata` header and COCO/KWCOCO records on `videos[].annotation_fps`.

```json
{
Expand Down Expand Up @@ -208,7 +208,7 @@ This information provides the specification for an individual dataset. It consi
* Annotation frame rate is stored as dataset `fps`.
* Included in [DIVE Annotation JSON](#annotation-frame-rate-fps) as top-level `fps`.
* Included in [VIAME CSV](#dataset-metadata-in-the-header) as the `# metadata` `fps` field.
* Included in [COCO / KWCOCO](#annotation-frame-rate-videosfps) as `videos[].fps` for video datasets.
* Included in [COCO / KWCOCO](#annotation-frame-rate-videosannotation_fps) as `videos[].annotation_fps` for video datasets.
* A track type hierarchy is stored in `typeHierarchy` as a child-type to immediate-parent-type map.

For example, this configuration makes `fish` a heading-only parent (it does not need to be an
Expand Down Expand Up @@ -443,7 +443,7 @@ advertised in `info.dive_extensions`:

* `info.dive_dataset_info = { "gfishsite_id": "2024TXN012", "year": "2024", ... }`

### Annotation frame rate (`videos[].fps`)
### Annotation frame rate (`videos[].annotation_fps`)

Neither MS-COCO nor KWCOCO define a frame-rate field. On import, DIVE reads the
annotation FPS the same way VIAME writes it: a positive numeric `fps` on an entry
Expand Down Expand Up @@ -502,9 +502,9 @@ For COCO files produced by DIVE:
* DIVE writes category-aligned `prob` plus exact `dive_confidence_pairs` on each annotation.
* Re-importing that file into DIVE preserves hierarchy edges, track IDs, complete confidence
vectors, attributes, and notes.
* For video datasets, DIVE also writes `videos[].fps` (and `images[].video_id`) so annotation
* For video datasets, DIVE also writes `videos[].annotation_fps` (and `images[].video_id`) so annotation
FPS round-trips. Image-sequence exports omit `videos`. See
[Annotation frame rate (`videos[].fps`)](#annotation-frame-rate-videosfps).
[Annotation frame rate (`videos[].annotation_fps`)](#annotation-frame-rate-videosannotation_fps).

For COCO files not produced by DIVE:

Expand Down
2 changes: 1 addition & 1 deletion server/dive_server/crud_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ def _coco_json_export_text(
for feature in track_data.get('features', []):
max_frame = max(max_frame, feature.get('frame', -1))
image_filenames = {i: f'frame_{i:06d}.jpg' for i in range(max_frame + 1)}
# Annotation FPS rides on videos[].fps for video datasets only; image sequences
# Annotation FPS rides on videos[].annotation_fps for video datasets only; image sequences
# omit the table so re-import does not treat them as video.
export_fps = None
if dataset_type == constants.VideoType:
Expand Down
2 changes: 1 addition & 1 deletion server/dive_utils/serializers/dive.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def frame_rate_from_dive(data: Any) -> Optional[float]:
"""Annotation FPS recorded on a DIVE JSON document, if usable.

Same rules as the VIAME CSV ``fps:`` header and COCO ``videos[].fps``:
Same rules as the VIAME CSV ``fps:`` header and COCO ``videos[].annotation_fps``:
a finite number greater than zero. Absent or unusable values are not an error.
"""
if not isinstance(data, dict):
Expand Down
8 changes: 5 additions & 3 deletions server/dive_utils/serializers/kwcoco.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def _validate_annotation_bounds(annotations: List[dict]) -> None:


def frame_rate_from_coco(coco: Dict[str, Any]) -> Optional[float]:
"""Frame rate recorded on the video, the COCO counterpart of the CSV header's fps.
"""Rate the annotations were produced at, the counterpart of the CSV header's fps.

Neither MS-COCO nor KWCOCO define a frame rate, so this reads the field VIAME
writes on the video entry. Image-sequence documents describe no video and
Expand All @@ -250,7 +250,7 @@ def frame_rate_from_coco(coco: Dict[str, Any]) -> Optional[float]:
for video in coco.get('videos') or []:
if not isinstance(video, dict):
continue
rate = video.get('fps')
rate = video.get('annotation_fps')
if isinstance(rate, bool) or not isinstance(rate, (int, float)):
continue
if math.isfinite(rate) and rate > 0:
Expand Down Expand Up @@ -716,5 +716,7 @@ def add_category_name(name: str) -> None:
'categories': categories_doc,
}
if emit_video:
coco['videos'] = [{'id': 1, 'name': dataset_name, 'fps': float(fps)}]
coco['videos'] = [
{'id': 1, 'name': dataset_name, 'annotation_fps': float(fps)}
]
return coco
10 changes: 5 additions & 5 deletions server/tests/test_deserialize_kwcoco_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,11 @@ def test_export_dive_as_coco_omits_empty_dataset_info(datasetInfo):


def test_export_dive_as_coco_writes_video_fps():
"""Video annotation FPS lands on videos[].fps with images linked by video_id."""
"""Video annotation FPS lands on videos[].annotation_fps, images linked by video_id."""
coco = kwcoco.export_dive_as_coco(
_EXPORT_TRACKS, {0: "frame_000000.jpg"}, dataset_name="clip", fps=5
)
assert coco["videos"] == [{"id": 1, "name": "clip", "fps": 5.0}]
assert coco["videos"] == [{"id": 1, "name": "clip", "annotation_fps": 5.0}]
assert all(image.get("video_id") == 1 for image in coco["images"])
assert kwcoco.frame_rate_from_coco(coco) == 5.0

Expand Down Expand Up @@ -1224,10 +1224,10 @@ def _fps_document(videos=None):
def test_frame_rate_read_from_video():
"""The COCO counterpart of the VIAME CSV header's fps."""
assert kwcoco.frame_rate_from_coco(
_fps_document([{'id': 1, 'name': 'clip', 'fps': 5}])
_fps_document([{'id': 1, 'name': 'clip', 'annotation_fps': 5}])
) == 5.0
assert kwcoco.frame_rate_from_coco(
_fps_document([{'id': 1}, {'id': 2, 'name': 'clip', 'fps': 29.97}])
_fps_document([{'id': 1}, {'id': 2, 'name': 'clip', 'annotation_fps': 29.97}])
) == 29.97


Expand All @@ -1237,5 +1237,5 @@ def test_frame_rate_absent_or_unusable():
assert kwcoco.frame_rate_from_coco(_fps_document([])) is None
for fps in [0, -5, '5', True, float('inf'), float('nan'), None]:
assert kwcoco.frame_rate_from_coco(
_fps_document([{'id': 1, 'fps': fps}])
_fps_document([{'id': 1, 'annotation_fps': fps}])
) is None
Loading