lightly-studio 0.4.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lightly_studio/__init__.py +12 -0
- lightly_studio/api/__init__.py +0 -0
- lightly_studio/api/app.py +131 -0
- lightly_studio/api/cache.py +77 -0
- lightly_studio/api/db_tables.py +35 -0
- lightly_studio/api/features.py +5 -0
- lightly_studio/api/routes/api/annotation.py +305 -0
- lightly_studio/api/routes/api/annotation_label.py +87 -0
- lightly_studio/api/routes/api/annotations/__init__.py +7 -0
- lightly_studio/api/routes/api/annotations/create_annotation.py +52 -0
- lightly_studio/api/routes/api/caption.py +100 -0
- lightly_studio/api/routes/api/classifier.py +384 -0
- lightly_studio/api/routes/api/dataset.py +191 -0
- lightly_studio/api/routes/api/dataset_tag.py +266 -0
- lightly_studio/api/routes/api/embeddings2d.py +90 -0
- lightly_studio/api/routes/api/exceptions.py +114 -0
- lightly_studio/api/routes/api/export.py +114 -0
- lightly_studio/api/routes/api/features.py +17 -0
- lightly_studio/api/routes/api/frame.py +241 -0
- lightly_studio/api/routes/api/image.py +155 -0
- lightly_studio/api/routes/api/metadata.py +161 -0
- lightly_studio/api/routes/api/operator.py +75 -0
- lightly_studio/api/routes/api/sample.py +103 -0
- lightly_studio/api/routes/api/selection.py +87 -0
- lightly_studio/api/routes/api/settings.py +41 -0
- lightly_studio/api/routes/api/status.py +19 -0
- lightly_studio/api/routes/api/text_embedding.py +50 -0
- lightly_studio/api/routes/api/validators.py +17 -0
- lightly_studio/api/routes/api/video.py +133 -0
- lightly_studio/api/routes/healthz.py +13 -0
- lightly_studio/api/routes/images.py +104 -0
- lightly_studio/api/routes/video_frames_media.py +116 -0
- lightly_studio/api/routes/video_media.py +223 -0
- lightly_studio/api/routes/webapp.py +51 -0
- lightly_studio/api/server.py +94 -0
- lightly_studio/core/__init__.py +0 -0
- lightly_studio/core/add_samples.py +533 -0
- lightly_studio/core/add_videos.py +294 -0
- lightly_studio/core/dataset.py +780 -0
- lightly_studio/core/dataset_query/__init__.py +14 -0
- lightly_studio/core/dataset_query/boolean_expression.py +67 -0
- lightly_studio/core/dataset_query/dataset_query.py +317 -0
- lightly_studio/core/dataset_query/field.py +113 -0
- lightly_studio/core/dataset_query/field_expression.py +79 -0
- lightly_studio/core/dataset_query/match_expression.py +23 -0
- lightly_studio/core/dataset_query/order_by.py +79 -0
- lightly_studio/core/dataset_query/sample_field.py +37 -0
- lightly_studio/core/dataset_query/tags_expression.py +46 -0
- lightly_studio/core/image_sample.py +36 -0
- lightly_studio/core/loading_log.py +56 -0
- lightly_studio/core/sample.py +291 -0
- lightly_studio/core/start_gui.py +54 -0
- lightly_studio/core/video_sample.py +38 -0
- lightly_studio/dataset/__init__.py +0 -0
- lightly_studio/dataset/edge_embedding_generator.py +155 -0
- lightly_studio/dataset/embedding_generator.py +129 -0
- lightly_studio/dataset/embedding_manager.py +349 -0
- lightly_studio/dataset/env.py +20 -0
- lightly_studio/dataset/file_utils.py +49 -0
- lightly_studio/dataset/fsspec_lister.py +275 -0
- lightly_studio/dataset/mobileclip_embedding_generator.py +158 -0
- lightly_studio/dataset/perception_encoder_embedding_generator.py +260 -0
- lightly_studio/db_manager.py +166 -0
- lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.GcXvs2l7.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/12.Dx6SXgAb.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/17.9X9_k6TP.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/18.BxiimdIO.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/2.CkOblLn7.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/ClassifierSamplesGrid.BJbCDlvs.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/LightlyLogo.BNjCIww-.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Bold.DGvYQtcs.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Italic-VariableFont_wdth_wght.B4AZ-wl6.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Medium.DVUZMR_6.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Regular.DxJTClRG.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-SemiBold.D3TTYgdB.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-VariableFont_wdth_wght.BZBpG5Iz.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.CefECEWA.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.D5tDcjY-.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_page.9X9_k6TP.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_page.BxiimdIO.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_page.Dx6SXgAb.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/transform._-1mPSEI.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/0dDyq72A.js +20 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/69_IOA4Y.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BK4An2kI.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BRmB-kJ9.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B_1cpokE.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BiqpDEr0.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BpLiSKgx.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BscxbINH.js +39 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C1FmrZbK.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C80h3dJx.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C8mfFM-u.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CGY1p9L4.js +517 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/COfLknXM.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWj6FrbW.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CYgJF_JY.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CmLg0ys7.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvGjimpO.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D3RDXHoj.js +39 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D4y7iiT3.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D9SC3jBb.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DCuAdx1Q.js +20 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DDBy-_jD.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIeogL5L.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DL9a7v5o.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DSKECuqX.js +39 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D_FFv0Oe.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DiZ5o5vz.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DkbXUtyG.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DmK2hulV.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DqnHaLTj.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DtWZc_tl.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DuUalyFS.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DwIonDAZ.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Il-mSPmK.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/KNLP4aJU.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/KjYeVjkE.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/MErlcOXj.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/VRI4prUD.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/VYb2dkNs.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/VqWvU2yF.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/dHC3otuL.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/da7Oy_lO.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/eAy8rZzC.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/erjNR5MX.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/f1oG3eFE.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/rsLi1iKv.js +20 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/rwuuBP9f.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/xGHZQ1pe.js +3 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.DrTRUgT3.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.BK5EOJl2.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.CIvTuljF.js +4 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.UBvSzxdA.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.CQ_tiLJa.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.KqkAcaxW.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.DoYsmxQc.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/13.571n2LZA.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/14.DGs689M-.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/15.CWG1ehzT.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/16.Dpq6jbSh.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/17.B5AZbHUU.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/18.CBga8cnq.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.D2HXgz-8.js +1090 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/3.f4HAg-y3.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/4.BKF4xuKQ.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.BAE0Pm_f.js +39 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.CouWWpzA.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.UBHT0ktp.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.FiYNElcc.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.B3-UaT23.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/clustering.worker-DKqeLtG0.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/search.worker-vNSty3B0.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -0
- lightly_studio/dist_lightly_studio_view_app/apple-touch-icon-precomposed.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/apple-touch-icon.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/favicon.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/index.html +45 -0
- lightly_studio/errors.py +5 -0
- lightly_studio/examples/example.py +25 -0
- lightly_studio/examples/example_coco.py +27 -0
- lightly_studio/examples/example_coco_caption.py +29 -0
- lightly_studio/examples/example_metadata.py +369 -0
- lightly_studio/examples/example_operators.py +111 -0
- lightly_studio/examples/example_selection.py +28 -0
- lightly_studio/examples/example_split_work.py +48 -0
- lightly_studio/examples/example_video.py +22 -0
- lightly_studio/examples/example_video_annotations.py +157 -0
- lightly_studio/examples/example_yolo.py +22 -0
- lightly_studio/export/coco_captions.py +69 -0
- lightly_studio/export/export_dataset.py +104 -0
- lightly_studio/export/lightly_studio_label_input.py +120 -0
- lightly_studio/export_schema.py +18 -0
- lightly_studio/export_version.py +57 -0
- lightly_studio/few_shot_classifier/__init__.py +0 -0
- lightly_studio/few_shot_classifier/classifier.py +80 -0
- lightly_studio/few_shot_classifier/classifier_manager.py +644 -0
- lightly_studio/few_shot_classifier/random_forest_classifier.py +495 -0
- lightly_studio/metadata/complex_metadata.py +47 -0
- lightly_studio/metadata/compute_similarity.py +84 -0
- lightly_studio/metadata/compute_typicality.py +67 -0
- lightly_studio/metadata/gps_coordinate.py +41 -0
- lightly_studio/metadata/metadata_protocol.py +17 -0
- lightly_studio/models/__init__.py +1 -0
- lightly_studio/models/annotation/__init__.py +0 -0
- lightly_studio/models/annotation/annotation_base.py +303 -0
- lightly_studio/models/annotation/instance_segmentation.py +56 -0
- lightly_studio/models/annotation/links.py +17 -0
- lightly_studio/models/annotation/object_detection.py +47 -0
- lightly_studio/models/annotation/semantic_segmentation.py +44 -0
- lightly_studio/models/annotation_label.py +47 -0
- lightly_studio/models/caption.py +49 -0
- lightly_studio/models/classifier.py +20 -0
- lightly_studio/models/dataset.py +70 -0
- lightly_studio/models/embedding_model.py +30 -0
- lightly_studio/models/image.py +96 -0
- lightly_studio/models/metadata.py +208 -0
- lightly_studio/models/range.py +17 -0
- lightly_studio/models/sample.py +154 -0
- lightly_studio/models/sample_embedding.py +36 -0
- lightly_studio/models/settings.py +69 -0
- lightly_studio/models/tag.py +96 -0
- lightly_studio/models/two_dim_embedding.py +16 -0
- lightly_studio/models/video.py +161 -0
- lightly_studio/plugins/__init__.py +0 -0
- lightly_studio/plugins/base_operator.py +60 -0
- lightly_studio/plugins/operator_registry.py +47 -0
- lightly_studio/plugins/parameter.py +70 -0
- lightly_studio/py.typed +0 -0
- lightly_studio/resolvers/__init__.py +0 -0
- lightly_studio/resolvers/annotation_label_resolver/__init__.py +22 -0
- lightly_studio/resolvers/annotation_label_resolver/create.py +27 -0
- lightly_studio/resolvers/annotation_label_resolver/delete.py +28 -0
- lightly_studio/resolvers/annotation_label_resolver/get_all.py +37 -0
- lightly_studio/resolvers/annotation_label_resolver/get_by_id.py +24 -0
- lightly_studio/resolvers/annotation_label_resolver/get_by_ids.py +25 -0
- lightly_studio/resolvers/annotation_label_resolver/get_by_label_name.py +24 -0
- lightly_studio/resolvers/annotation_label_resolver/names_by_ids.py +25 -0
- lightly_studio/resolvers/annotation_label_resolver/update.py +38 -0
- lightly_studio/resolvers/annotation_resolver/__init__.py +40 -0
- lightly_studio/resolvers/annotation_resolver/count_annotations_by_dataset.py +129 -0
- lightly_studio/resolvers/annotation_resolver/create_many.py +124 -0
- lightly_studio/resolvers/annotation_resolver/delete_annotation.py +87 -0
- lightly_studio/resolvers/annotation_resolver/delete_annotations.py +60 -0
- lightly_studio/resolvers/annotation_resolver/get_all.py +85 -0
- lightly_studio/resolvers/annotation_resolver/get_all_with_payload.py +179 -0
- lightly_studio/resolvers/annotation_resolver/get_by_id.py +34 -0
- lightly_studio/resolvers/annotation_resolver/get_by_id_with_payload.py +130 -0
- lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +142 -0
- lightly_studio/resolvers/annotation_resolver/update_bounding_box.py +68 -0
- lightly_studio/resolvers/annotations/__init__.py +1 -0
- lightly_studio/resolvers/annotations/annotations_filter.py +88 -0
- lightly_studio/resolvers/caption_resolver.py +129 -0
- lightly_studio/resolvers/dataset_resolver/__init__.py +55 -0
- lightly_studio/resolvers/dataset_resolver/check_dataset_type.py +29 -0
- lightly_studio/resolvers/dataset_resolver/create.py +20 -0
- lightly_studio/resolvers/dataset_resolver/delete.py +20 -0
- lightly_studio/resolvers/dataset_resolver/export.py +267 -0
- lightly_studio/resolvers/dataset_resolver/get_all.py +19 -0
- lightly_studio/resolvers/dataset_resolver/get_by_id.py +16 -0
- lightly_studio/resolvers/dataset_resolver/get_by_name.py +12 -0
- lightly_studio/resolvers/dataset_resolver/get_dataset_details.py +27 -0
- lightly_studio/resolvers/dataset_resolver/get_hierarchy.py +31 -0
- lightly_studio/resolvers/dataset_resolver/get_or_create_child_dataset.py +58 -0
- lightly_studio/resolvers/dataset_resolver/get_parent_dataset_by_sample_id.py +27 -0
- lightly_studio/resolvers/dataset_resolver/get_parent_dataset_id.py +22 -0
- lightly_studio/resolvers/dataset_resolver/get_root_dataset.py +61 -0
- lightly_studio/resolvers/dataset_resolver/get_root_datasets_overview.py +41 -0
- lightly_studio/resolvers/dataset_resolver/update.py +25 -0
- lightly_studio/resolvers/embedding_model_resolver.py +120 -0
- lightly_studio/resolvers/image_filter.py +50 -0
- lightly_studio/resolvers/image_resolver/__init__.py +21 -0
- lightly_studio/resolvers/image_resolver/create_many.py +52 -0
- lightly_studio/resolvers/image_resolver/delete.py +20 -0
- lightly_studio/resolvers/image_resolver/filter_new_paths.py +23 -0
- lightly_studio/resolvers/image_resolver/get_all_by_dataset_id.py +117 -0
- lightly_studio/resolvers/image_resolver/get_by_id.py +14 -0
- lightly_studio/resolvers/image_resolver/get_dimension_bounds.py +75 -0
- lightly_studio/resolvers/image_resolver/get_many_by_id.py +22 -0
- lightly_studio/resolvers/image_resolver/get_samples_excluding.py +43 -0
- lightly_studio/resolvers/metadata_resolver/__init__.py +15 -0
- lightly_studio/resolvers/metadata_resolver/metadata_filter.py +163 -0
- lightly_studio/resolvers/metadata_resolver/sample/__init__.py +21 -0
- lightly_studio/resolvers/metadata_resolver/sample/bulk_update_metadata.py +46 -0
- lightly_studio/resolvers/metadata_resolver/sample/get_by_sample_id.py +24 -0
- lightly_studio/resolvers/metadata_resolver/sample/get_metadata_info.py +104 -0
- lightly_studio/resolvers/metadata_resolver/sample/get_value_for_sample.py +27 -0
- lightly_studio/resolvers/metadata_resolver/sample/set_value_for_sample.py +53 -0
- lightly_studio/resolvers/sample_embedding_resolver.py +132 -0
- lightly_studio/resolvers/sample_resolver/__init__.py +17 -0
- lightly_studio/resolvers/sample_resolver/count_by_dataset_id.py +16 -0
- lightly_studio/resolvers/sample_resolver/create.py +16 -0
- lightly_studio/resolvers/sample_resolver/create_many.py +25 -0
- lightly_studio/resolvers/sample_resolver/get_by_id.py +14 -0
- lightly_studio/resolvers/sample_resolver/get_filtered_samples.py +56 -0
- lightly_studio/resolvers/sample_resolver/get_many_by_id.py +22 -0
- lightly_studio/resolvers/sample_resolver/sample_filter.py +74 -0
- lightly_studio/resolvers/settings_resolver.py +62 -0
- lightly_studio/resolvers/tag_resolver.py +299 -0
- lightly_studio/resolvers/twodim_embedding_resolver.py +119 -0
- lightly_studio/resolvers/video_frame_resolver/__init__.py +23 -0
- lightly_studio/resolvers/video_frame_resolver/count_video_frames_annotations.py +83 -0
- lightly_studio/resolvers/video_frame_resolver/create_many.py +57 -0
- lightly_studio/resolvers/video_frame_resolver/get_all_by_dataset_id.py +63 -0
- lightly_studio/resolvers/video_frame_resolver/get_by_id.py +13 -0
- lightly_studio/resolvers/video_frame_resolver/get_table_fields_bounds.py +44 -0
- lightly_studio/resolvers/video_frame_resolver/video_frame_annotations_counter_filter.py +47 -0
- lightly_studio/resolvers/video_frame_resolver/video_frame_filter.py +57 -0
- lightly_studio/resolvers/video_resolver/__init__.py +27 -0
- lightly_studio/resolvers/video_resolver/count_video_frame_annotations_by_video_dataset.py +86 -0
- lightly_studio/resolvers/video_resolver/create_many.py +58 -0
- lightly_studio/resolvers/video_resolver/filter_new_paths.py +33 -0
- lightly_studio/resolvers/video_resolver/get_all_by_dataset_id.py +181 -0
- lightly_studio/resolvers/video_resolver/get_by_id.py +22 -0
- lightly_studio/resolvers/video_resolver/get_table_fields_bounds.py +72 -0
- lightly_studio/resolvers/video_resolver/get_view_by_id.py +52 -0
- lightly_studio/resolvers/video_resolver/video_count_annotations_filter.py +50 -0
- lightly_studio/resolvers/video_resolver/video_filter.py +98 -0
- lightly_studio/selection/__init__.py +1 -0
- lightly_studio/selection/mundig.py +143 -0
- lightly_studio/selection/select.py +203 -0
- lightly_studio/selection/select_via_db.py +273 -0
- lightly_studio/selection/selection_config.py +49 -0
- lightly_studio/services/annotations_service/__init__.py +33 -0
- lightly_studio/services/annotations_service/create_annotation.py +64 -0
- lightly_studio/services/annotations_service/delete_annotation.py +22 -0
- lightly_studio/services/annotations_service/get_annotation_by_id.py +31 -0
- lightly_studio/services/annotations_service/update_annotation.py +54 -0
- lightly_studio/services/annotations_service/update_annotation_bounding_box.py +36 -0
- lightly_studio/services/annotations_service/update_annotation_label.py +48 -0
- lightly_studio/services/annotations_service/update_annotations.py +29 -0
- lightly_studio/setup_logging.py +59 -0
- lightly_studio/type_definitions.py +31 -0
- lightly_studio/utils/__init__.py +3 -0
- lightly_studio/utils/download.py +94 -0
- lightly_studio/vendor/__init__.py +1 -0
- lightly_studio/vendor/mobileclip/ACKNOWLEDGEMENTS +422 -0
- lightly_studio/vendor/mobileclip/LICENSE +31 -0
- lightly_studio/vendor/mobileclip/LICENSE_weights_data +50 -0
- lightly_studio/vendor/mobileclip/README.md +5 -0
- lightly_studio/vendor/mobileclip/__init__.py +96 -0
- lightly_studio/vendor/mobileclip/clip.py +77 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_b.json +18 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_s0.json +18 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_s1.json +18 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_s2.json +18 -0
- lightly_studio/vendor/mobileclip/image_encoder.py +67 -0
- lightly_studio/vendor/mobileclip/logger.py +154 -0
- lightly_studio/vendor/mobileclip/models/__init__.py +10 -0
- lightly_studio/vendor/mobileclip/models/mci.py +933 -0
- lightly_studio/vendor/mobileclip/models/vit.py +433 -0
- lightly_studio/vendor/mobileclip/modules/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/common/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/common/mobileone.py +341 -0
- lightly_studio/vendor/mobileclip/modules/common/transformer.py +451 -0
- lightly_studio/vendor/mobileclip/modules/image/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/image/image_projection.py +113 -0
- lightly_studio/vendor/mobileclip/modules/image/replknet.py +188 -0
- lightly_studio/vendor/mobileclip/modules/text/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/text/repmixer.py +281 -0
- lightly_studio/vendor/mobileclip/modules/text/tokenizer.py +38 -0
- lightly_studio/vendor/mobileclip/text_encoder.py +245 -0
- lightly_studio/vendor/perception_encoder/LICENSE.PE +201 -0
- lightly_studio/vendor/perception_encoder/README.md +11 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/__init__.py +0 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/bpe_simple_vocab_16e6.txt.gz +0 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/config.py +205 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/config_src.py +264 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/pe.py +766 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/rope.py +352 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/tokenizer.py +347 -0
- lightly_studio/vendor/perception_encoder/vision_encoder/transforms.py +36 -0
- lightly_studio-0.4.6.dist-info/METADATA +88 -0
- lightly_studio-0.4.6.dist-info/RECORD +356 -0
- lightly_studio-0.4.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""API routes for exporting dataset annotation tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from pathlib import Path as PathlibPath
|
|
7
|
+
from tempfile import TemporaryDirectory
|
|
8
|
+
|
|
9
|
+
from fastapi import APIRouter, Depends, Path
|
|
10
|
+
from fastapi.responses import StreamingResponse
|
|
11
|
+
from typing_extensions import Annotated
|
|
12
|
+
|
|
13
|
+
from lightly_studio.api.routes.api import dataset as dataset_api
|
|
14
|
+
from lightly_studio.core.dataset_query.dataset_query import DatasetQuery
|
|
15
|
+
from lightly_studio.db_manager import SessionDep
|
|
16
|
+
from lightly_studio.export import export_dataset
|
|
17
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
18
|
+
|
|
19
|
+
export_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["export"])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@export_router.get("/export/annotations")
|
|
23
|
+
def export_dataset_annotations(
|
|
24
|
+
dataset: Annotated[
|
|
25
|
+
DatasetTable,
|
|
26
|
+
Path(title="Dataset Id"),
|
|
27
|
+
Depends(dataset_api.get_and_validate_dataset_id),
|
|
28
|
+
],
|
|
29
|
+
session: SessionDep,
|
|
30
|
+
) -> StreamingResponse:
|
|
31
|
+
"""Export dataset annotations for an object detection task in COCO format."""
|
|
32
|
+
# Query to export - all samples in the dataset.
|
|
33
|
+
dataset_query = DatasetQuery(dataset=dataset, session=session)
|
|
34
|
+
|
|
35
|
+
# Create the export in a temporary directory. We cannot use a context manager
|
|
36
|
+
# because the directory should be deleted only after the file has finished streaming.
|
|
37
|
+
temp_dir = TemporaryDirectory()
|
|
38
|
+
output_path = PathlibPath(temp_dir.name) / "coco_export.json"
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
export_dataset.to_coco_object_detections(
|
|
42
|
+
session=session,
|
|
43
|
+
samples=dataset_query,
|
|
44
|
+
output_json=output_path,
|
|
45
|
+
)
|
|
46
|
+
except Exception:
|
|
47
|
+
temp_dir.cleanup()
|
|
48
|
+
# Reraise.
|
|
49
|
+
raise
|
|
50
|
+
|
|
51
|
+
return StreamingResponse(
|
|
52
|
+
content=_stream_export_file(
|
|
53
|
+
temp_dir=temp_dir,
|
|
54
|
+
file_path=output_path,
|
|
55
|
+
),
|
|
56
|
+
media_type="application/json",
|
|
57
|
+
headers={
|
|
58
|
+
"Access-Control-Expose-Headers": "Content-Disposition",
|
|
59
|
+
"Content-Disposition": f"attachment; filename={output_path.name}",
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@export_router.get("/export/captions")
|
|
65
|
+
def export_dataset_captions(
|
|
66
|
+
dataset: Annotated[
|
|
67
|
+
DatasetTable,
|
|
68
|
+
Path(title="Dataset Id"),
|
|
69
|
+
Depends(dataset_api.get_and_validate_dataset_id),
|
|
70
|
+
],
|
|
71
|
+
session: SessionDep,
|
|
72
|
+
) -> StreamingResponse:
|
|
73
|
+
"""Export dataset captions in COCO format."""
|
|
74
|
+
# Query to export - all samples in the dataset.
|
|
75
|
+
dataset_query = DatasetQuery(dataset=dataset, session=session)
|
|
76
|
+
|
|
77
|
+
# Create the export in a temporary directory. We cannot use a context manager
|
|
78
|
+
# because the directory should be deleted only after the file has finished streaming.
|
|
79
|
+
temp_dir = TemporaryDirectory()
|
|
80
|
+
output_path = PathlibPath(temp_dir.name) / "coco_captions_export.json"
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
export_dataset.to_coco_captions(
|
|
84
|
+
samples=dataset_query,
|
|
85
|
+
output_json=output_path,
|
|
86
|
+
)
|
|
87
|
+
except Exception:
|
|
88
|
+
temp_dir.cleanup()
|
|
89
|
+
# Reraise.
|
|
90
|
+
raise
|
|
91
|
+
|
|
92
|
+
return StreamingResponse(
|
|
93
|
+
content=_stream_export_file(
|
|
94
|
+
temp_dir=temp_dir,
|
|
95
|
+
file_path=output_path,
|
|
96
|
+
),
|
|
97
|
+
media_type="application/json",
|
|
98
|
+
headers={
|
|
99
|
+
"Access-Control-Expose-Headers": "Content-Disposition",
|
|
100
|
+
"Content-Disposition": f"attachment; filename={output_path.name}",
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _stream_export_file(
|
|
106
|
+
temp_dir: TemporaryDirectory[str],
|
|
107
|
+
file_path: PathlibPath,
|
|
108
|
+
) -> Generator[bytes, None, None]:
|
|
109
|
+
"""Stream the export file and clean up the temporary directory afterwards."""
|
|
110
|
+
try:
|
|
111
|
+
with file_path.open("rb") as file:
|
|
112
|
+
yield from file
|
|
113
|
+
finally:
|
|
114
|
+
temp_dir.cleanup()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""This module contains the API routes for active features."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
|
|
7
|
+
from lightly_studio.api.features import lightly_studio_active_features
|
|
8
|
+
|
|
9
|
+
__all__ = ["features_router", "lightly_studio_active_features"]
|
|
10
|
+
|
|
11
|
+
features_router = APIRouter()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@features_router.get("/features")
|
|
15
|
+
def get_features() -> list[str]:
|
|
16
|
+
"""Get the list of active features in the LightlyStudio app."""
|
|
17
|
+
return lightly_studio_active_features
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""API routes for dataset frames."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, Path
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
|
|
12
|
+
from lightly_studio.api.routes.api.validators import Paginated, PaginatedWithCursor
|
|
13
|
+
from lightly_studio.db_manager import SessionDep
|
|
14
|
+
from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable, AnnotationView
|
|
15
|
+
from lightly_studio.models.annotation.instance_segmentation import (
|
|
16
|
+
InstanceSegmentationAnnotationView,
|
|
17
|
+
)
|
|
18
|
+
from lightly_studio.models.annotation.object_detection import ObjectDetectionAnnotationView
|
|
19
|
+
from lightly_studio.models.annotation.semantic_segmentation import (
|
|
20
|
+
SemanticSegmentationAnnotationView,
|
|
21
|
+
)
|
|
22
|
+
from lightly_studio.models.sample import SampleTable, SampleView
|
|
23
|
+
from lightly_studio.models.video import (
|
|
24
|
+
FrameView,
|
|
25
|
+
VideoFrameFieldsBoundsView,
|
|
26
|
+
VideoFrameTable,
|
|
27
|
+
VideoFrameView,
|
|
28
|
+
VideoFrameViewsWithCount,
|
|
29
|
+
VideoTable,
|
|
30
|
+
VideoView,
|
|
31
|
+
)
|
|
32
|
+
from lightly_studio.resolvers import video_frame_resolver
|
|
33
|
+
from lightly_studio.resolvers.video_frame_resolver.video_frame_annotations_counter_filter import (
|
|
34
|
+
VideoFrameAnnotationsCounterFilter,
|
|
35
|
+
)
|
|
36
|
+
from lightly_studio.resolvers.video_frame_resolver.video_frame_filter import (
|
|
37
|
+
VideoFrameFilter,
|
|
38
|
+
)
|
|
39
|
+
from lightly_studio.resolvers.video_resolver.count_video_frame_annotations_by_video_dataset import (
|
|
40
|
+
CountAnnotationsView,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
frame_router = APIRouter(prefix="/datasets/{video_frame_dataset_id}/frame", tags=["frame"])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ReadVideoFramesRequest(BaseModel):
|
|
47
|
+
"""Request body for reading videos."""
|
|
48
|
+
|
|
49
|
+
filter: VideoFrameFilter | None = Field(None, description="Filter parameters for video frames")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ReadCountVideoFramesAnnotationsRequest(BaseModel):
|
|
53
|
+
"""Request body for reading video frames annotations counter."""
|
|
54
|
+
|
|
55
|
+
filter: VideoFrameAnnotationsCounterFilter | None = Field(
|
|
56
|
+
None, description="Filter parameters for video frames annotations counter"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@frame_router.post("/", response_model=VideoFrameViewsWithCount)
|
|
61
|
+
def get_all_frames(
|
|
62
|
+
video_frame_dataset_id: Annotated[UUID, Path(title="Video dataset Id")],
|
|
63
|
+
session: SessionDep,
|
|
64
|
+
pagination: Annotated[PaginatedWithCursor, Depends()],
|
|
65
|
+
body: ReadVideoFramesRequest,
|
|
66
|
+
) -> VideoFrameViewsWithCount:
|
|
67
|
+
"""Retrieve a list of all frames for a given dataset ID with pagination.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
session: The database session.
|
|
71
|
+
video_frame_dataset_id: The ID of the dataset to retrieve frames for.
|
|
72
|
+
pagination: Pagination parameters including offset and limit.
|
|
73
|
+
body: The body containing the filters
|
|
74
|
+
Returns:
|
|
75
|
+
A list of frames along with the total count.
|
|
76
|
+
"""
|
|
77
|
+
result = video_frame_resolver.get_all_by_dataset_id(
|
|
78
|
+
session=session,
|
|
79
|
+
dataset_id=video_frame_dataset_id,
|
|
80
|
+
pagination=Paginated(offset=pagination.offset, limit=pagination.limit),
|
|
81
|
+
video_frame_filter=body.filter,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return VideoFrameViewsWithCount(
|
|
85
|
+
samples=[_build_video_frame_view(vf=frame) for frame in result.samples],
|
|
86
|
+
total_count=result.total_count,
|
|
87
|
+
next_cursor=result.next_cursor,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@frame_router.get("/bounds")
|
|
92
|
+
def get_video_frames_fields_bounds(
|
|
93
|
+
session: SessionDep,
|
|
94
|
+
video_frame_dataset_id: Annotated[UUID, Path(title="Dataset Id")],
|
|
95
|
+
) -> VideoFrameFieldsBoundsView | None:
|
|
96
|
+
"""Retrieve the video fields bounds for a given dataset ID.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
session: The database session.
|
|
100
|
+
video_frame_dataset_id: The ID of the dataset to retrieve video frames bounds.
|
|
101
|
+
body: The body containg the filters.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
A video frame fields bounds object.
|
|
105
|
+
"""
|
|
106
|
+
return video_frame_resolver.get_table_fields_bounds(
|
|
107
|
+
dataset_id=video_frame_dataset_id,
|
|
108
|
+
session=session,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@frame_router.get("/{sample_id}", response_model=VideoFrameView)
|
|
113
|
+
def get_frame_by_id(
|
|
114
|
+
session: SessionDep,
|
|
115
|
+
sample_id: Annotated[UUID, Path(title="Sample Id")],
|
|
116
|
+
) -> VideoFrameView:
|
|
117
|
+
"""Retrieve a frame by its sample ID within a given dataset.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
session: The database session.
|
|
121
|
+
sample_id: The ID of the sample to retrieve.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
A frame corresponding to the given sample ID.
|
|
125
|
+
"""
|
|
126
|
+
result = video_frame_resolver.get_by_id(session=session, sample_id=sample_id)
|
|
127
|
+
|
|
128
|
+
return _build_video_frame_view(result)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@frame_router.post("/annotations/count", response_model=List[CountAnnotationsView])
|
|
132
|
+
def count_video_frame_annotations(
|
|
133
|
+
session: SessionDep,
|
|
134
|
+
video_frame_dataset_id: Annotated[UUID, Path(title="Video dataset Id")],
|
|
135
|
+
body: ReadCountVideoFramesAnnotationsRequest,
|
|
136
|
+
) -> list[CountAnnotationsView]:
|
|
137
|
+
"""Retrieve a list of annotations along with total count and filtered count.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
session: The database session.
|
|
141
|
+
video_frame_dataset_id: The ID of the dataset to retrieve videos for.
|
|
142
|
+
body: The body containing filters.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
A list of annotations and counters.
|
|
146
|
+
"""
|
|
147
|
+
return video_frame_resolver.count_video_frames_annotations(
|
|
148
|
+
session=session,
|
|
149
|
+
dataset_id=video_frame_dataset_id,
|
|
150
|
+
filters=body.filter,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# TODO (Leonardo 11/25): These manual conversions are needed because
|
|
155
|
+
# of the circular import between Annotation and Sample.
|
|
156
|
+
def _build_annotation_view(a: AnnotationBaseTable) -> AnnotationView:
|
|
157
|
+
return AnnotationView(
|
|
158
|
+
parent_sample_id=a.parent_sample_id,
|
|
159
|
+
sample_id=a.sample_id,
|
|
160
|
+
annotation_type=a.annotation_type,
|
|
161
|
+
confidence=a.confidence,
|
|
162
|
+
created_at=a.created_at,
|
|
163
|
+
annotation_label=AnnotationView.AnnotationLabel(
|
|
164
|
+
annotation_label_name=a.annotation_label.annotation_label_name
|
|
165
|
+
),
|
|
166
|
+
object_detection_details=(
|
|
167
|
+
ObjectDetectionAnnotationView(
|
|
168
|
+
x=a.object_detection_details.x,
|
|
169
|
+
y=a.object_detection_details.y,
|
|
170
|
+
width=a.object_detection_details.width,
|
|
171
|
+
height=a.object_detection_details.height,
|
|
172
|
+
)
|
|
173
|
+
if a.object_detection_details
|
|
174
|
+
else None
|
|
175
|
+
),
|
|
176
|
+
instance_segmentation_details=(
|
|
177
|
+
InstanceSegmentationAnnotationView(
|
|
178
|
+
width=a.instance_segmentation_details.width,
|
|
179
|
+
height=a.instance_segmentation_details.height,
|
|
180
|
+
x=a.instance_segmentation_details.x,
|
|
181
|
+
y=a.instance_segmentation_details.y,
|
|
182
|
+
)
|
|
183
|
+
if a.instance_segmentation_details
|
|
184
|
+
else None
|
|
185
|
+
),
|
|
186
|
+
semantic_segmentation_details=(
|
|
187
|
+
SemanticSegmentationAnnotationView(
|
|
188
|
+
segmentation_mask=a.semantic_segmentation_details.segmentation_mask,
|
|
189
|
+
)
|
|
190
|
+
if a.semantic_segmentation_details
|
|
191
|
+
else None
|
|
192
|
+
),
|
|
193
|
+
tags=[AnnotationView.AnnotationViewTag(tag_id=t.tag_id, name=t.name) for t in a.tags],
|
|
194
|
+
sample=_build_sample_view(a.sample),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _build_sample_view(sample: SampleTable) -> SampleView:
|
|
199
|
+
return SampleView(
|
|
200
|
+
dataset_id=sample.dataset_id,
|
|
201
|
+
sample_id=sample.sample_id,
|
|
202
|
+
created_at=sample.created_at,
|
|
203
|
+
updated_at=sample.updated_at,
|
|
204
|
+
tags=sample.tags,
|
|
205
|
+
metadata_dict=sample.metadata_dict,
|
|
206
|
+
captions=sample.captions,
|
|
207
|
+
annotations=[_build_annotation_view(a) for a in sample.annotations],
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _build_video_view(video: VideoTable) -> VideoView:
|
|
212
|
+
return VideoView(
|
|
213
|
+
width=video.width,
|
|
214
|
+
height=video.height,
|
|
215
|
+
duration_s=video.duration_s,
|
|
216
|
+
fps=video.fps,
|
|
217
|
+
file_name=video.file_name,
|
|
218
|
+
file_path_abs=video.file_path_abs,
|
|
219
|
+
sample_id=video.sample_id,
|
|
220
|
+
sample=_build_sample_view(video.sample),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _build_video_frame_view(vf: VideoFrameTable) -> VideoFrameView:
|
|
225
|
+
return VideoFrameView(
|
|
226
|
+
frame_number=vf.frame_number,
|
|
227
|
+
frame_timestamp_s=vf.frame_timestamp_s,
|
|
228
|
+
sample_id=vf.sample_id,
|
|
229
|
+
video=_build_video_view(vf.video),
|
|
230
|
+
sample=_build_sample_view(vf.sample),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def build_frame_view(vf: VideoFrameTable) -> FrameView:
|
|
235
|
+
"""Create a FrameView."""
|
|
236
|
+
return FrameView(
|
|
237
|
+
frame_number=vf.frame_number,
|
|
238
|
+
frame_timestamp_s=vf.frame_timestamp_s,
|
|
239
|
+
sample_id=vf.sample_id,
|
|
240
|
+
sample=_build_sample_view(vf.sample),
|
|
241
|
+
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""This module contains the API routes for managing samples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
|
|
11
|
+
from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
|
|
12
|
+
from lightly_studio.api.routes.api.status import (
|
|
13
|
+
HTTP_STATUS_NOT_FOUND,
|
|
14
|
+
)
|
|
15
|
+
from lightly_studio.api.routes.api.validators import Paginated
|
|
16
|
+
from lightly_studio.db_manager import SessionDep
|
|
17
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
18
|
+
from lightly_studio.models.image import (
|
|
19
|
+
ImageView,
|
|
20
|
+
ImageViewsWithCount,
|
|
21
|
+
)
|
|
22
|
+
from lightly_studio.resolvers import (
|
|
23
|
+
image_resolver,
|
|
24
|
+
)
|
|
25
|
+
from lightly_studio.resolvers.image_filter import (
|
|
26
|
+
ImageFilter,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
image_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["image"])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReadImagesRequest(BaseModel):
|
|
33
|
+
"""Request body for reading samples with text embedding."""
|
|
34
|
+
|
|
35
|
+
filters: ImageFilter | None = Field(None, description="Filter parameters for samples")
|
|
36
|
+
text_embedding: list[float] | None = Field(None, description="Text embedding to search for")
|
|
37
|
+
sample_ids: list[UUID] | None = Field(None, description="The list of requested sample IDs")
|
|
38
|
+
pagination: Paginated | None = Field(
|
|
39
|
+
None, description="Pagination parameters for offset and limit"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@image_router.post("/images/list")
|
|
44
|
+
def read_images(
|
|
45
|
+
session: SessionDep,
|
|
46
|
+
dataset_id: Annotated[UUID, Path(title="Dataset Id")],
|
|
47
|
+
body: ReadImagesRequest,
|
|
48
|
+
) -> ImageViewsWithCount:
|
|
49
|
+
"""Retrieve a list of samples from the database with optional filtering.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
session: The database session.
|
|
53
|
+
dataset_id: The ID of the dataset to filter samples by.
|
|
54
|
+
body: Optional request body containing text embedding.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A list of filtered samples.
|
|
58
|
+
"""
|
|
59
|
+
result = image_resolver.get_all_by_dataset_id(
|
|
60
|
+
session=session,
|
|
61
|
+
dataset_id=dataset_id,
|
|
62
|
+
pagination=body.pagination,
|
|
63
|
+
filters=body.filters,
|
|
64
|
+
text_embedding=body.text_embedding,
|
|
65
|
+
sample_ids=body.sample_ids,
|
|
66
|
+
)
|
|
67
|
+
# TODO(Michal, 10/2025): Add SampleView to ImageView and then use a response model
|
|
68
|
+
# instead of manual conversion.
|
|
69
|
+
return ImageViewsWithCount(
|
|
70
|
+
samples=[
|
|
71
|
+
ImageView(
|
|
72
|
+
file_name=image.file_name,
|
|
73
|
+
file_path_abs=image.file_path_abs,
|
|
74
|
+
sample_id=image.sample_id,
|
|
75
|
+
annotations=image.sample.annotations,
|
|
76
|
+
captions=image.sample.captions,
|
|
77
|
+
tags=[
|
|
78
|
+
ImageView.ImageViewTag(
|
|
79
|
+
tag_id=tag.tag_id,
|
|
80
|
+
name=tag.name,
|
|
81
|
+
kind=tag.kind,
|
|
82
|
+
created_at=tag.created_at,
|
|
83
|
+
updated_at=tag.updated_at,
|
|
84
|
+
)
|
|
85
|
+
for tag in image.sample.tags
|
|
86
|
+
],
|
|
87
|
+
metadata_dict=image.sample.metadata_dict,
|
|
88
|
+
width=image.width,
|
|
89
|
+
height=image.height,
|
|
90
|
+
sample=image.sample,
|
|
91
|
+
)
|
|
92
|
+
for image in result.samples
|
|
93
|
+
],
|
|
94
|
+
total_count=result.total_count,
|
|
95
|
+
next_cursor=result.next_cursor,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@image_router.get("/images/dimensions")
|
|
100
|
+
def get_image_dimensions(
|
|
101
|
+
session: SessionDep,
|
|
102
|
+
dataset: Annotated[
|
|
103
|
+
DatasetTable,
|
|
104
|
+
Path(title="Dataset Id"),
|
|
105
|
+
Depends(get_and_validate_dataset_id),
|
|
106
|
+
],
|
|
107
|
+
annotation_label_ids: Annotated[list[UUID] | None, Query()] = None,
|
|
108
|
+
) -> dict[str, int]:
|
|
109
|
+
"""Get min and max dimensions of samples in a dataset."""
|
|
110
|
+
return image_resolver.get_dimension_bounds(
|
|
111
|
+
session=session,
|
|
112
|
+
dataset_id=dataset.dataset_id,
|
|
113
|
+
annotation_label_ids=annotation_label_ids,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@image_router.get("/images/{sample_id}")
|
|
118
|
+
def read_image(
|
|
119
|
+
session: SessionDep,
|
|
120
|
+
sample_id: Annotated[UUID, Path(title="Sample Id")],
|
|
121
|
+
) -> ImageView:
|
|
122
|
+
"""Retrieve a single sample from the database."""
|
|
123
|
+
image = image_resolver.get_by_id(session=session, sample_id=sample_id)
|
|
124
|
+
if not image:
|
|
125
|
+
raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail="Sample not found")
|
|
126
|
+
# TODO(Michal, 10/2025): Add SampleView to ImageView and then use a response model
|
|
127
|
+
# instead of manual conversion.
|
|
128
|
+
return ImageView(
|
|
129
|
+
file_name=image.file_name,
|
|
130
|
+
file_path_abs=image.file_path_abs,
|
|
131
|
+
sample_id=image.sample_id,
|
|
132
|
+
annotations=image.sample.annotations,
|
|
133
|
+
captions=image.sample.captions,
|
|
134
|
+
tags=[
|
|
135
|
+
ImageView.ImageViewTag(
|
|
136
|
+
tag_id=tag.tag_id,
|
|
137
|
+
name=tag.name,
|
|
138
|
+
kind=tag.kind,
|
|
139
|
+
created_at=tag.created_at,
|
|
140
|
+
updated_at=tag.updated_at,
|
|
141
|
+
)
|
|
142
|
+
for tag in image.sample.tags
|
|
143
|
+
],
|
|
144
|
+
metadata_dict=image.sample.metadata_dict,
|
|
145
|
+
width=image.width,
|
|
146
|
+
height=image.height,
|
|
147
|
+
sample=image.sample,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SampleAdjacentsParams(BaseModel):
|
|
152
|
+
"""Parameters for getting adjacent samples."""
|
|
153
|
+
|
|
154
|
+
filters: ImageFilter | None = None
|
|
155
|
+
text_embedding: list[float] | None = None
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""This module contains the API routes for managing datasets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException, Path
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
|
|
12
|
+
from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
|
|
13
|
+
from lightly_studio.api.routes.api.status import HTTP_STATUS_NOT_FOUND
|
|
14
|
+
from lightly_studio.db_manager import SessionDep
|
|
15
|
+
from lightly_studio.errors import TagNotFoundError
|
|
16
|
+
from lightly_studio.metadata import compute_similarity, compute_typicality
|
|
17
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
18
|
+
from lightly_studio.models.metadata import MetadataInfoView
|
|
19
|
+
from lightly_studio.resolvers import embedding_model_resolver
|
|
20
|
+
from lightly_studio.resolvers.metadata_resolver.sample.get_metadata_info import (
|
|
21
|
+
get_all_metadata_keys_and_schema,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
metadata_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["metadata"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@metadata_router.get("/metadata/info", response_model=List[MetadataInfoView])
|
|
28
|
+
def get_metadata_info(
|
|
29
|
+
session: SessionDep,
|
|
30
|
+
dataset_id: Annotated[UUID, Path(title="Dataset Id")],
|
|
31
|
+
) -> list[MetadataInfoView]:
|
|
32
|
+
"""Get all metadata keys and their schema for a dataset.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
session: The database session.
|
|
36
|
+
dataset_id: The ID of the dataset.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of metadata info objects with name, type, and optionally min/max values
|
|
40
|
+
for numerical metadata types.
|
|
41
|
+
"""
|
|
42
|
+
return get_all_metadata_keys_and_schema(session=session, dataset_id=dataset_id)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ComputeTypicalityRequest(BaseModel):
|
|
46
|
+
"""Request model for computing typicality metadata."""
|
|
47
|
+
|
|
48
|
+
embedding_model_name: str | None = Field(
|
|
49
|
+
default=None,
|
|
50
|
+
description="Embedding model name (uses default if not specified)",
|
|
51
|
+
)
|
|
52
|
+
metadata_name: str = Field(
|
|
53
|
+
default="typicality",
|
|
54
|
+
description="Metadata field name (defaults to 'typicality')",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@metadata_router.post(
|
|
59
|
+
"/metadata/typicality",
|
|
60
|
+
status_code=204,
|
|
61
|
+
response_model=None,
|
|
62
|
+
)
|
|
63
|
+
def compute_typicality_metadata(
|
|
64
|
+
session: SessionDep,
|
|
65
|
+
dataset: Annotated[
|
|
66
|
+
DatasetTable,
|
|
67
|
+
Depends(get_and_validate_dataset_id),
|
|
68
|
+
],
|
|
69
|
+
request: ComputeTypicalityRequest,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Compute typicality metadata for a dataset.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
session: The database session.
|
|
75
|
+
dataset: The dataset to compute typicality for.
|
|
76
|
+
request: Request parameters including optional embedding model name
|
|
77
|
+
and metadata field name.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
None (204 No Content on success).
|
|
81
|
+
"""
|
|
82
|
+
embedding_model = embedding_model_resolver.get_by_name(
|
|
83
|
+
session=session,
|
|
84
|
+
dataset_id=dataset.dataset_id,
|
|
85
|
+
embedding_model_name=request.embedding_model_name,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
compute_typicality.compute_typicality_metadata(
|
|
89
|
+
session=session,
|
|
90
|
+
dataset_id=dataset.dataset_id,
|
|
91
|
+
embedding_model_id=embedding_model.embedding_model_id,
|
|
92
|
+
metadata_name=request.metadata_name,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ComputeSimilarityRequest(BaseModel):
|
|
97
|
+
"""Request model for computing typicality metadata."""
|
|
98
|
+
|
|
99
|
+
embedding_model_name: str | None = Field(
|
|
100
|
+
default=None,
|
|
101
|
+
description="Embedding model name (uses default if not specified)",
|
|
102
|
+
)
|
|
103
|
+
metadata_name: str | None = Field(
|
|
104
|
+
default=None,
|
|
105
|
+
description="Metadata field name (defaults to None)",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@metadata_router.post(
|
|
110
|
+
"/metadata/similarity/{query_tag_id}",
|
|
111
|
+
response_model=str,
|
|
112
|
+
)
|
|
113
|
+
def compute_similarity_metadata(
|
|
114
|
+
session: SessionDep,
|
|
115
|
+
dataset: Annotated[
|
|
116
|
+
DatasetTable,
|
|
117
|
+
Depends(get_and_validate_dataset_id),
|
|
118
|
+
],
|
|
119
|
+
query_tag_id: Annotated[UUID, Path(title="Query Tag ID")],
|
|
120
|
+
request: ComputeSimilarityRequest,
|
|
121
|
+
) -> str:
|
|
122
|
+
"""Compute similarity metadata for a dataset.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
session: The database session.
|
|
126
|
+
dataset: The dataset to compute similarity for.
|
|
127
|
+
query_tag_id: The ID of the tag to use for the query
|
|
128
|
+
request: Request parameters including optional embedding model name
|
|
129
|
+
and metadata field name.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Metadata name used for the similarity.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
HTTPException: 404 if invalid embedding model or query tag is given.
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
embedding_model = embedding_model_resolver.get_by_name(
|
|
139
|
+
session=session,
|
|
140
|
+
dataset_id=dataset.dataset_id,
|
|
141
|
+
embedding_model_name=request.embedding_model_name,
|
|
142
|
+
)
|
|
143
|
+
except ValueError as e:
|
|
144
|
+
raise HTTPException(
|
|
145
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
146
|
+
detail="Embedding model not found",
|
|
147
|
+
) from e
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
return compute_similarity.compute_similarity_metadata(
|
|
151
|
+
session=session,
|
|
152
|
+
key_dataset_id=dataset.dataset_id,
|
|
153
|
+
query_tag_id=query_tag_id,
|
|
154
|
+
embedding_model_id=embedding_model.embedding_model_id,
|
|
155
|
+
metadata_name=request.metadata_name,
|
|
156
|
+
)
|
|
157
|
+
except TagNotFoundError as e:
|
|
158
|
+
raise HTTPException(
|
|
159
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
160
|
+
detail=f"Query tag {query_tag_id} not found",
|
|
161
|
+
) from e
|