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,75 @@
|
|
|
1
|
+
"""Implementation of get_dimension_bounds function for images."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from sqlmodel import Session, col, func, select
|
|
8
|
+
from sqlmodel.sql.expression import Select
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
|
|
11
|
+
from lightly_studio.models.annotation_label import AnnotationLabelTable
|
|
12
|
+
from lightly_studio.models.image import ImageTable
|
|
13
|
+
from lightly_studio.models.sample import SampleTable
|
|
14
|
+
from lightly_studio.models.tag import TagTable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_dimension_bounds(
|
|
18
|
+
session: Session,
|
|
19
|
+
dataset_id: UUID,
|
|
20
|
+
annotation_label_ids: list[UUID] | None = None,
|
|
21
|
+
tag_ids: list[UUID] | None = None,
|
|
22
|
+
) -> dict[str, int]:
|
|
23
|
+
"""Get min and max dimensions of samples in a dataset."""
|
|
24
|
+
# Prepare the base query for dimensions
|
|
25
|
+
query: Select[tuple[int | None, int | None, int | None, int | None]] = select(
|
|
26
|
+
func.min(ImageTable.width).label("min_width"),
|
|
27
|
+
func.max(ImageTable.width).label("max_width"),
|
|
28
|
+
func.min(ImageTable.height).label("min_height"),
|
|
29
|
+
func.max(ImageTable.height).label("max_height"),
|
|
30
|
+
)
|
|
31
|
+
query = query.join(ImageTable.sample)
|
|
32
|
+
|
|
33
|
+
if annotation_label_ids:
|
|
34
|
+
# Subquery to filter samples matching all annotation labels
|
|
35
|
+
label_filter = (
|
|
36
|
+
select(ImageTable.sample_id)
|
|
37
|
+
.join(ImageTable.sample)
|
|
38
|
+
.join(
|
|
39
|
+
AnnotationBaseTable,
|
|
40
|
+
col(ImageTable.sample_id) == col(AnnotationBaseTable.parent_sample_id),
|
|
41
|
+
)
|
|
42
|
+
.join(
|
|
43
|
+
AnnotationLabelTable,
|
|
44
|
+
col(AnnotationBaseTable.annotation_label_id)
|
|
45
|
+
== col(AnnotationLabelTable.annotation_label_id),
|
|
46
|
+
)
|
|
47
|
+
.where(
|
|
48
|
+
SampleTable.dataset_id == dataset_id,
|
|
49
|
+
col(AnnotationLabelTable.annotation_label_id).in_(annotation_label_ids),
|
|
50
|
+
)
|
|
51
|
+
.group_by(col(ImageTable.sample_id))
|
|
52
|
+
.having(
|
|
53
|
+
func.count(col(AnnotationLabelTable.annotation_label_id).distinct())
|
|
54
|
+
== len(annotation_label_ids)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
# Filter the dimension query based on the subquery
|
|
58
|
+
query = query.where(col(ImageTable.sample_id).in_(label_filter))
|
|
59
|
+
else:
|
|
60
|
+
# If no labels specified, filter dimensions
|
|
61
|
+
# for all samples in the dataset
|
|
62
|
+
query = query.where(SampleTable.dataset_id == dataset_id)
|
|
63
|
+
|
|
64
|
+
if tag_ids:
|
|
65
|
+
query = (
|
|
66
|
+
query.join(SampleTable.tags)
|
|
67
|
+
.where(SampleTable.tags.any(col(TagTable.tag_id).in_(tag_ids)))
|
|
68
|
+
.distinct()
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Note: We use SQLAlchemy's session.execute instead of SQLModel's
|
|
72
|
+
# ession.exec to be able to fetch the columns with names with the
|
|
73
|
+
# `mappings()` method.
|
|
74
|
+
result = session.execute(query).mappings().one()
|
|
75
|
+
return {key: value for key, value in result.items() if value is not None}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Implementation of get_many_by_id function for images."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from sqlmodel import Session, col, select
|
|
8
|
+
|
|
9
|
+
from lightly_studio.models.image import ImageTable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_many_by_id(session: Session, sample_ids: list[UUID]) -> list[ImageTable]:
|
|
13
|
+
"""Retrieve multiple samples by their IDs.
|
|
14
|
+
|
|
15
|
+
Output order matches the input order.
|
|
16
|
+
"""
|
|
17
|
+
results = session.exec(
|
|
18
|
+
select(ImageTable).where(col(ImageTable.sample_id).in_(sample_ids))
|
|
19
|
+
).all()
|
|
20
|
+
# Return samples in the same order as the input IDs
|
|
21
|
+
sample_map = {sample.sample_id: sample for sample in results}
|
|
22
|
+
return [sample_map[id_] for id_ in sample_ids if id_ in sample_map]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Implementation of get_samples_excluding function for images."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session, col, func, select
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.image import ImageTable
|
|
11
|
+
from lightly_studio.models.sample import SampleTable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_samples_excluding(
|
|
15
|
+
session: Session,
|
|
16
|
+
dataset_id: UUID,
|
|
17
|
+
excluded_sample_ids: list[UUID],
|
|
18
|
+
limit: int | None = None,
|
|
19
|
+
) -> Sequence[ImageTable]:
|
|
20
|
+
"""Get random samples excluding specified sample IDs.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
session: The database session.
|
|
24
|
+
dataset_id: The dataset ID to filter by.
|
|
25
|
+
excluded_sample_ids: List of sample IDs to exclude from the result.
|
|
26
|
+
limit: Maximum number of samples to return.
|
|
27
|
+
If None, returns all matches.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of samples not associated with the excluded IDs.
|
|
31
|
+
"""
|
|
32
|
+
query = (
|
|
33
|
+
select(ImageTable)
|
|
34
|
+
.join(ImageTable.sample)
|
|
35
|
+
.where(SampleTable.dataset_id == dataset_id)
|
|
36
|
+
.where(col(SampleTable.sample_id).not_in(excluded_sample_ids))
|
|
37
|
+
.order_by(func.random())
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if limit is not None:
|
|
41
|
+
query = query.limit(limit)
|
|
42
|
+
|
|
43
|
+
return session.exec(query).all()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Metadata resolver module."""
|
|
2
|
+
|
|
3
|
+
from lightly_studio.resolvers.metadata_resolver.sample import (
|
|
4
|
+
bulk_update_metadata,
|
|
5
|
+
get_by_sample_id,
|
|
6
|
+
get_value_for_sample,
|
|
7
|
+
set_value_for_sample,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"bulk_update_metadata",
|
|
12
|
+
"get_by_sample_id",
|
|
13
|
+
"get_value_for_sample",
|
|
14
|
+
"set_value_for_sample",
|
|
15
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Generic metadata filtering utilities."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Dict, List, Literal, Protocol, Type, TypeVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from sqlalchemy import text
|
|
9
|
+
|
|
10
|
+
from lightly_studio.type_definitions import QueryType
|
|
11
|
+
|
|
12
|
+
# Type variables for generic constraints
|
|
13
|
+
T = TypeVar("T", bound=BaseModel)
|
|
14
|
+
M = TypeVar("M", bound="HasMetadata")
|
|
15
|
+
|
|
16
|
+
# Valid operators for metadata filtering
|
|
17
|
+
MetadataOperator = Literal[">", "<", "==", ">=", "<=", "!="]
|
|
18
|
+
|
|
19
|
+
# Default metadata column name
|
|
20
|
+
METADATA_COLUMN = "metadata.data"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HasMetadata(Protocol):
|
|
24
|
+
"""Protocol for models that have metadata."""
|
|
25
|
+
|
|
26
|
+
data: Dict[str, Any]
|
|
27
|
+
metadata_schema: Dict[str, str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MetadataFilter(BaseModel):
|
|
31
|
+
"""Encapsulates a single metadata filter condition."""
|
|
32
|
+
|
|
33
|
+
key: str
|
|
34
|
+
op: MetadataOperator
|
|
35
|
+
value: Any
|
|
36
|
+
|
|
37
|
+
def model_post_init(self, __context: Any) -> None:
|
|
38
|
+
"""Post-initialization hook to serialize string values."""
|
|
39
|
+
# Pre-serialize string values for JSON comparison
|
|
40
|
+
if isinstance(self.value, str):
|
|
41
|
+
# Avoid double-serialization
|
|
42
|
+
try:
|
|
43
|
+
json.loads(self.value)
|
|
44
|
+
# Already serialized, don't serialize again
|
|
45
|
+
except (json.JSONDecodeError, TypeError):
|
|
46
|
+
# Not serialized, serialize it
|
|
47
|
+
self.value = json.dumps(self.value)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Metadata:
|
|
51
|
+
"""Helper class for creating metadata filters with operator syntax."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, key: str) -> None:
|
|
54
|
+
"""Initialize metadata filter with key."""
|
|
55
|
+
self.key = key
|
|
56
|
+
|
|
57
|
+
def __gt__(self, value: Any) -> MetadataFilter:
|
|
58
|
+
"""Create greater than filter."""
|
|
59
|
+
return MetadataFilter(key=self.key, op=">", value=value)
|
|
60
|
+
|
|
61
|
+
def __lt__(self, value: Any) -> MetadataFilter:
|
|
62
|
+
"""Create less than filter."""
|
|
63
|
+
return MetadataFilter(key=self.key, op="<", value=value)
|
|
64
|
+
|
|
65
|
+
def __ge__(self, value: Any) -> MetadataFilter:
|
|
66
|
+
"""Create greater than or equal filter."""
|
|
67
|
+
return MetadataFilter(key=self.key, op=">=", value=value)
|
|
68
|
+
|
|
69
|
+
def __le__(self, value: Any) -> MetadataFilter:
|
|
70
|
+
"""Create less than or equal filter."""
|
|
71
|
+
return MetadataFilter(key=self.key, op="<=", value=value)
|
|
72
|
+
|
|
73
|
+
def __eq__(self, value: Any) -> MetadataFilter: # type: ignore
|
|
74
|
+
"""Create equality filter."""
|
|
75
|
+
return MetadataFilter(key=self.key, op="==", value=value)
|
|
76
|
+
|
|
77
|
+
def __ne__(self, value: Any) -> MetadataFilter: # type: ignore
|
|
78
|
+
"""Create not equal filter."""
|
|
79
|
+
return MetadataFilter(key=self.key, op="!=", value=value)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _sanitize_param_name(field: str) -> str:
|
|
83
|
+
"""Sanitize field name for use as SQL parameter name.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
field: The field name (may contain dots for nested paths).
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A sanitized parameter name safe for SQL binding.
|
|
90
|
+
"""
|
|
91
|
+
# Replace dots and other problematic characters with underscores
|
|
92
|
+
return re.sub(r"[^a-zA-Z0-9_]", "_", field)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def apply_metadata_filters(
|
|
96
|
+
query: QueryType,
|
|
97
|
+
metadata_filters: List[MetadataFilter],
|
|
98
|
+
*,
|
|
99
|
+
metadata_model: Type[M],
|
|
100
|
+
metadata_join_condition: Any,
|
|
101
|
+
) -> QueryType:
|
|
102
|
+
"""Apply metadata filters to a query.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
query: The base query to filter.
|
|
106
|
+
metadata_filters: The list of metadata filters to apply.
|
|
107
|
+
metadata_model: The metadata table/model class.
|
|
108
|
+
metadata_join_condition: The join condition between the main table
|
|
109
|
+
and metadata table.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The filtered query.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If any field name contains invalid characters.
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```python
|
|
119
|
+
# Simple filters (AND by default)
|
|
120
|
+
query = apply_metadata_filters(
|
|
121
|
+
query,
|
|
122
|
+
metadata_filters=[
|
|
123
|
+
Metadata("temperature") > 25,
|
|
124
|
+
Metadata("location") == "city",
|
|
125
|
+
],
|
|
126
|
+
metadata_model=SampleMetadataTable,
|
|
127
|
+
metadata_join_condition=SampleMetadataTable.sample_id ==
|
|
128
|
+
ImageTable.sample_id,
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
"""
|
|
132
|
+
if not metadata_filters:
|
|
133
|
+
return query
|
|
134
|
+
|
|
135
|
+
# Apply the filters using JSON extraction
|
|
136
|
+
query = query.join(
|
|
137
|
+
metadata_model,
|
|
138
|
+
metadata_join_condition,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for i, meta_filter in enumerate(metadata_filters):
|
|
142
|
+
field = meta_filter.key
|
|
143
|
+
value = meta_filter.value
|
|
144
|
+
op = meta_filter.op
|
|
145
|
+
|
|
146
|
+
json_path = "$." + field
|
|
147
|
+
# Add unique identifier to parameter name to avoid conflicts
|
|
148
|
+
param_name = f"{_sanitize_param_name(field)}_{i}"
|
|
149
|
+
|
|
150
|
+
# Build the condition based on value type
|
|
151
|
+
if isinstance(value, (int, float)):
|
|
152
|
+
# For numeric values, use json_extract with CAST
|
|
153
|
+
condition = (
|
|
154
|
+
f"CAST(json_extract({METADATA_COLUMN}, '{json_path}') AS FLOAT) {op} :{param_name}"
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
# For string values, use json_extract with parameter binding
|
|
158
|
+
condition = f"json_extract({METADATA_COLUMN}, '{json_path}') {op} :{param_name}"
|
|
159
|
+
|
|
160
|
+
# Apply the condition (same for both types)
|
|
161
|
+
query = query.where(text(condition).bindparams(**{param_name: value}))
|
|
162
|
+
|
|
163
|
+
return query
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Resolvers for metadata operations."""
|
|
2
|
+
|
|
3
|
+
from .bulk_update_metadata import (
|
|
4
|
+
bulk_update_metadata,
|
|
5
|
+
)
|
|
6
|
+
from .get_by_sample_id import (
|
|
7
|
+
get_by_sample_id,
|
|
8
|
+
)
|
|
9
|
+
from .get_value_for_sample import (
|
|
10
|
+
get_value_for_sample,
|
|
11
|
+
)
|
|
12
|
+
from .set_value_for_sample import (
|
|
13
|
+
set_value_for_sample,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"bulk_update_metadata",
|
|
18
|
+
"get_by_sample_id",
|
|
19
|
+
"get_value_for_sample",
|
|
20
|
+
"set_value_for_sample",
|
|
21
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Resolver for operations for setting metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session, col, select
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.metadata import SampleMetadataTable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def bulk_update_metadata(
|
|
14
|
+
session: Session,
|
|
15
|
+
sample_metadata: list[tuple[UUID, dict[str, Any]]],
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Bulk insert or update metadata for multiple samples.
|
|
18
|
+
|
|
19
|
+
If a sample does not have metadata, a new metadata row is created.
|
|
20
|
+
If a sample already has metadata, the new key-value pairs are merged with the existing metadata.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
session: The database session.
|
|
24
|
+
sample_metadata: List of (sample_id, metadata_dict) tuples.
|
|
25
|
+
"""
|
|
26
|
+
# TODO(Mihnea, 10/2025): Consider using SQLAlchemy's bulk operations
|
|
27
|
+
# (Session.bulk_insert/update_mappings) if performance becomes a bottleneck.
|
|
28
|
+
if not sample_metadata:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# Get all existing metadata rows for the given sample IDs.
|
|
32
|
+
sample_ids = [s[0] for s in sample_metadata]
|
|
33
|
+
existing_metadata = session.exec(
|
|
34
|
+
select(SampleMetadataTable).where(col(SampleMetadataTable.sample_id).in_(sample_ids))
|
|
35
|
+
).all()
|
|
36
|
+
sample_id_to_existing_metadata = {meta.sample_id: meta for meta in existing_metadata}
|
|
37
|
+
|
|
38
|
+
for sample_id, new_metadata in sample_metadata:
|
|
39
|
+
metadata = sample_id_to_existing_metadata.get(
|
|
40
|
+
sample_id, SampleMetadataTable(sample_id=sample_id)
|
|
41
|
+
)
|
|
42
|
+
for key, value in new_metadata.items():
|
|
43
|
+
metadata.set_value(key, value)
|
|
44
|
+
session.add(metadata)
|
|
45
|
+
|
|
46
|
+
session.commit()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Resolver for operations for retrieving metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from sqlmodel import Session, select
|
|
8
|
+
|
|
9
|
+
from lightly_studio.models.metadata import SampleMetadataTable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_by_sample_id(session: Session, sample_id: UUID) -> SampleMetadataTable | None:
|
|
13
|
+
"""Retrieve the metadata object for a given sample.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
session: The database session.
|
|
17
|
+
sample_id: The sample's UUID.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
The CustomMetadataTable instance or None if not found.
|
|
21
|
+
"""
|
|
22
|
+
return session.exec(
|
|
23
|
+
select(SampleMetadataTable).where(SampleMetadataTable.sample_id == sample_id)
|
|
24
|
+
).one_or_none()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Resolver for operations for retrieving metadata info."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import Float, func
|
|
8
|
+
from sqlmodel import Session, col, select
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.metadata import (
|
|
11
|
+
MetadataInfoView,
|
|
12
|
+
SampleMetadataTable,
|
|
13
|
+
)
|
|
14
|
+
from lightly_studio.models.sample import SampleTable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_all_metadata_keys_and_schema(
|
|
18
|
+
session: Session,
|
|
19
|
+
dataset_id: UUID,
|
|
20
|
+
) -> list[MetadataInfoView]:
|
|
21
|
+
"""Get all unique metadata keys and their schema for a dataset.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
session: The database session.
|
|
25
|
+
dataset_id: The dataset's UUID.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of dicts with 'name', 'type', and optionally 'min'/'max' for numerical types.
|
|
29
|
+
"""
|
|
30
|
+
# Query all metadata_schema dicts for samples in the dataset
|
|
31
|
+
rows = session.exec(
|
|
32
|
+
select(SampleMetadataTable.metadata_schema)
|
|
33
|
+
.select_from(SampleTable)
|
|
34
|
+
.join(
|
|
35
|
+
SampleMetadataTable,
|
|
36
|
+
col(SampleMetadataTable.sample_id) == col(SampleTable.sample_id),
|
|
37
|
+
)
|
|
38
|
+
.where(SampleTable.dataset_id == dataset_id)
|
|
39
|
+
).all()
|
|
40
|
+
# Merge all schemas
|
|
41
|
+
merged: dict[str, str] = {}
|
|
42
|
+
for schema_dict in rows:
|
|
43
|
+
merged.update(schema_dict)
|
|
44
|
+
|
|
45
|
+
# Get min and max values for numerical metadata
|
|
46
|
+
result = []
|
|
47
|
+
for key, metadata_type in merged.items():
|
|
48
|
+
metadata_info = MetadataInfoView(name=key, type=metadata_type)
|
|
49
|
+
|
|
50
|
+
# Add min and max for numerical types
|
|
51
|
+
if metadata_type in ["integer", "float"]:
|
|
52
|
+
min_max_values = _get_metadata_min_max_values(session, dataset_id, key, metadata_type)
|
|
53
|
+
if min_max_values:
|
|
54
|
+
metadata_info.min = min_max_values[0]
|
|
55
|
+
metadata_info.max = min_max_values[1]
|
|
56
|
+
|
|
57
|
+
result.append(metadata_info)
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_metadata_min_max_values(
|
|
63
|
+
session: Session,
|
|
64
|
+
dataset_id: UUID,
|
|
65
|
+
metadata_key: str,
|
|
66
|
+
metadata_type: str,
|
|
67
|
+
) -> tuple[int, int] | tuple[float, float] | None:
|
|
68
|
+
"""Get min and max values for a specific numerical metadata key.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
session: The database session.
|
|
72
|
+
dataset_id: The dataset's UUID.
|
|
73
|
+
metadata_key: The metadata key to get min/max for.
|
|
74
|
+
metadata_type: The metadata type ("integer" or "float").
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Tuple with 'min' and 'max' values, or None if no values found.
|
|
78
|
+
"""
|
|
79
|
+
# Build JSON path for the metadata key.
|
|
80
|
+
json_path = f"$.{metadata_key}"
|
|
81
|
+
|
|
82
|
+
query = (
|
|
83
|
+
select(
|
|
84
|
+
func.min(func.cast(func.json_extract(SampleMetadataTable.data, json_path), Float)),
|
|
85
|
+
func.max(func.cast(func.json_extract(SampleMetadataTable.data, json_path), Float)),
|
|
86
|
+
)
|
|
87
|
+
.select_from(SampleTable)
|
|
88
|
+
.join(SampleMetadataTable, col(SampleMetadataTable.sample_id) == col(SampleTable.sample_id))
|
|
89
|
+
.where(
|
|
90
|
+
SampleTable.dataset_id == dataset_id,
|
|
91
|
+
func.json_extract(SampleMetadataTable.data, json_path).is_not(None),
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
result = session.exec(query).first()
|
|
96
|
+
|
|
97
|
+
if result and result[0] is not None and result[1] is not None:
|
|
98
|
+
# Convert to appropriate type
|
|
99
|
+
if metadata_type == "integer":
|
|
100
|
+
return int(result[0]), int(result[1])
|
|
101
|
+
if metadata_type == "float":
|
|
102
|
+
return float(result[0]), float(result[1])
|
|
103
|
+
|
|
104
|
+
return None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Resolver for operations for retrieving metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session
|
|
9
|
+
|
|
10
|
+
from .get_by_sample_id import get_by_sample_id
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_value_for_sample(session: Session, sample_id: UUID, key: str) -> Any | None:
|
|
14
|
+
"""Get a specific metadata value for a sample.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
session: The database session.
|
|
18
|
+
sample_id: The sample's UUID.
|
|
19
|
+
key: The metadata key.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The value for the given key, or None if not found.
|
|
23
|
+
"""
|
|
24
|
+
metadata = get_by_sample_id(session=session, sample_id=sample_id)
|
|
25
|
+
if metadata is None:
|
|
26
|
+
return None
|
|
27
|
+
return metadata.data.get(key)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Resolver for operations for setting metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.metadata import (
|
|
11
|
+
SampleMetadataTable,
|
|
12
|
+
)
|
|
13
|
+
from lightly_studio.resolvers.metadata_resolver.sample.get_by_sample_id import (
|
|
14
|
+
get_by_sample_id,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def set_value_for_sample(
|
|
19
|
+
session: Session,
|
|
20
|
+
sample_id: UUID,
|
|
21
|
+
key: str,
|
|
22
|
+
value: Any,
|
|
23
|
+
) -> SampleMetadataTable:
|
|
24
|
+
"""Set a specific metadata value for a sample.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session: The database session.
|
|
28
|
+
sample_id: The sample's UUID.
|
|
29
|
+
key: The metadata key.
|
|
30
|
+
value: The value to set.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The updated CustomMetadataTable instance.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If the value type doesn't match the schema.
|
|
37
|
+
"""
|
|
38
|
+
metadata = get_by_sample_id(session=session, sample_id=sample_id)
|
|
39
|
+
if metadata is None:
|
|
40
|
+
# Create new metadata row if it does not exist
|
|
41
|
+
metadata = SampleMetadataTable(
|
|
42
|
+
sample_id=sample_id,
|
|
43
|
+
data={},
|
|
44
|
+
metadata_schema={},
|
|
45
|
+
)
|
|
46
|
+
session.add(metadata)
|
|
47
|
+
|
|
48
|
+
metadata.set_value(key, value)
|
|
49
|
+
|
|
50
|
+
# Commit changes and refresh the object
|
|
51
|
+
session.commit()
|
|
52
|
+
session.refresh(metadata)
|
|
53
|
+
return metadata
|