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,12 @@
|
|
|
1
|
+
# Set up logging before importing any other modules.
|
|
2
|
+
# Add noqa to silence unused import and unsorted imports linter warnings.
|
|
3
|
+
from . import setup_logging # noqa: F401 I001
|
|
4
|
+
from lightly_studio.core.dataset import Dataset
|
|
5
|
+
from lightly_studio.core.start_gui import start_gui
|
|
6
|
+
from lightly_studio.models.dataset import SampleType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# TODO (Jonas 08/25): This will be removed as soon as the new interface is used in the examples
|
|
10
|
+
from lightly_studio.models.annotation.annotation_base import AnnotationType
|
|
11
|
+
|
|
12
|
+
__all__ = ["AnnotationType", "Dataset", "SampleType", "start_gui"]
|
|
File without changes
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""This module contains the FastAPI app configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncGenerator
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, Depends, FastAPI
|
|
9
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
10
|
+
from fastapi.routing import APIRoute
|
|
11
|
+
from sqlmodel import Session
|
|
12
|
+
from typing_extensions import Annotated
|
|
13
|
+
|
|
14
|
+
from lightly_studio import db_manager
|
|
15
|
+
from lightly_studio.api.routes import (
|
|
16
|
+
healthz,
|
|
17
|
+
images,
|
|
18
|
+
video_frames_media,
|
|
19
|
+
video_media,
|
|
20
|
+
webapp,
|
|
21
|
+
)
|
|
22
|
+
from lightly_studio.api.routes.api import (
|
|
23
|
+
annotation,
|
|
24
|
+
annotation_label,
|
|
25
|
+
caption,
|
|
26
|
+
classifier,
|
|
27
|
+
dataset,
|
|
28
|
+
dataset_tag,
|
|
29
|
+
embeddings2d,
|
|
30
|
+
export,
|
|
31
|
+
features,
|
|
32
|
+
frame,
|
|
33
|
+
image,
|
|
34
|
+
metadata,
|
|
35
|
+
operator,
|
|
36
|
+
sample,
|
|
37
|
+
selection,
|
|
38
|
+
settings,
|
|
39
|
+
text_embedding,
|
|
40
|
+
video,
|
|
41
|
+
)
|
|
42
|
+
from lightly_studio.api.routes.api.exceptions import (
|
|
43
|
+
register_exception_handlers,
|
|
44
|
+
)
|
|
45
|
+
from lightly_studio.dataset.env import LIGHTLY_STUDIO_DEBUG
|
|
46
|
+
|
|
47
|
+
SessionDep = Annotated[Session, Depends(db_manager.session)]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@asynccontextmanager
|
|
51
|
+
async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
|
52
|
+
"""Lifespan context for initializing and cleaning up resources.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
_: The FastAPI application instance.
|
|
56
|
+
|
|
57
|
+
Yields:
|
|
58
|
+
None when the application is ready.
|
|
59
|
+
"""
|
|
60
|
+
yield
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if LIGHTLY_STUDIO_DEBUG:
|
|
64
|
+
import logging
|
|
65
|
+
|
|
66
|
+
# TODO(Lukas, 12/2025): move this into setup_logging.py, drop `basicConfig()`. Also everything
|
|
67
|
+
# seems to be on the INFO level. `logging.DEBUG` maybe doesn't make a difference.
|
|
68
|
+
logging.basicConfig()
|
|
69
|
+
logger = logging.getLogger("sqlalchemy.engine")
|
|
70
|
+
logger.setLevel(logging.DEBUG)
|
|
71
|
+
|
|
72
|
+
"""Create the FastAPI app."""
|
|
73
|
+
app = FastAPI(lifespan=lifespan)
|
|
74
|
+
|
|
75
|
+
app.add_middleware(
|
|
76
|
+
CORSMiddleware,
|
|
77
|
+
allow_origins=["*"],
|
|
78
|
+
allow_credentials=True,
|
|
79
|
+
allow_methods=["*"],
|
|
80
|
+
allow_headers=["*"],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def use_route_names_as_operation_ids(app: FastAPI) -> None:
|
|
85
|
+
"""Use API function name for operation IDs.
|
|
86
|
+
|
|
87
|
+
Should be called only after all routes have been added.
|
|
88
|
+
"""
|
|
89
|
+
for route in app.routes:
|
|
90
|
+
if isinstance(route, APIRoute):
|
|
91
|
+
route.operation_id = route.name # in this case, 'read_items'
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
register_exception_handlers(app)
|
|
95
|
+
|
|
96
|
+
# api routes
|
|
97
|
+
api_router = APIRouter(prefix="/api", tags=["api"])
|
|
98
|
+
|
|
99
|
+
api_router.include_router(dataset.dataset_router)
|
|
100
|
+
api_router.include_router(dataset_tag.tag_router)
|
|
101
|
+
api_router.include_router(export.export_router)
|
|
102
|
+
api_router.include_router(image.image_router)
|
|
103
|
+
api_router.include_router(sample.sample_router)
|
|
104
|
+
api_router.include_router(annotation_label.annotations_label_router)
|
|
105
|
+
api_router.include_router(annotation.annotations_router)
|
|
106
|
+
api_router.include_router(caption.captions_router)
|
|
107
|
+
api_router.include_router(text_embedding.text_embedding_router)
|
|
108
|
+
api_router.include_router(settings.settings_router)
|
|
109
|
+
api_router.include_router(classifier.classifier_router)
|
|
110
|
+
api_router.include_router(embeddings2d.embeddings2d_router)
|
|
111
|
+
api_router.include_router(features.features_router)
|
|
112
|
+
api_router.include_router(metadata.metadata_router)
|
|
113
|
+
api_router.include_router(selection.selection_router)
|
|
114
|
+
api_router.include_router(operator.operator_router)
|
|
115
|
+
api_router.include_router(frame.frame_router)
|
|
116
|
+
api_router.include_router(video.video_router)
|
|
117
|
+
|
|
118
|
+
app.include_router(api_router)
|
|
119
|
+
|
|
120
|
+
# images serving
|
|
121
|
+
app.include_router(images.app_router, prefix="/images")
|
|
122
|
+
app.include_router(video_frames_media.frames_router)
|
|
123
|
+
app.include_router(video_media.app_router)
|
|
124
|
+
|
|
125
|
+
# health status check
|
|
126
|
+
app.include_router(healthz.health_router)
|
|
127
|
+
|
|
128
|
+
# webapp routes
|
|
129
|
+
app.include_router(webapp.app_router)
|
|
130
|
+
|
|
131
|
+
use_route_names_as_operation_ids(app)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""This module contains the FastAPI cache configuration for static files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from os import PathLike, stat_result
|
|
7
|
+
|
|
8
|
+
from fastapi import Response
|
|
9
|
+
from fastapi.staticfiles import StaticFiles
|
|
10
|
+
from starlette.types import Scope
|
|
11
|
+
|
|
12
|
+
from .routes.api.status import HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StaticFilesCache(StaticFiles):
|
|
16
|
+
"""StaticFiles class with cache headers."""
|
|
17
|
+
|
|
18
|
+
days_to_expire = 1
|
|
19
|
+
|
|
20
|
+
def __init__( # noqa: PLR0913 (too-many-arguments)
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
directory: str | PathLike[str] | None = None,
|
|
24
|
+
packages: list[str | tuple[str, str]] | None = None,
|
|
25
|
+
html: bool = False,
|
|
26
|
+
check_dir: bool = True,
|
|
27
|
+
follow_symlink: bool = False,
|
|
28
|
+
cachecontrol: str | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize the StaticFilesCache class."""
|
|
31
|
+
self.cachecontrol = cachecontrol or f"private, max-age={self.days_to_expire * 24 * 60 * 60}"
|
|
32
|
+
super().__init__(
|
|
33
|
+
directory=directory,
|
|
34
|
+
packages=packages,
|
|
35
|
+
html=html,
|
|
36
|
+
check_dir=check_dir,
|
|
37
|
+
follow_symlink=follow_symlink,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def file_response(
|
|
41
|
+
self,
|
|
42
|
+
full_path: str | PathLike[str],
|
|
43
|
+
stat_result: stat_result,
|
|
44
|
+
scope: Scope,
|
|
45
|
+
status_code: int = 200,
|
|
46
|
+
) -> Response:
|
|
47
|
+
"""Override the file_response method to add cache headers."""
|
|
48
|
+
allowed_extensions = (
|
|
49
|
+
# Images
|
|
50
|
+
".png",
|
|
51
|
+
".jpg",
|
|
52
|
+
".jpeg",
|
|
53
|
+
".gif",
|
|
54
|
+
".webp",
|
|
55
|
+
".bmp",
|
|
56
|
+
".tiff",
|
|
57
|
+
# Movies
|
|
58
|
+
".mov",
|
|
59
|
+
".mp4",
|
|
60
|
+
".avi",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not str(full_path).lower().endswith(allowed_extensions):
|
|
64
|
+
return Response(
|
|
65
|
+
status_code=HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE
|
|
66
|
+
) # Unsupported Media Type
|
|
67
|
+
resp: Response = super().file_response(full_path, stat_result, scope, status_code)
|
|
68
|
+
resp.headers.setdefault("Cache-Control", self.cachecontrol)
|
|
69
|
+
|
|
70
|
+
# Calculate expiration date
|
|
71
|
+
expire_date = datetime.now(timezone.utc) + timedelta(days=self.days_to_expire)
|
|
72
|
+
resp.headers.setdefault("Expires", expire_date.strftime("%a, %d %b %Y %H:%M:%S GMT"))
|
|
73
|
+
|
|
74
|
+
# Add Vary header to make sure caches respect the query parameters
|
|
75
|
+
resp.headers.setdefault("Vary", "Accept-Encoding, Origin, v")
|
|
76
|
+
|
|
77
|
+
return resp
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Module provides functions to initialize and manage the DuckDB."""
|
|
2
|
+
|
|
3
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
4
|
+
AnnotationBaseTable, # noqa: F401, required for SQLModel to work properly
|
|
5
|
+
)
|
|
6
|
+
from lightly_studio.models.annotation_label import (
|
|
7
|
+
AnnotationLabelTable, # noqa: F401, required for SQLModel to work properly
|
|
8
|
+
)
|
|
9
|
+
from lightly_studio.models.dataset import (
|
|
10
|
+
DatasetTable, # noqa: F401, required for SQLModel to work properly
|
|
11
|
+
)
|
|
12
|
+
from lightly_studio.models.embedding_model import (
|
|
13
|
+
EmbeddingModelTable, # noqa: F401, required for SQLModel to work properly
|
|
14
|
+
)
|
|
15
|
+
from lightly_studio.models.image import (
|
|
16
|
+
ImageTable, # noqa: F401, required for SQLModel to work properly
|
|
17
|
+
)
|
|
18
|
+
from lightly_studio.models.metadata import (
|
|
19
|
+
SampleMetadataTable, # noqa: F401, required for SQLModel to work properly
|
|
20
|
+
)
|
|
21
|
+
from lightly_studio.models.sample import (
|
|
22
|
+
SampleTable, # noqa: F401, required for SQLModel to work properly
|
|
23
|
+
)
|
|
24
|
+
from lightly_studio.models.sample_embedding import (
|
|
25
|
+
SampleEmbeddingTable, # noqa: F401, required for SQLModel to work properly
|
|
26
|
+
)
|
|
27
|
+
from lightly_studio.models.settings import (
|
|
28
|
+
SettingTable, # noqa: F401, required for SQLModel to work properly
|
|
29
|
+
)
|
|
30
|
+
from lightly_studio.models.tag import (
|
|
31
|
+
TagTable, # noqa: F401, required for SQLModel to work properly
|
|
32
|
+
)
|
|
33
|
+
from lightly_studio.models.two_dim_embedding import (
|
|
34
|
+
TwoDimEmbeddingTable, # noqa: F401, required for SQLModel to work properly
|
|
35
|
+
)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""This module contains the API routes for managing annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Body, Depends, HTTPException, Path
|
|
8
|
+
from fastapi.params import Query
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
|
|
12
|
+
from lightly_studio.api.routes.api import annotations as annotations_module
|
|
13
|
+
from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
|
|
14
|
+
from lightly_studio.api.routes.api.status import (
|
|
15
|
+
HTTP_STATUS_CREATED,
|
|
16
|
+
HTTP_STATUS_NOT_FOUND,
|
|
17
|
+
)
|
|
18
|
+
from lightly_studio.api.routes.api.validators import Paginated, PaginatedWithCursor
|
|
19
|
+
from lightly_studio.db_manager import SessionDep
|
|
20
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
21
|
+
AnnotationBaseTable,
|
|
22
|
+
AnnotationDetailsWithPayloadView,
|
|
23
|
+
AnnotationView,
|
|
24
|
+
AnnotationViewsWithCount,
|
|
25
|
+
AnnotationWithPayloadAndCountView,
|
|
26
|
+
)
|
|
27
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
28
|
+
from lightly_studio.resolvers import annotation_resolver, tag_resolver
|
|
29
|
+
from lightly_studio.resolvers.annotation_resolver.get_all import (
|
|
30
|
+
GetAllAnnotationsResult,
|
|
31
|
+
)
|
|
32
|
+
from lightly_studio.resolvers.annotation_resolver.update_bounding_box import BoundingBoxCoordinates
|
|
33
|
+
from lightly_studio.resolvers.annotations.annotations_filter import (
|
|
34
|
+
AnnotationsFilter,
|
|
35
|
+
)
|
|
36
|
+
from lightly_studio.services import annotations_service
|
|
37
|
+
from lightly_studio.services.annotations_service.update_annotation import (
|
|
38
|
+
AnnotationUpdate,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
annotations_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["annotations"])
|
|
42
|
+
annotations_router.include_router(annotations_module.create_annotation_router)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AnnotationQueryParamsModel(BaseModel):
|
|
46
|
+
"""Model for all annotation query parameters."""
|
|
47
|
+
|
|
48
|
+
pagination: PaginatedWithCursor
|
|
49
|
+
annotation_label_ids: list[UUID] | None = None
|
|
50
|
+
tag_ids: list[UUID] | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_annotation_query_params(
|
|
54
|
+
pagination: Annotated[PaginatedWithCursor, Depends()],
|
|
55
|
+
annotation_label_ids: Annotated[list[UUID] | None, Query()] = None,
|
|
56
|
+
tag_ids: Annotated[list[UUID] | None, Query()] = None,
|
|
57
|
+
) -> AnnotationQueryParamsModel:
|
|
58
|
+
return AnnotationQueryParamsModel(
|
|
59
|
+
pagination=pagination,
|
|
60
|
+
annotation_label_ids=annotation_label_ids,
|
|
61
|
+
tag_ids=tag_ids,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@annotations_router.get("/annotations/count")
|
|
66
|
+
def count_annotations_by_dataset( # noqa: PLR0913 // FIXME: refactor to use proper pydantic
|
|
67
|
+
dataset: Annotated[
|
|
68
|
+
DatasetTable,
|
|
69
|
+
Path(title="Dataset Id"),
|
|
70
|
+
Depends(get_and_validate_dataset_id),
|
|
71
|
+
],
|
|
72
|
+
session: SessionDep,
|
|
73
|
+
filtered_labels: Annotated[list[str] | None, Query()] = None,
|
|
74
|
+
min_width: Annotated[int | None, Query(ge=0)] = None,
|
|
75
|
+
max_width: Annotated[int | None, Query(ge=0)] = None,
|
|
76
|
+
min_height: Annotated[int | None, Query(ge=0)] = None,
|
|
77
|
+
max_height: Annotated[int | None, Query(ge=0)] = None,
|
|
78
|
+
tag_ids: list[UUID] | None = None,
|
|
79
|
+
) -> list[dict[str, str | int]]:
|
|
80
|
+
"""Get annotation counts for a specific dataset.
|
|
81
|
+
|
|
82
|
+
Returns a list of dictionaries with label name and count.
|
|
83
|
+
"""
|
|
84
|
+
counts = annotation_resolver.count_annotations_by_dataset(
|
|
85
|
+
session=session,
|
|
86
|
+
dataset_id=dataset.dataset_id,
|
|
87
|
+
filtered_labels=filtered_labels,
|
|
88
|
+
min_width=min_width,
|
|
89
|
+
max_width=max_width,
|
|
90
|
+
min_height=min_height,
|
|
91
|
+
max_height=max_height,
|
|
92
|
+
tag_ids=tag_ids,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
"label_name": label_name,
|
|
98
|
+
"current_count": current_count,
|
|
99
|
+
"total_count": total_count,
|
|
100
|
+
}
|
|
101
|
+
for label_name, current_count, total_count in counts
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@annotations_router.get(
|
|
106
|
+
"/annotations",
|
|
107
|
+
response_model=AnnotationViewsWithCount,
|
|
108
|
+
)
|
|
109
|
+
def read_annotations(
|
|
110
|
+
dataset_id: Annotated[UUID, Path(title="Dataset Id", description="The ID of the dataset")],
|
|
111
|
+
session: SessionDep,
|
|
112
|
+
pagination: Annotated[PaginatedWithCursor, Depends()],
|
|
113
|
+
annotation_label_ids: Annotated[list[UUID] | None, Query()] = None,
|
|
114
|
+
tag_ids: Annotated[list[UUID] | None, Query()] = None,
|
|
115
|
+
) -> GetAllAnnotationsResult:
|
|
116
|
+
"""Retrieve a list of annotations from the database."""
|
|
117
|
+
return annotation_resolver.get_all(
|
|
118
|
+
session=session,
|
|
119
|
+
pagination=Paginated(
|
|
120
|
+
offset=pagination.offset,
|
|
121
|
+
limit=pagination.limit,
|
|
122
|
+
),
|
|
123
|
+
filters=AnnotationsFilter(
|
|
124
|
+
dataset_ids=[dataset_id],
|
|
125
|
+
annotation_label_ids=annotation_label_ids,
|
|
126
|
+
annotation_tag_ids=tag_ids,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@annotations_router.get(
|
|
132
|
+
"/annotations/payload",
|
|
133
|
+
)
|
|
134
|
+
def read_annotations_with_payload(
|
|
135
|
+
dataset_id: Annotated[UUID, Path(title="Dataset Id", description="The ID of the dataset")],
|
|
136
|
+
session: SessionDep,
|
|
137
|
+
params: Annotated[AnnotationQueryParamsModel, Depends(_get_annotation_query_params)],
|
|
138
|
+
) -> AnnotationWithPayloadAndCountView:
|
|
139
|
+
"""Retrieve a list of annotations along with the parent sample data from the database."""
|
|
140
|
+
return annotation_resolver.get_all_with_payload(
|
|
141
|
+
session=session,
|
|
142
|
+
pagination=Paginated(
|
|
143
|
+
offset=params.pagination.offset,
|
|
144
|
+
limit=params.pagination.limit,
|
|
145
|
+
),
|
|
146
|
+
filters=AnnotationsFilter(
|
|
147
|
+
dataset_ids=[dataset_id],
|
|
148
|
+
annotation_label_ids=params.annotation_label_ids,
|
|
149
|
+
annotation_tag_ids=params.tag_ids,
|
|
150
|
+
),
|
|
151
|
+
dataset_id=dataset_id,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@annotations_router.post(
|
|
156
|
+
"/annotations/{annotation_id}/tag/{tag_id}",
|
|
157
|
+
status_code=HTTP_STATUS_CREATED,
|
|
158
|
+
)
|
|
159
|
+
def add_tag_to_annotation(
|
|
160
|
+
session: SessionDep,
|
|
161
|
+
annotation_id: UUID,
|
|
162
|
+
tag_id: UUID,
|
|
163
|
+
) -> bool:
|
|
164
|
+
"""Add annotation to a tag."""
|
|
165
|
+
annotation = annotation_resolver.get_by_id(session=session, annotation_id=annotation_id)
|
|
166
|
+
if not annotation:
|
|
167
|
+
raise HTTPException(
|
|
168
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
169
|
+
detail=f"Annotation {annotation_id} not found",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if not tag_resolver.add_tag_to_annotation(
|
|
173
|
+
session=session, tag_id=tag_id, annotation=annotation
|
|
174
|
+
):
|
|
175
|
+
raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail=f"Tag {tag_id} not found")
|
|
176
|
+
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class AnnotationUpdateInput(BaseModel):
|
|
181
|
+
"""API input model for updating an annotation."""
|
|
182
|
+
|
|
183
|
+
annotation_id: UUID
|
|
184
|
+
dataset_id: UUID
|
|
185
|
+
label_name: str | None = None
|
|
186
|
+
bounding_box: BoundingBoxCoordinates | None = None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@annotations_router.put("/annotations/{annotation_id}")
|
|
190
|
+
def update_annotation(
|
|
191
|
+
session: SessionDep,
|
|
192
|
+
dataset_id: Annotated[
|
|
193
|
+
UUID,
|
|
194
|
+
Path(title="Dataset Id"),
|
|
195
|
+
],
|
|
196
|
+
annotation_id: Annotated[
|
|
197
|
+
UUID,
|
|
198
|
+
Path(title="Annotation ID", description="ID of the annotation to update"),
|
|
199
|
+
],
|
|
200
|
+
annotation_update_input: Annotated[AnnotationUpdateInput, Body()],
|
|
201
|
+
) -> AnnotationBaseTable:
|
|
202
|
+
"""Update an existing annotation in the database."""
|
|
203
|
+
return annotations_service.update_annotation(
|
|
204
|
+
session=session,
|
|
205
|
+
annotation_update=AnnotationUpdate(
|
|
206
|
+
annotation_id=annotation_id,
|
|
207
|
+
dataset_id=dataset_id,
|
|
208
|
+
label_name=annotation_update_input.label_name,
|
|
209
|
+
bounding_box=annotation_update_input.bounding_box,
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@annotations_router.put(
|
|
215
|
+
"/annotations",
|
|
216
|
+
)
|
|
217
|
+
def update_annotations(
|
|
218
|
+
session: SessionDep,
|
|
219
|
+
dataset_id: Annotated[
|
|
220
|
+
UUID,
|
|
221
|
+
Path(title="Dataset Id"),
|
|
222
|
+
],
|
|
223
|
+
annotation_update_inputs: Annotated[list[AnnotationUpdateInput], Body()],
|
|
224
|
+
) -> list[AnnotationBaseTable]:
|
|
225
|
+
"""Update multiple annotations in the database."""
|
|
226
|
+
return annotations_service.update_annotations(
|
|
227
|
+
session=session,
|
|
228
|
+
annotation_updates=[
|
|
229
|
+
AnnotationUpdate(
|
|
230
|
+
annotation_id=annotation_update_input.annotation_id,
|
|
231
|
+
dataset_id=dataset_id,
|
|
232
|
+
label_name=annotation_update_input.label_name,
|
|
233
|
+
bounding_box=annotation_update_input.bounding_box,
|
|
234
|
+
)
|
|
235
|
+
for annotation_update_input in annotation_update_inputs
|
|
236
|
+
],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@annotations_router.get("/annotations/{annotation_id}", response_model=AnnotationView)
|
|
241
|
+
def get_annotation(
|
|
242
|
+
session: SessionDep,
|
|
243
|
+
dataset_id: Annotated[ # noqa: ARG001
|
|
244
|
+
UUID,
|
|
245
|
+
Path(title="Dataset Id", description="The ID of the dataset"),
|
|
246
|
+
], # We need dataset_id because otherwise the path would not match
|
|
247
|
+
annotation_id: Annotated[UUID, Path(title="Annotation ID")],
|
|
248
|
+
) -> AnnotationBaseTable:
|
|
249
|
+
"""Retrieve an existing annotation from the database."""
|
|
250
|
+
return annotations_service.get_annotation_by_id(session=session, annotation_id=annotation_id)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@annotations_router.delete("/annotations/{annotation_id}/tag/{tag_id}")
|
|
254
|
+
def remove_tag_from_annotation(
|
|
255
|
+
session: SessionDep,
|
|
256
|
+
tag_id: UUID,
|
|
257
|
+
annotation_id: UUID,
|
|
258
|
+
) -> bool:
|
|
259
|
+
"""Remove annotation from a tag."""
|
|
260
|
+
annotation = annotation_resolver.get_by_id(session=session, annotation_id=annotation_id)
|
|
261
|
+
if not annotation:
|
|
262
|
+
raise HTTPException(
|
|
263
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
264
|
+
detail=f"Annotation {annotation_id} not found",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if not tag_resolver.remove_tag_from_annotation(
|
|
268
|
+
session=session, tag_id=tag_id, annotation=annotation
|
|
269
|
+
):
|
|
270
|
+
raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail=f"Tag {tag_id} not found")
|
|
271
|
+
|
|
272
|
+
return True
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@annotations_router.delete("/annotations/{annotation_id}")
|
|
276
|
+
def delete_annotation(
|
|
277
|
+
session: SessionDep,
|
|
278
|
+
# We need dataset_id because generator doesn't include it
|
|
279
|
+
# actuall path for this route is /datasets/{dataset_id}/annotations/{annotation_id}
|
|
280
|
+
dataset_id: Annotated[ # noqa: ARG001
|
|
281
|
+
UUID,
|
|
282
|
+
Path(title="Dataset Id", description="The ID of the dataset"),
|
|
283
|
+
],
|
|
284
|
+
annotation_id: Annotated[
|
|
285
|
+
UUID, Path(title="Annotation ID", description="ID of the annotation to delete")
|
|
286
|
+
],
|
|
287
|
+
) -> dict[str, str]:
|
|
288
|
+
"""Delete an annotation from the database."""
|
|
289
|
+
try:
|
|
290
|
+
annotations_service.delete_annotation(session=session, annotation_id=annotation_id)
|
|
291
|
+
return {"status": "deleted"}
|
|
292
|
+
except ValueError as e:
|
|
293
|
+
raise HTTPException(
|
|
294
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
295
|
+
detail="Annotation not found",
|
|
296
|
+
) from e
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@annotations_router.get("/annotations/payload/{sample_id}")
|
|
300
|
+
def get_annotation_with_payload(
|
|
301
|
+
session: SessionDep,
|
|
302
|
+
sample_id: Annotated[UUID, Path(title="Annotation ID")],
|
|
303
|
+
) -> AnnotationDetailsWithPayloadView | None:
|
|
304
|
+
"""Retrieve an existing annotation with payload from the database."""
|
|
305
|
+
return annotation_resolver.get_by_id_with_payload(session=session, sample_id=sample_id)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""This module contains the API routes for managing annotation labels."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, HTTPException
|
|
8
|
+
|
|
9
|
+
from lightly_studio.api.routes.api.status import (
|
|
10
|
+
HTTP_STATUS_CREATED,
|
|
11
|
+
HTTP_STATUS_NOT_FOUND,
|
|
12
|
+
)
|
|
13
|
+
from lightly_studio.db_manager import SessionDep
|
|
14
|
+
from lightly_studio.models.annotation_label import (
|
|
15
|
+
AnnotationLabelCreate,
|
|
16
|
+
AnnotationLabelTable,
|
|
17
|
+
)
|
|
18
|
+
from lightly_studio.resolvers import annotation_label_resolver
|
|
19
|
+
|
|
20
|
+
annotations_label_router = APIRouter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@annotations_label_router.post(
|
|
24
|
+
"/annotation_labels",
|
|
25
|
+
status_code=HTTP_STATUS_CREATED,
|
|
26
|
+
)
|
|
27
|
+
def create_annotation_label(
|
|
28
|
+
input_label: AnnotationLabelCreate,
|
|
29
|
+
session: SessionDep,
|
|
30
|
+
) -> AnnotationLabelTable:
|
|
31
|
+
"""Create a new annotation label in the database."""
|
|
32
|
+
return annotation_label_resolver.create(session=session, label=input_label)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@annotations_label_router.get("/annotation_labels")
|
|
36
|
+
def read_annotation_labels(
|
|
37
|
+
session: SessionDep,
|
|
38
|
+
) -> list[AnnotationLabelTable]:
|
|
39
|
+
"""Retrieve a list of annotation labels from the database."""
|
|
40
|
+
return annotation_label_resolver.get_all(session=session)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@annotations_label_router.get("/annotation_labels/{label_id}")
|
|
44
|
+
def read_annotation_label(
|
|
45
|
+
label_id: UUID,
|
|
46
|
+
session: SessionDep,
|
|
47
|
+
) -> AnnotationLabelTable:
|
|
48
|
+
"""Retrieve a single annotation label from the database."""
|
|
49
|
+
label = annotation_label_resolver.get_by_id(session=session, label_id=label_id)
|
|
50
|
+
if not label:
|
|
51
|
+
raise HTTPException(
|
|
52
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
53
|
+
detail="Annotation label not found",
|
|
54
|
+
)
|
|
55
|
+
return label
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@annotations_label_router.put("/annotation_labels/{label_id}")
|
|
59
|
+
def update_annotation_label(
|
|
60
|
+
label_id: UUID,
|
|
61
|
+
label_input: AnnotationLabelCreate,
|
|
62
|
+
session: SessionDep,
|
|
63
|
+
) -> AnnotationLabelTable:
|
|
64
|
+
"""Update an existing annotation label in the database."""
|
|
65
|
+
label = annotation_label_resolver.update(
|
|
66
|
+
session=session, label_id=label_id, label_data=label_input
|
|
67
|
+
)
|
|
68
|
+
if not label:
|
|
69
|
+
raise HTTPException(
|
|
70
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
71
|
+
detail="Annotation label not found",
|
|
72
|
+
)
|
|
73
|
+
return label
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@annotations_label_router.delete("/annotation_labels/{label_id}")
|
|
77
|
+
def delete_annotation_label(
|
|
78
|
+
label_id: UUID,
|
|
79
|
+
session: SessionDep,
|
|
80
|
+
) -> dict[str, str]:
|
|
81
|
+
"""Delete an annotation label from the database."""
|
|
82
|
+
if not annotation_label_resolver.delete(session=session, label_id=label_id):
|
|
83
|
+
raise HTTPException(
|
|
84
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
85
|
+
detail="Annotation label not found",
|
|
86
|
+
)
|
|
87
|
+
return {"status": "deleted"}
|