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,14 @@
|
|
|
1
|
+
from .boolean_expression import AND, NOT, OR
|
|
2
|
+
from .dataset_query import DatasetQuery
|
|
3
|
+
from .order_by import OrderByExpression, OrderByField
|
|
4
|
+
from .sample_field import SampleField
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"AND",
|
|
8
|
+
"NOT",
|
|
9
|
+
"OR",
|
|
10
|
+
"DatasetQuery",
|
|
11
|
+
"OrderByExpression",
|
|
12
|
+
"OrderByField",
|
|
13
|
+
"SampleField",
|
|
14
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Classes for boolean expressions in dataset queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import ColumnElement, and_, false, not_, or_, true
|
|
6
|
+
|
|
7
|
+
from lightly_studio.core.dataset_query.match_expression import MatchExpression
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AND(MatchExpression):
|
|
11
|
+
"""Logical AND operation between other MatchExpression objects."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, *terms: MatchExpression) -> None:
|
|
14
|
+
"""Initialize AND Expression with multiple MatchExpression terms.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
terms: The MatchExpression instances to combine with AND. They can also be nested.
|
|
18
|
+
"""
|
|
19
|
+
self.terms = terms
|
|
20
|
+
|
|
21
|
+
def get(self) -> ColumnElement[bool]:
|
|
22
|
+
"""Combine expressions of all terms using AND.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The combined SQLAlchemy expression.
|
|
26
|
+
"""
|
|
27
|
+
return and_(true(), *(term.get() for term in self.terms))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OR(MatchExpression):
|
|
31
|
+
"""Logical OR operation between other MatchExpression objects."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *terms: MatchExpression) -> None:
|
|
34
|
+
"""Initialize OR Expression with multiple MatchExpression terms.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
terms: The MatchExpression instances to combine with OR. They can also be nested.
|
|
38
|
+
"""
|
|
39
|
+
self.terms = terms
|
|
40
|
+
|
|
41
|
+
def get(self) -> ColumnElement[bool]:
|
|
42
|
+
"""Combine expressions of all terms using OR.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The combined SQLAlchemy expression.
|
|
46
|
+
"""
|
|
47
|
+
return or_(false(), *(term.get() for term in self.terms))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class NOT(MatchExpression):
|
|
51
|
+
"""Logical NOT operation for a MatchExpression object."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, term: MatchExpression) -> None:
|
|
54
|
+
"""Initialize NOT Expression with a MatchExpression term.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
term: The MatchExpression to be negated. It can also be nested.
|
|
58
|
+
"""
|
|
59
|
+
self.term = term
|
|
60
|
+
|
|
61
|
+
def get(self) -> ColumnElement[bool]:
|
|
62
|
+
"""Negate the expression of the term using NOT.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The combined SQLAlchemy expression.
|
|
66
|
+
"""
|
|
67
|
+
return not_(self.term.get())
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Dataset query utilities for filtering, ordering, and slicing samples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterator
|
|
6
|
+
|
|
7
|
+
from sqlmodel import Session, select
|
|
8
|
+
|
|
9
|
+
from lightly_studio.core.dataset_query.match_expression import MatchExpression
|
|
10
|
+
from lightly_studio.core.dataset_query.order_by import OrderByExpression, OrderByField
|
|
11
|
+
from lightly_studio.core.dataset_query.sample_field import SampleField
|
|
12
|
+
from lightly_studio.core.image_sample import ImageSample
|
|
13
|
+
from lightly_studio.export.export_dataset import DatasetExport
|
|
14
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
15
|
+
from lightly_studio.models.image import ImageTable
|
|
16
|
+
from lightly_studio.models.sample import SampleTable
|
|
17
|
+
from lightly_studio.resolvers import tag_resolver
|
|
18
|
+
from lightly_studio.selection.select import Selection
|
|
19
|
+
|
|
20
|
+
_SliceType = slice # to avoid shadowing built-in slice in type annotations
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DatasetQuery:
|
|
24
|
+
"""Class for executing a query on a dataset.
|
|
25
|
+
|
|
26
|
+
# Filtering, ordering, and slicing samples in a dataset
|
|
27
|
+
Allows filtering, ordering, and slicing of samples in a dataset.
|
|
28
|
+
This class can be accessed via calling `.query()` on a Dataset instance.
|
|
29
|
+
```python
|
|
30
|
+
dataset : Dataset = ...
|
|
31
|
+
query = dataset.query()
|
|
32
|
+
```
|
|
33
|
+
The `match()`, `order_by()`, and `slice()` methods can be chained in this order.
|
|
34
|
+
You can also access the methods directly on the Dataset instance:
|
|
35
|
+
```python
|
|
36
|
+
dataset.match(...) # shorthand for dataset.query().match(...)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The object is converted to a SQL query that is lazily evaluated when iterating over
|
|
40
|
+
it or converting it to a list.
|
|
41
|
+
|
|
42
|
+
## match() - Filtering samples
|
|
43
|
+
Filtering is done via the `match()` method.
|
|
44
|
+
```python
|
|
45
|
+
from lightly_studio.core.dataset_query import SampleField
|
|
46
|
+
|
|
47
|
+
query_1 = dataset.query().match(SampleField.width > 100)
|
|
48
|
+
query_2 = dataset.query().match(SampleField.tags.contains('cat'))
|
|
49
|
+
```
|
|
50
|
+
AND and OR operators are available for combining multiple conditions.
|
|
51
|
+
```python
|
|
52
|
+
from lightly_studio.core.dataset_query import SampleField, AND, OR
|
|
53
|
+
|
|
54
|
+
query = dataset.query().match(
|
|
55
|
+
AND(
|
|
56
|
+
SampleField.height < 200,
|
|
57
|
+
OR(
|
|
58
|
+
SampleField.file_name == 'image.png',
|
|
59
|
+
SampleField.file_name == 'image2.png',
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## order_by() - Ordering samples
|
|
66
|
+
The results can be ordered by using `order_by()`. For tie-breaking, multiple fields
|
|
67
|
+
can be provided. The first field has the highest priority. The default is
|
|
68
|
+
ascending order. To order in descending order, use `OrderByField(...).desc()`.
|
|
69
|
+
```python
|
|
70
|
+
from lightly_studio.core.dataset_query import OrderByField, SampleField
|
|
71
|
+
query = query.order_by(
|
|
72
|
+
OrderByField(SampleField.width),
|
|
73
|
+
OrderByField(SampleField.file_name).desc()
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## slice() - Slicing samples
|
|
78
|
+
Slicing can be applied via `slice()` or bracket notation.
|
|
79
|
+
```python
|
|
80
|
+
query = query.slice(offset=10, limit=20)
|
|
81
|
+
query = query[10:30] # equivalent to slice(offset=10, limit=20)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
# Usage of the filtered, ordered and sliced query
|
|
85
|
+
|
|
86
|
+
## Iterating and converting to list
|
|
87
|
+
Finally, the query can be executed by iterating over it or converting to a list.
|
|
88
|
+
```python
|
|
89
|
+
for sample in query:
|
|
90
|
+
print(sample.file_name)
|
|
91
|
+
samples = query.to_list()
|
|
92
|
+
```
|
|
93
|
+
The samples returned are instances of the `Sample` class. They are writable, and
|
|
94
|
+
changes to them will be persisted to the database.
|
|
95
|
+
|
|
96
|
+
## Adding tags to matching samples
|
|
97
|
+
The filtered set can also be used to add a tag to all matching samples.
|
|
98
|
+
```python
|
|
99
|
+
query.add_tag('my_tag')
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Selecting a subset of samples using smart selection
|
|
103
|
+
A Selection interface can be created from the current query results. It will only
|
|
104
|
+
select the samples matching the current query at the time of calling selection().
|
|
105
|
+
```python
|
|
106
|
+
# Choosing 100 diverse samples from the 'cat' tag.
|
|
107
|
+
# Save them under the tag name "diverse_cats".
|
|
108
|
+
selection = dataset.query().match(
|
|
109
|
+
SampleField.tags.contains('cat')
|
|
110
|
+
).selection()
|
|
111
|
+
selection.diverse(100, "diverse_cats")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Exporting the query results
|
|
115
|
+
An export interface can be created from the current query results.
|
|
116
|
+
```python
|
|
117
|
+
export = dataset.query().match(...).export()
|
|
118
|
+
export.to_coco_object_detections('/path/to/coco.json')
|
|
119
|
+
```
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, dataset: DatasetTable, session: Session) -> None:
|
|
123
|
+
"""Initialize with dataset and database session.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
dataset: The dataset to query.
|
|
127
|
+
session: Database session for executing queries.
|
|
128
|
+
"""
|
|
129
|
+
self.dataset = dataset
|
|
130
|
+
self.session = session
|
|
131
|
+
self.match_expression: MatchExpression | None = None
|
|
132
|
+
self.order_by_expressions: list[OrderByExpression] | None = None
|
|
133
|
+
self._slice: _SliceType | None = None
|
|
134
|
+
|
|
135
|
+
def match(self, match_expression: MatchExpression) -> DatasetQuery:
|
|
136
|
+
"""Store a field condition for filtering.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
match_expression: Defines the filter.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Self for method chaining.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
ValueError: If match() has already been called on this instance.
|
|
146
|
+
"""
|
|
147
|
+
if self.match_expression is not None:
|
|
148
|
+
raise ValueError("match() can only be called once per DatasetQuery instance")
|
|
149
|
+
|
|
150
|
+
self.match_expression = match_expression
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def order_by(self, *order_by: OrderByExpression) -> DatasetQuery:
|
|
154
|
+
"""Store ordering expressions.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
order_by: One or more ordering expressions. They are applied in order.
|
|
158
|
+
E.g. first ordering by sample width and then by sample file_name will
|
|
159
|
+
only order the samples with the same sample width by file_name.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Self for method chaining.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ValueError: If order_by() has already been called on this instance.
|
|
166
|
+
"""
|
|
167
|
+
if self.order_by_expressions:
|
|
168
|
+
raise ValueError("order_by() can only be called once per DatasetQuery instance")
|
|
169
|
+
|
|
170
|
+
self.order_by_expressions = list(order_by)
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def slice(self, offset: int = 0, limit: int | None = None) -> DatasetQuery:
|
|
174
|
+
"""Apply offset and limit to results.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
offset: Number of items to skip from beginning (default: 0).
|
|
178
|
+
limit: Maximum number of items to return (None = no limit).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Self for method chaining.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
ValueError: If slice() has already been called on this instance.
|
|
185
|
+
"""
|
|
186
|
+
if self._slice is not None:
|
|
187
|
+
raise ValueError("slice() can only be called once per DatasetQuery instance")
|
|
188
|
+
|
|
189
|
+
# Convert to slice object for internal consistency
|
|
190
|
+
stop = None if limit is None else offset + limit
|
|
191
|
+
self._slice = _SliceType(offset, stop)
|
|
192
|
+
return self
|
|
193
|
+
|
|
194
|
+
def __getitem__(self, key: _SliceType) -> DatasetQuery:
|
|
195
|
+
"""Enable bracket notation for slicing.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
key: A slice object (e.g., [10:20], [:50], [100:]).
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Self with slice applied.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
TypeError: If key is not a slice object.
|
|
205
|
+
ValueError: If slice contains unsupported features or conflicts with existing slice.
|
|
206
|
+
"""
|
|
207
|
+
if not isinstance(key, _SliceType):
|
|
208
|
+
raise TypeError(
|
|
209
|
+
"DatasetQuery only supports slice notation, not integer indexing. "
|
|
210
|
+
"Use execute() to get results as a list for element access."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Validate unsupported features
|
|
214
|
+
if key.step is not None:
|
|
215
|
+
raise ValueError("Strides are not supported. Use simple slices like [start:stop].")
|
|
216
|
+
|
|
217
|
+
if (key.start is not None and key.start < 0) or (key.stop is not None and key.stop < 0):
|
|
218
|
+
raise ValueError("Negative indices are not supported. Use positive indices only.")
|
|
219
|
+
|
|
220
|
+
# Check for conflicts with existing slice
|
|
221
|
+
if self._slice is not None:
|
|
222
|
+
raise ValueError("Cannot use bracket notation after slice() has been called.")
|
|
223
|
+
|
|
224
|
+
# Set slice and return self
|
|
225
|
+
self._slice = key
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
def __iter__(self) -> Iterator[ImageSample]:
|
|
229
|
+
"""Iterate over the query results.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Iterator of Sample objects from the database.
|
|
233
|
+
"""
|
|
234
|
+
# Build query
|
|
235
|
+
query = (
|
|
236
|
+
select(ImageTable)
|
|
237
|
+
.join(ImageTable.sample)
|
|
238
|
+
.where(SampleTable.dataset_id == self.dataset.dataset_id)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Apply filter if present
|
|
242
|
+
if self.match_expression:
|
|
243
|
+
query = query.where(self.match_expression.get())
|
|
244
|
+
|
|
245
|
+
# Apply ordering
|
|
246
|
+
if self.order_by_expressions:
|
|
247
|
+
for order_by in self.order_by_expressions:
|
|
248
|
+
query = order_by.apply(query)
|
|
249
|
+
else:
|
|
250
|
+
# Order by SampleField.created_at by default.
|
|
251
|
+
default_order_by = OrderByField(SampleField.created_at)
|
|
252
|
+
query = default_order_by.apply(query)
|
|
253
|
+
|
|
254
|
+
# Apply slicing if present
|
|
255
|
+
if self._slice is not None:
|
|
256
|
+
start = self._slice.start or 0
|
|
257
|
+
query = query.offset(start)
|
|
258
|
+
if self._slice.stop is not None:
|
|
259
|
+
limit = max(self._slice.stop - start, 0)
|
|
260
|
+
query = query.limit(limit)
|
|
261
|
+
|
|
262
|
+
# Execute query and yield results
|
|
263
|
+
for image_table in self.session.exec(query):
|
|
264
|
+
yield ImageSample(inner=image_table)
|
|
265
|
+
|
|
266
|
+
def to_list(self) -> list[ImageSample]:
|
|
267
|
+
"""Execute the query and return the results as a list.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
List of Sample objects from the database.
|
|
271
|
+
"""
|
|
272
|
+
return list(self)
|
|
273
|
+
|
|
274
|
+
def add_tag(self, tag_name: str) -> None:
|
|
275
|
+
"""Add a tag to all samples returned by this query.
|
|
276
|
+
|
|
277
|
+
First, creates the tag if it doesn't exist. Then applies the tag to all samples
|
|
278
|
+
that match the current query filters. Samples already having that tag are unchanged,
|
|
279
|
+
as the database prevents duplicates.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
tag_name: Name of the tag to add to matching samples.
|
|
283
|
+
"""
|
|
284
|
+
# Get or create the tag
|
|
285
|
+
tag = tag_resolver.get_or_create_sample_tag_by_name(
|
|
286
|
+
session=self.session, dataset_id=self.dataset.dataset_id, tag_name=tag_name
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Execute query to get matching samples
|
|
290
|
+
samples = self.to_list()
|
|
291
|
+
sample_ids = [sample.sample_id for sample in samples]
|
|
292
|
+
|
|
293
|
+
# Use resolver to bulk assign tag (handles validation and edge cases)
|
|
294
|
+
tag_resolver.add_sample_ids_to_tag_id(
|
|
295
|
+
session=self.session, tag_id=tag.tag_id, sample_ids=sample_ids
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
def selection(self) -> Selection:
|
|
299
|
+
"""Selection interface for this query.
|
|
300
|
+
|
|
301
|
+
The returned Selection snapshots the current query results immediately.
|
|
302
|
+
Mutating the query after calling this method will therefore not affect
|
|
303
|
+
the samples used by that Selection instance.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Selection interface operating on the current query result snapshot.
|
|
307
|
+
"""
|
|
308
|
+
input_sample_ids = (sample.sample_id for sample in self)
|
|
309
|
+
return Selection(
|
|
310
|
+
dataset_id=self.dataset.dataset_id,
|
|
311
|
+
session=self.session,
|
|
312
|
+
input_sample_ids=input_sample_ids,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def export(self) -> DatasetExport:
|
|
316
|
+
"""Return a DatasetExport instance which can export the dataset in various formats."""
|
|
317
|
+
return DatasetExport(session=self.session, samples=self)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Base field classes for building dataset queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any, Generic, TypeVar, Union
|
|
8
|
+
|
|
9
|
+
from sqlalchemy.orm import Mapped
|
|
10
|
+
|
|
11
|
+
from lightly_studio.core.dataset_query.field_expression import (
|
|
12
|
+
OrdinalFieldExpression,
|
|
13
|
+
StringFieldExpression,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Field(ABC):
|
|
20
|
+
"""Abstract base class for all field types in dataset queries."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def get_sqlmodel_field(self) -> Mapped[Any]:
|
|
24
|
+
"""Get the database column or property that this field represents.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The database column or property for queries.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OrdinalField(Field, Generic[T]):
|
|
32
|
+
"""Generic field for ordinal values that support comparison operations.
|
|
33
|
+
|
|
34
|
+
Ordinal values have a natural ordering and support all comparison operators:
|
|
35
|
+
>, <, >=, <=, ==, !=
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, column: Mapped[T]) -> None:
|
|
39
|
+
"""Initialize the ordinal field with a database column.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
column: The database column this field represents.
|
|
43
|
+
"""
|
|
44
|
+
self._column = column
|
|
45
|
+
|
|
46
|
+
def get_sqlmodel_field(self) -> Mapped[T]:
|
|
47
|
+
"""Get the ordinal database column or property.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The ordinal column for database queries.
|
|
51
|
+
"""
|
|
52
|
+
return self._column
|
|
53
|
+
|
|
54
|
+
def __gt__(self, other: T) -> OrdinalFieldExpression[T]:
|
|
55
|
+
"""Create a greater-than expression."""
|
|
56
|
+
return OrdinalFieldExpression(field=self, operator=">", value=other)
|
|
57
|
+
|
|
58
|
+
def __lt__(self, other: T) -> OrdinalFieldExpression[T]:
|
|
59
|
+
"""Create a less-than expression."""
|
|
60
|
+
return OrdinalFieldExpression(field=self, operator="<", value=other)
|
|
61
|
+
|
|
62
|
+
def __ge__(self, other: T) -> OrdinalFieldExpression[T]:
|
|
63
|
+
"""Create a greater-than-or-equal expression."""
|
|
64
|
+
return OrdinalFieldExpression(field=self, operator=">=", value=other)
|
|
65
|
+
|
|
66
|
+
def __le__(self, other: T) -> OrdinalFieldExpression[T]:
|
|
67
|
+
"""Create a less-than-or-equal expression."""
|
|
68
|
+
return OrdinalFieldExpression(field=self, operator="<=", value=other)
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other: T) -> OrdinalFieldExpression[T]: # type: ignore[override]
|
|
71
|
+
"""Create an equality expression."""
|
|
72
|
+
return OrdinalFieldExpression(field=self, operator="==", value=other)
|
|
73
|
+
|
|
74
|
+
def __ne__(self, other: T) -> OrdinalFieldExpression[T]: # type: ignore[override]
|
|
75
|
+
"""Create a not-equal expression."""
|
|
76
|
+
return OrdinalFieldExpression(field=self, operator="!=", value=other)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
NumericalField = OrdinalField[Union[float, int]]
|
|
80
|
+
DatetimeField = OrdinalField[datetime]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class StringField(Field):
|
|
84
|
+
"""Field for string values that supports equality operations.
|
|
85
|
+
|
|
86
|
+
Optional refactor when needed: Split into
|
|
87
|
+
- StringField(ABC) with the comparison operators.
|
|
88
|
+
- StringColumnField(StringField) for the __init__ and get_sqlmodel_field implementation.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, column: Mapped[str]) -> None:
|
|
92
|
+
"""Initialize the string field with a database column.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
column: The database column this field represents.
|
|
96
|
+
"""
|
|
97
|
+
self._column = column
|
|
98
|
+
|
|
99
|
+
def get_sqlmodel_field(self) -> Mapped[str]:
|
|
100
|
+
"""Get the string database column or property.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The string column for database queries.
|
|
104
|
+
"""
|
|
105
|
+
return self._column
|
|
106
|
+
|
|
107
|
+
def __eq__(self, other: str) -> StringFieldExpression: # type: ignore[override]
|
|
108
|
+
"""Create an equality expression."""
|
|
109
|
+
return StringFieldExpression(field=self, operator="==", value=other)
|
|
110
|
+
|
|
111
|
+
def __ne__(self, other: str) -> StringFieldExpression: # type: ignore[override]
|
|
112
|
+
"""Create a not-equal expression."""
|
|
113
|
+
return StringFieldExpression(field=self, operator="!=", value=other)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Field expressions for building specific query conditions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING, Callable, Generic, Literal, TypeVar, Union
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import ColumnElement
|
|
10
|
+
from sqlalchemy.orm import Mapped
|
|
11
|
+
|
|
12
|
+
from lightly_studio.core.dataset_query.match_expression import MatchExpression
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from lightly_studio.core.dataset_query.field import (
|
|
16
|
+
OrdinalField,
|
|
17
|
+
StringField,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
"""Conditions themselves, in the format <field> <operator> <value>
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
SampleField.file_name == "img1.jpg",
|
|
27
|
+
becomes StringField(file_name) == "img1.jpg",
|
|
28
|
+
becomes StringFieldExpression(field=StringField(file_name), operator="==", value="img1.jpg")
|
|
29
|
+
becomes SQLQuery.where(...)
|
|
30
|
+
"""
|
|
31
|
+
StringOperator = Literal["==", "!="]
|
|
32
|
+
OrdinalOperator = Literal[">", "<", "==", ">=", "<=", "!="]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class OrdinalFieldExpression(MatchExpression, Generic[T]):
|
|
37
|
+
"""Generic expression for ordinal field comparisons."""
|
|
38
|
+
|
|
39
|
+
field: OrdinalField[T]
|
|
40
|
+
operator: OrdinalOperator
|
|
41
|
+
value: T
|
|
42
|
+
|
|
43
|
+
def get(self) -> ColumnElement[bool]:
|
|
44
|
+
"""Return the SQLAlchemy expression for this ordinal field expression."""
|
|
45
|
+
table_property = self.field.get_sqlmodel_field()
|
|
46
|
+
operations: dict[
|
|
47
|
+
OrdinalOperator,
|
|
48
|
+
Callable[[Mapped[T], T], ColumnElement[bool]],
|
|
49
|
+
] = {
|
|
50
|
+
"<": lambda tp, v: tp < v,
|
|
51
|
+
"<=": lambda tp, v: tp <= v,
|
|
52
|
+
">": lambda tp, v: tp > v,
|
|
53
|
+
">=": lambda tp, v: tp >= v,
|
|
54
|
+
"==": lambda tp, v: tp == v,
|
|
55
|
+
"!=": lambda tp, v: tp != v,
|
|
56
|
+
}
|
|
57
|
+
return operations[self.operator](table_property, self.value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
NumericalFieldExpression = OrdinalFieldExpression[Union[float, int]]
|
|
61
|
+
DatetimeFieldExpression = OrdinalFieldExpression[datetime]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class StringFieldExpression(MatchExpression):
|
|
66
|
+
"""Expression for string field comparisons."""
|
|
67
|
+
|
|
68
|
+
field: StringField
|
|
69
|
+
operator: StringOperator
|
|
70
|
+
value: str
|
|
71
|
+
|
|
72
|
+
def get(self) -> ColumnElement[bool]:
|
|
73
|
+
"""Return the SQLAlchemy expression for this string field expression."""
|
|
74
|
+
table_property = self.field.get_sqlmodel_field()
|
|
75
|
+
operations: dict[StringOperator, Callable[[Mapped[str], str], ColumnElement[bool]]] = {
|
|
76
|
+
"==": lambda tp, v: tp == v,
|
|
77
|
+
"!=": lambda tp, v: tp != v,
|
|
78
|
+
}
|
|
79
|
+
return operations[self.operator](table_property, self.value)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Base classes for match expressions in dataset queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ColumnElement
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MatchExpression(ABC):
|
|
11
|
+
"""Base class for all match expressions that can be applied to database queries.
|
|
12
|
+
|
|
13
|
+
This class provides the foundation for implementing complex query expressions
|
|
14
|
+
that can be combined using AND/OR operations in the future.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def get(self) -> ColumnElement[bool]:
|
|
19
|
+
"""Get the SQLAlchemy expression for this match expression.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The combined SQLAlchemy expression.
|
|
23
|
+
"""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Classes for order by expressions in dataset queries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
from sqlmodel.sql.expression import SelectOfScalar
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from lightly_studio.core.dataset_query.field import Field
|
|
11
|
+
from lightly_studio.models.image import ImageTable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OrderByExpression(ABC):
|
|
15
|
+
"""Base class for all order by expressions that can be applied to database queries."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, *, ascending: bool = True) -> None:
|
|
18
|
+
"""Initialize the order by expression.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
ascending: Whether to order in ascending (True) or descending (False) order.
|
|
22
|
+
"""
|
|
23
|
+
self.ascending = ascending
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def apply(self, query: SelectOfScalar[ImageTable]) -> SelectOfScalar[ImageTable]:
|
|
27
|
+
"""Apply this ordering to a SQLModel Select query.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
query: The SQLModel Select query to modify.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The modified query after ordering
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def asc(self) -> Self:
|
|
37
|
+
"""Set the ordering to ascending.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Self for method chaining.
|
|
41
|
+
"""
|
|
42
|
+
self.ascending = True
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def desc(self) -> Self:
|
|
46
|
+
"""Set the ordering to descending.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Self for method chaining.
|
|
50
|
+
"""
|
|
51
|
+
self.ascending = False
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OrderByField(OrderByExpression):
|
|
56
|
+
"""Order by a specific field, either ascending or descending.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
field: The field to order by.
|
|
60
|
+
ascending: Whether to order in ascending (True) or descending (False) order.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, field: Field) -> None:
|
|
64
|
+
"""Initialize with field and order direction."""
|
|
65
|
+
super().__init__()
|
|
66
|
+
self.field = field
|
|
67
|
+
|
|
68
|
+
def apply(self, query: SelectOfScalar[ImageTable]) -> SelectOfScalar[ImageTable]:
|
|
69
|
+
"""Apply this ordering to a SQLModel Select query.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
query: The SQLModel Select query to modify.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The modified query after ordering
|
|
76
|
+
"""
|
|
77
|
+
if self.ascending:
|
|
78
|
+
return query.order_by(self.field.get_sqlmodel_field().asc())
|
|
79
|
+
return query.order_by(self.field.get_sqlmodel_field().desc())
|