kuration-sdk 0.1.0__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.
Files changed (199) hide show
  1. kuration/__init__.py +16 -0
  2. kuration/core/__init__.py +180 -0
  3. kuration/core/_pipeline_support.py +182 -0
  4. kuration/core/_registration.py +15 -0
  5. kuration/core/adapters/__init__.py +18 -0
  6. kuration/core/adapters/base.py +248 -0
  7. kuration/core/adapters/curated.py +208 -0
  8. kuration/core/adapters/hdf5.py +115 -0
  9. kuration/core/adapters/huggingface.py +171 -0
  10. kuration/core/adapters/jsonl.py +47 -0
  11. kuration/core/adapters/npz.py +89 -0
  12. kuration/core/adapters/parquet_dataset.py +137 -0
  13. kuration/core/adapters/timeseries.py +220 -0
  14. kuration/core/adapters/zarr_store.py +129 -0
  15. kuration/core/analyzers.py +540 -0
  16. kuration/core/binning.py +63 -0
  17. kuration/core/bounded.py +362 -0
  18. kuration/core/buffer.py +134 -0
  19. kuration/core/builtin_operators.py +358 -0
  20. kuration/core/checkpoints.py +419 -0
  21. kuration/core/cli/__init__.py +1 -0
  22. kuration/core/cli/_common.py +277 -0
  23. kuration/core/cli/check.py +124 -0
  24. kuration/core/cli/checkpoint.py +534 -0
  25. kuration/core/cli/curate.py +147 -0
  26. kuration/core/cli/diff.py +47 -0
  27. kuration/core/cli/discovery.py +65 -0
  28. kuration/core/cli/experiment.py +100 -0
  29. kuration/core/cli/explain.py +135 -0
  30. kuration/core/cli/export.py +335 -0
  31. kuration/core/cli/import_dataset.py +103 -0
  32. kuration/core/cli/infer_schema_cli.py +122 -0
  33. kuration/core/cli/init_job.py +148 -0
  34. kuration/core/cli/inspect.py +77 -0
  35. kuration/core/cli/job.py +218 -0
  36. kuration/core/cli/main.py +179 -0
  37. kuration/core/cli/migrate.py +38 -0
  38. kuration/core/cli/plugin_check.py +39 -0
  39. kuration/core/cli/preview.py +133 -0
  40. kuration/core/cli/report.py +111 -0
  41. kuration/core/cli/stats.py +80 -0
  42. kuration/core/cli/validate.py +123 -0
  43. kuration/core/columnar.py +232 -0
  44. kuration/core/curate.py +120 -0
  45. kuration/core/dsl/__init__.py +5 -0
  46. kuration/core/dsl/grammar.py +597 -0
  47. kuration/core/dsl/registry.py +42 -0
  48. kuration/core/execution.py +119 -0
  49. kuration/core/experiment.py +246 -0
  50. kuration/core/external.py +622 -0
  51. kuration/core/features.py +86 -0
  52. kuration/core/html_report.py +46 -0
  53. kuration/core/importing.py +971 -0
  54. kuration/core/infer.py +388 -0
  55. kuration/core/inspection.py +210 -0
  56. kuration/core/io.py +589 -0
  57. kuration/core/jsonutil.py +57 -0
  58. kuration/core/loaders.py +918 -0
  59. kuration/core/manifest.py +627 -0
  60. kuration/core/media.py +447 -0
  61. kuration/core/methods.py +414 -0
  62. kuration/core/output/__init__.py +21 -0
  63. kuration/core/output/base.py +71 -0
  64. kuration/core/output/containers.py +176 -0
  65. kuration/core/output/dataset.py +694 -0
  66. kuration/core/output/external_fill.py +318 -0
  67. kuration/core/output/fill.py +217 -0
  68. kuration/core/output/presets.py +119 -0
  69. kuration/core/output/shardwriter.py +269 -0
  70. kuration/core/output/spec.py +268 -0
  71. kuration/core/output/stream.py +119 -0
  72. kuration/core/output/track.py +53 -0
  73. kuration/core/pipeline.py +2492 -0
  74. kuration/core/planning.py +410 -0
  75. kuration/core/plugin_validation.py +211 -0
  76. kuration/core/plugins.py +332 -0
  77. kuration/core/progress.py +144 -0
  78. kuration/core/provenance.py +495 -0
  79. kuration/core/quality.py +241 -0
  80. kuration/core/registry.py +623 -0
  81. kuration/core/report.py +212 -0
  82. kuration/core/results.py +363 -0
  83. kuration/core/review.py +389 -0
  84. kuration/core/schema.py +394 -0
  85. kuration/core/schemas/job.schema.json +224 -0
  86. kuration/core/scorers.py +260 -0
  87. kuration/core/scoring.py +394 -0
  88. kuration/core/segment.py +145 -0
  89. kuration/core/select.py +200 -0
  90. kuration/core/stats.py +1308 -0
  91. kuration/core/strategy.py +593 -0
  92. kuration/core/tags.py +54 -0
  93. kuration/core/torch.py +67 -0
  94. kuration/core/transforms.py +700 -0
  95. kuration/core/versioning.py +203 -0
  96. kuration/core/work_paths.py +119 -0
  97. kuration/gameplay/__init__.py +94 -0
  98. kuration/gameplay/_stats.py +486 -0
  99. kuration/gameplay/action_state_consistency/__init__.py +279 -0
  100. kuration/gameplay/action_state_consistency/action_state.py +554 -0
  101. kuration/gameplay/action_state_consistency/anomaly_checker.py +654 -0
  102. kuration/gameplay/collision/__init__.py +168 -0
  103. kuration/gameplay/collision/asg_adapter.py +129 -0
  104. kuration/gameplay/collision/collision_operator.py +400 -0
  105. kuration/gameplay/collision/directional_collision_analyzer.py +581 -0
  106. kuration/gameplay/collision/kinematics.py +85 -0
  107. kuration/gameplay/collision/mesh_cache.py +682 -0
  108. kuration/gameplay/collision/physics.py +107 -0
  109. kuration/gameplay/collision/ramp_fitting.py +148 -0
  110. kuration/gameplay/collision/teapearce_adapter.py +187 -0
  111. kuration/gameplay/convenience.py +102 -0
  112. kuration/gameplay/pixel_shift/__init__.py +87 -0
  113. kuration/gameplay/pixel_shift/diamond_action_decode.py +625 -0
  114. kuration/gameplay/pixel_shift/pixel_shift_operator.py +638 -0
  115. kuration/gameplay/slam/__init__.py +243 -0
  116. kuration/gameplay/slam/_paths.py +26 -0
  117. kuration/gameplay/slam/_runtime.py +1022 -0
  118. kuration/gameplay/slam/_vendor/__init__.py +1 -0
  119. kuration/gameplay/slam/_vendor/depth_anything_3/LICENSE +201 -0
  120. kuration/gameplay/slam/_vendor/depth_anything_3/NOTICE +20 -0
  121. kuration/gameplay/slam/_vendor/depth_anything_3/__init__.py +0 -0
  122. kuration/gameplay/slam/_vendor/depth_anything_3/api.py +455 -0
  123. kuration/gameplay/slam/_vendor/depth_anything_3/cfg.py +149 -0
  124. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-base.yaml +45 -0
  125. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-giant.yaml +71 -0
  126. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-large.yaml +45 -0
  127. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-small.yaml +45 -0
  128. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3metric-large.yaml +28 -0
  129. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3mono-large.yaml +28 -0
  130. kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3nested-giant-large.yaml +10 -0
  131. kuration/gameplay/slam/_vendor/depth_anything_3/model/__init__.py +20 -0
  132. kuration/gameplay/slam/_vendor/depth_anything_3/model/cam_dec.py +45 -0
  133. kuration/gameplay/slam/_vendor/depth_anything_3/model/cam_enc.py +80 -0
  134. kuration/gameplay/slam/_vendor/depth_anything_3/model/da3.py +445 -0
  135. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/__init__.py +0 -0
  136. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/dinov2.py +64 -0
  137. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/__init__.py +25 -0
  138. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/attention.py +100 -0
  139. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/block.py +143 -0
  140. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/drop_path.py +35 -0
  141. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/layer_scale.py +31 -0
  142. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/mlp.py +40 -0
  143. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/patch_embed.py +94 -0
  144. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/rope.py +200 -0
  145. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/swiglu_ffn.py +62 -0
  146. kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/vision_transformer.py +458 -0
  147. kuration/gameplay/slam/_vendor/depth_anything_3/model/dpt.py +458 -0
  148. kuration/gameplay/slam/_vendor/depth_anything_3/model/dualdpt.py +488 -0
  149. kuration/gameplay/slam/_vendor/depth_anything_3/model/reference_view_selector.py +223 -0
  150. kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/__init__.py +0 -0
  151. kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/attention.py +109 -0
  152. kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/block.py +81 -0
  153. kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/head_utils.py +230 -0
  154. kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/transform.py +208 -0
  155. kuration/gameplay/slam/_vendor/depth_anything_3/registry.py +49 -0
  156. kuration/gameplay/slam/_vendor/depth_anything_3/specs.py +45 -0
  157. kuration/gameplay/slam/_vendor/depth_anything_3/utils/__init__.py +0 -0
  158. kuration/gameplay/slam/_vendor/depth_anything_3/utils/alignment.py +163 -0
  159. kuration/gameplay/slam/_vendor/depth_anything_3/utils/constants.py +270 -0
  160. kuration/gameplay/slam/_vendor/depth_anything_3/utils/export.py +20 -0
  161. kuration/gameplay/slam/_vendor/depth_anything_3/utils/geometry.py +498 -0
  162. kuration/gameplay/slam/_vendor/depth_anything_3/utils/io/__init__.py +0 -0
  163. kuration/gameplay/slam/_vendor/depth_anything_3/utils/io/input_processor.py +501 -0
  164. kuration/gameplay/slam/_vendor/depth_anything_3/utils/io/output_processor.py +172 -0
  165. kuration/gameplay/slam/_vendor/depth_anything_3/utils/logger.py +82 -0
  166. kuration/gameplay/slam/_vendor/depth_anything_3/utils/parallel_utils.py +133 -0
  167. kuration/gameplay/slam/_vendor/depth_anything_3/utils/pose_align.py +347 -0
  168. kuration/gameplay/slam/_vendor/depth_anything_3/utils/ray_utils.py +524 -0
  169. kuration/gameplay/slam/adapter.py +181 -0
  170. kuration/gameplay/slam/cli.py +317 -0
  171. kuration/gameplay/slam/demo.py +84 -0
  172. kuration/gameplay/slam/export.py +420 -0
  173. kuration/gameplay/slam/motion.py +132 -0
  174. kuration/gameplay/slam/pointcloud.py +273 -0
  175. kuration/gameplay/slam/preflight.py +270 -0
  176. kuration/gameplay/slam/quality.py +227 -0
  177. kuration/gameplay/slam/runner.py +697 -0
  178. kuration/gameplay/slam/schema.py +52 -0
  179. kuration/gameplay/slam/viz.py +221 -0
  180. kuration/gameplay/slam/workflow.py +143 -0
  181. kuration/gameplay/video.py +325 -0
  182. kuration/py.typed +0 -0
  183. kuration/sample_data/__init__.py +19 -0
  184. kuration/sample_data/sessions/session-0.parquet +0 -0
  185. kuration/sample_data/sessions/session-1.parquet +0 -0
  186. kuration/sample_data/sessions/session-2.parquet +0 -0
  187. kuration/sample_data/sessions/session-3.parquet +0 -0
  188. kuration/sample_data/sessions/session-4.parquet +0 -0
  189. kuration/sample_data/sessions/session-5.parquet +0 -0
  190. kuration/selection/__init__.py +88 -0
  191. kuration/selection/operations.py +253 -0
  192. kuration/selection/operators.py +80 -0
  193. kuration/selection/plan.py +186 -0
  194. kuration_sdk-0.1.0.dist-info/METADATA +629 -0
  195. kuration_sdk-0.1.0.dist-info/RECORD +199 -0
  196. kuration_sdk-0.1.0.dist-info/WHEEL +5 -0
  197. kuration_sdk-0.1.0.dist-info/entry_points.txt +26 -0
  198. kuration_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
  199. kuration_sdk-0.1.0.dist-info/top_level.txt +1 -0
kuration/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """Kuration's public package surface.
2
+
3
+ Implementation lives in explicit product domains:
4
+
5
+ - :mod:`kuration.core` contains the dataset-agnostic curation SDK.
6
+ - :mod:`kuration.gameplay` contains maintained gameplay capabilities.
7
+ - :mod:`kuration.selection` contains built-in dataset-selection recipes
8
+ (balanced coverage, rare-event weighting, cluster-balanced diversity).
9
+
10
+ Core symbols remain available from :mod:`kuration` for a concise default API, while
11
+ domain-specific code is imported from its domain package.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from .core import * # noqa: F403
16
+ from .core import __all__ as __all__
@@ -0,0 +1,180 @@
1
+ """Kuration's dataset-agnostic curation core.
2
+
3
+ Full pipeline: schema-driven metric DSL, input adapters, segmentation, feature engine,
4
+ binning, declarative strategies (base + feature-space + filter + pre-pass filters + a
5
+ learned-scorer seam), the output layer (declared modalities, npz/json/hdf5/parquet shards,
6
+ hours-budget fill, sub-track spans), tag-coverage, experiment + report, parallel /
7
+ skip-and-log / resume / out-of-core execution, path-agnostic local + cloud I/O, schema
8
+ inference, and an `input(...).operator(...)` pipeline API.
9
+ This package contains no gameplay-specific implementation. See README.md and docs/.
10
+ """
11
+ from importlib.metadata import PackageNotFoundError, version
12
+
13
+ try:
14
+ __version__ = version("kuration-sdk")
15
+ except PackageNotFoundError: # source tree imported before installation
16
+ __version__ = "0+unknown"
17
+
18
+ from .adapters import (
19
+ CuratedDatasetAdapter,
20
+ HDF5Adapter,
21
+ HuggingFaceAdapter,
22
+ InputAdapter,
23
+ JsonlAdapter,
24
+ NpzAdapter,
25
+ ParquetDatasetAdapter,
26
+ TimeseriesAdapter,
27
+ ZarrAdapter,
28
+ VideoAdapter,
29
+ JoinedMediaAdapter,
30
+ detect_adapter,
31
+ get_adapter,
32
+ register_adapter,
33
+ schema_from_output,
34
+ )
35
+ from .binning import bin_segments, quantile_bins
36
+ from .buffer import Tick, WindowBuffer, build_window, empty_like
37
+ from .columnar import IDENTITY_COLUMNS, SegmentBatch, iter_segment_batches
38
+ from .checkpoints import (CheckpointIdentity, PartitionCommit, StageCheckpoint,
39
+ canonical_hash)
40
+ from .bounded import FeatureStageResult, checkpoint_feature_stage
41
+ from .external import (external_quantile_edges, external_top_k, external_uniform_bins,
42
+ external_weighted, iter_external_span_items,
43
+ iter_external_hash_dedup, iter_external_top_k_priorities,
44
+ iter_external_uniform_priorities,
45
+ iter_external_weighted_priorities)
46
+ from .curate import CurateResult, curate
47
+ from .dsl import compile_metric, register_compiled, register_metric
48
+ from .experiment import Arm, run_experiment, union_define
49
+ from .features import FeatureEngine
50
+ from .infer import infer_schema
51
+ from .io import exists, local_copy, read_text, write_bytes, write_text
52
+ from .loaders import iter_shards, read_shard_arrays, shard_entries
53
+ from .inspection import compare_datasets, inspect_dataset
54
+ from .importing import import_dataset
55
+ from .html_report import render_html_report, write_html_report
56
+ from .manifest import build_config, dump_segment_manifest, write_config
57
+ from .methods import apply_filters, method_priorities, register_filter
58
+ from .pipeline import Pipeline, input
59
+ from . import builtin_operators # noqa: F401 -- registers every built-in Method/Filter/Scorer
60
+ # as a named Pipeline operator; see builtin_operators.py
61
+ from .plugins import load_plugins, loaded_plugin_modules, plugin_failures, plugin_provenance
62
+ from .plugin_validation import PluginCheckResult, check_plugins
63
+ from .progress import (ProgressCallback, ProgressSpec, cli_progress, notebook_progress,
64
+ progress_callback)
65
+ from .report import dataset_report
66
+ from .stats import stats
67
+ from .review import hours_selection_review, preview_dataset, selection_review
68
+ from .quality import (QualityGateError, check_quality, evaluate_quality,
69
+ validate_expectations)
70
+ from .scorers import SegmentScorer, apply_scorers, get_scorer, register_scorer
71
+ from .output import (
72
+ Container,
73
+ Modality,
74
+ OutputEncoder,
75
+ OutputSpec,
76
+ PassthroughEncoder,
77
+ Track,
78
+ fill_to_hours,
79
+ get_encoder,
80
+ register_encoder,
81
+ register_container,
82
+ output_preset,
83
+ tracks_from_ticks,
84
+ write_dataset,
85
+ write_track,
86
+ )
87
+ from .schema import FieldSpec, MediaSpec, SchemaSpec
88
+ from .media import FrameRef, MediaProvider, PyAVMediaProvider, VideoMetadata
89
+ from .transforms import (BatchTransform, TransformContext, apply_transforms,
90
+ apply_transforms_to_windows, get_transform,
91
+ register_batch_transform, register_transform)
92
+ from .analyzers import (
93
+ Analyzer,
94
+ DatasetAnalyzer,
95
+ SplitLeakageAnalyzer,
96
+ StreamAnalyzer,
97
+ get_analyzer,
98
+ register_analyzer,
99
+ run_dataset_analyzer,
100
+ run_stream_analyzer,
101
+ )
102
+ from .execution import (ExecutionBackend, LocalExecutionBackend, RayExecutionBackend,
103
+ ResourceSpec, get_execution_backend)
104
+ from .planning import (OperatorResources, OperatorTraits, PhysicalOperator, PhysicalPlan,
105
+ PhysicalStage, compile_physical_plan)
106
+ from .scoring import score_sources
107
+ from .segment import Segment, Segmenter
108
+ from .select import assign_splits
109
+ from .strategy import Strategy, load_strategy, segment_priorities, select
110
+ from .registry import (ComponentDescriptor, Registry, default_registry, describe_component,
111
+ list_components, register_method, register_operator, register_preset)
112
+ from .results import (DatasetComparison, DatasetInspection, DatasetPreview, DatasetReport,
113
+ DatasetWriteResult, ExecutionPlan, ExperimentResult,
114
+ HoursSelectionReview, QualityGateResult, SelectionReview,
115
+ StatsExamples, StatsReport, ValidationResult)
116
+ from .versioning import (ARTIFACT_FORMAT_VERSION, JOB_SCHEMA_VERSION, migrate,
117
+ migrate_config, migrate_job)
118
+
119
+ __all__ = [
120
+ "__version__",
121
+ "SchemaSpec", "FieldSpec", "MediaSpec",
122
+ "WindowBuffer", "Tick", "build_window", "empty_like",
123
+ "SegmentBatch", "IDENTITY_COLUMNS", "iter_segment_batches",
124
+ "CheckpointIdentity", "PartitionCommit", "StageCheckpoint", "canonical_hash",
125
+ "FeatureStageResult", "checkpoint_feature_stage",
126
+ "external_quantile_edges", "external_top_k",
127
+ "external_uniform_bins",
128
+ "iter_external_top_k_priorities", "iter_external_span_items",
129
+ "iter_external_uniform_priorities",
130
+ "external_weighted", "iter_external_weighted_priorities",
131
+ "iter_external_hash_dedup",
132
+ "compile_metric", "register_metric", "register_compiled",
133
+ "InputAdapter", "TimeseriesAdapter", "HDF5Adapter", "NpzAdapter", "JsonlAdapter",
134
+ "CuratedDatasetAdapter", "ParquetDatasetAdapter", "HuggingFaceAdapter", "ZarrAdapter",
135
+ "schema_from_output", "register_adapter", "get_adapter", "detect_adapter",
136
+ "FrameRef", "VideoMetadata", "MediaProvider", "PyAVMediaProvider",
137
+ "VideoAdapter", "JoinedMediaAdapter",
138
+ "BatchTransform", "TransformContext", "register_batch_transform", "register_transform",
139
+ "get_transform",
140
+ "apply_transforms", "apply_transforms_to_windows",
141
+ "Analyzer", "DatasetAnalyzer", "StreamAnalyzer", "SplitLeakageAnalyzer",
142
+ "register_analyzer", "get_analyzer", "run_dataset_analyzer", "run_stream_analyzer",
143
+ "ExecutionBackend", "LocalExecutionBackend", "RayExecutionBackend", "ResourceSpec",
144
+ "get_execution_backend",
145
+ "OperatorResources", "OperatorTraits", "PhysicalOperator", "PhysicalStage",
146
+ "PhysicalPlan", "compile_physical_plan",
147
+ "Arm", "run_experiment", "union_define",
148
+ "Segment", "Segmenter", "FeatureEngine",
149
+ "bin_segments", "quantile_bins",
150
+ "Strategy", "load_strategy", "select", "segment_priorities", "assign_splits",
151
+ "apply_filters", "method_priorities", "score_sources", "dataset_report", "stats",
152
+ "register_method", "register_filter", "register_operator",
153
+ "SegmentScorer", "register_scorer", "get_scorer", "apply_scorers",
154
+ "curate", "CurateResult",
155
+ "build_config", "dump_segment_manifest", "write_config",
156
+ "read_text", "write_text", "write_bytes", "exists", "local_copy",
157
+ "OutputSpec", "Modality", "OutputEncoder", "PassthroughEncoder",
158
+ "register_encoder", "get_encoder", "Track", "tracks_from_ticks", "write_track",
159
+ "fill_to_hours", "write_dataset",
160
+ "Container", "register_container", "output_preset", "register_preset",
161
+ "iter_shards", "read_shard_arrays", "shard_entries",
162
+ "inspect_dataset", "DatasetInspection", "compare_datasets", "DatasetComparison",
163
+ "import_dataset",
164
+ "preview_dataset", "DatasetPreview", "selection_review", "SelectionReview",
165
+ "hours_selection_review", "HoursSelectionReview",
166
+ "evaluate_quality", "check_quality", "validate_expectations",
167
+ "QualityGateResult", "QualityGateError",
168
+ "render_html_report", "write_html_report",
169
+ "infer_schema", "Pipeline", "input",
170
+ "load_plugins", "loaded_plugin_modules", "plugin_failures", "plugin_provenance",
171
+ "check_plugins",
172
+ "PluginCheckResult", "ProgressCallback", "ProgressSpec", "progress_callback",
173
+ "notebook_progress", "cli_progress",
174
+ "Registry", "default_registry", "ComponentDescriptor", "list_components",
175
+ "describe_component", "ExecutionPlan", "ARTIFACT_FORMAT_VERSION",
176
+ "DatasetWriteResult", "ExperimentResult", "DatasetReport", "StatsReport",
177
+ "StatsExamples",
178
+ "ValidationResult",
179
+ "JOB_SCHEMA_VERSION", "migrate", "migrate_job", "migrate_config",
180
+ ]
@@ -0,0 +1,182 @@
1
+ """Input adapters and validation helpers used by the pipeline facade.
2
+
3
+ Kept separate so ``pipeline.py`` can focus on pipeline plan state and terminal execution. These
4
+ objects remain module-level because in-memory adapters must be pickleable.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import numpy as np
13
+
14
+ from .adapters.timeseries import TimeseriesAdapter
15
+ from .schema import SchemaSpec
16
+
17
+ GLOB_CHARS = set("*?[")
18
+ FILTER_TYPES = {"anomaly", "dedup"}
19
+ OPTION_KEYS = {"workers", "on_error", "seed", "streaming", "resume", "sample_hz", "progress",
20
+ "mode", "executor", "memory_budget", "batch_size", "checkpoint_dir",
21
+ "spill_dir", "approximation", "max_in_flight"}
22
+ TABULAR_KINDS = {"timeseries", "jsonl", "json-rows", "parquet-dataset"}
23
+ SUFFIX_CONTAINER = {".hdf5": "hdf5", ".h5": "hdf5", ".npz": "npz",
24
+ ".parquet": "parquet", ".json": "json"}
25
+
26
+ OP_ALIASES = {
27
+ "<=": "<=", "le": "<=", "lte": "<=", "lessorequal": "<=", "leq": "<=",
28
+ ">=": ">=", "ge": ">=", "gte": ">=", "greaterorequal": ">=", "geq": ">=",
29
+ "<": "<", "lt": "<", "less": "<", ">": ">", "gt": ">", "greater": ">",
30
+ "==": "==", "eq": "==", "equal": "==", "=": "==",
31
+ "!=": "!=", "ne": "!=", "neq": "!=", "notequal": "!=",
32
+ }
33
+
34
+ METHOD_ALIASES = {
35
+ "equal": "uniform-bins", "uniform": "uniform-bins", "uniform-bins": "uniform-bins",
36
+ "top": "top-k", "top-k": "top-k", "topk": "top-k",
37
+ "target": "target-distribution", "target-distribution": "target-distribution",
38
+ "cluster": "cluster-balanced", "cluster-balanced": "cluster-balanced",
39
+ "facility": "facility-location", "facility-location": "facility-location",
40
+ "weighted": "weighted", "k-center": "k-center", "dsir": "dsir",
41
+ "rare": "rare", "typical": "typical", "filter": "filter",
42
+ }
43
+
44
+
45
+ def jsonable(value):
46
+ """Return a compact, stable, publishable provenance representation."""
47
+ if value is None or isinstance(value, (bool, int, float)):
48
+ return value
49
+ if isinstance(value, str):
50
+ looks_private = (
51
+ "://" in value
52
+ or value.startswith(("/", "\\", "file://"))
53
+ or (len(value) >= 3 and value[1] == ":" and value[2] in "\\/")
54
+ )
55
+ if looks_private:
56
+ from .provenance import publishable_source_id
57
+ return publishable_source_id(value)
58
+ return value
59
+ if isinstance(value, Path):
60
+ from .provenance import publishable_source_id
61
+ return publishable_source_id(value)
62
+ if isinstance(value, np.generic):
63
+ return jsonable(value.item())
64
+ if isinstance(value, np.ndarray):
65
+ return {
66
+ "type": "numpy.ndarray",
67
+ "shape": list(value.shape),
68
+ "dtype": str(value.dtype),
69
+ "content_sha256": hashlib.sha256(value.tobytes(order="C")).hexdigest(),
70
+ }
71
+ if isinstance(value, bytes):
72
+ return {
73
+ "type": "bytes",
74
+ "size": len(value),
75
+ "content_sha256": hashlib.sha256(value).hexdigest(),
76
+ }
77
+ if isinstance(value, (list, tuple)):
78
+ return [jsonable(item) for item in value]
79
+ if isinstance(value, dict):
80
+ return {str(jsonable(key)): jsonable(item) for key, item in value.items()}
81
+ to_dict = getattr(value, "to_dict", None)
82
+ if callable(to_dict):
83
+ return jsonable(to_dict())
84
+ from .provenance import component_identity
85
+ try:
86
+ identity = component_identity(value)
87
+ except Exception:
88
+ identity = {
89
+ "type": f"{type(value).__module__}.{type(value).__qualname__}",
90
+ "identity": "unavailable",
91
+ }
92
+ return {"component": identity}
93
+
94
+
95
+ def describe_src(src) -> Any:
96
+ from .provenance import publishable_source_id
97
+ if isinstance(src, str):
98
+ return publishable_source_id(src)
99
+ if isinstance(src, (list, tuple)) and src and all(isinstance(item, str) for item in src):
100
+ return [publishable_source_id(item) for item in src]
101
+ return f"<in-memory {type(src).__name__}>"
102
+
103
+
104
+ def auto_qualify(token: str, schema: SchemaSpec) -> str:
105
+ if "." in token:
106
+ return token
107
+ if token in schema.fields:
108
+ return f"{schema.entity_axis}.{token}" if schema.entity_axis else token
109
+ if token in schema.globals:
110
+ return token
111
+ if token in schema.event_streams:
112
+ raise ValueError(
113
+ f"{token!r} is an event stream — name a field: '{token}.<field>' "
114
+ f"(fields: {sorted(schema.event_streams[token]['fields'])})")
115
+ raise ValueError(f"unknown metric source {token!r}; schema fields: "
116
+ f"{sorted(schema.fields)}, globals: {sorted(schema.globals)}")
117
+
118
+
119
+ def default_field_map(schema: SchemaSpec, *, tabular: bool) -> dict:
120
+ out: dict = {}
121
+ for name, field in schema.fields.items():
122
+ out[name] = ([f"{name}.{i}" for i in range(len(field.dims))]
123
+ if tabular and field.kind == "vector" else name)
124
+ return out
125
+
126
+
127
+ def expand_row(row: dict) -> dict:
128
+ out = dict(row)
129
+ for key, value in row.items():
130
+ if isinstance(value, np.ndarray) and value.ndim == 1:
131
+ value = value.tolist()
132
+ if isinstance(value, (list, tuple)):
133
+ for index, item in enumerate(value):
134
+ out[f"{key}.{index}"] = item
135
+ return out
136
+
137
+
138
+ class RowsAdapter(TimeseriesAdapter):
139
+ in_memory = True
140
+
141
+ def __init__(self, schema, *, data: dict, fields: dict, time_column=None,
142
+ entity_column=None, globals=None):
143
+ super().__init__(schema, fields=fields, time_column=time_column,
144
+ entity_column=entity_column, globals=globals)
145
+ self._data = data
146
+
147
+ def ticks(self, source_uri):
148
+ if source_uri not in self._data:
149
+ raise ValueError(f"unknown in-memory source {source_uri!r} "
150
+ f"(have: {sorted(self._data)})")
151
+ return self._rows_to_ticks([expand_row(row) for row in self._data[source_uri]])
152
+
153
+ def source_fingerprint(self, source_uri):
154
+ from .provenance import _state_hash
155
+ return _state_hash(self._data[source_uri])
156
+
157
+
158
+ class TickAdapter:
159
+ in_memory = True
160
+
161
+ def __init__(self, schema, data: dict):
162
+ self.schema = schema
163
+ self._data = data
164
+
165
+ def ticks(self, source_uri):
166
+ if source_uri not in self._data:
167
+ raise ValueError(f"unknown in-memory source {source_uri!r} "
168
+ f"(have: {sorted(self._data)})")
169
+ return self._data[source_uri]
170
+
171
+ def source_fingerprint(self, source_uri):
172
+ from .provenance import _state_hash
173
+ return _state_hash(self._data[source_uri])
174
+
175
+
176
+ class JsonRowsAdapter(TimeseriesAdapter):
177
+ def ticks(self, source_uri):
178
+ from .infer import load_rows
179
+ return self._rows_to_ticks([expand_row(row) for row in load_rows(source_uri)])
180
+
181
+ def iter_ticks(self, source_uri):
182
+ yield from self.ticks(source_uri)
@@ -0,0 +1,15 @@
1
+ """Shared collision-safe idempotence for built-in component registration."""
2
+ from __future__ import annotations
3
+
4
+
5
+ def needs_registration(registry, kind: str, name: str, component) -> bool:
6
+ """Return whether a component is absent, accepting only exact repeat registration."""
7
+ if name not in registry.names(kind):
8
+ return True
9
+ if registry.get(kind, name) is component:
10
+ return False
11
+ descriptor = registry.descriptor(kind, name)
12
+ owner = descriptor.module or descriptor.package or "an unknown implementation"
13
+ raise ValueError(
14
+ f"cannot register {kind} {name!r}: the name is already owned by {owner!r}"
15
+ )
@@ -0,0 +1,18 @@
1
+ """Input adapters: raw source -> canonical Tick stream."""
2
+ from .base import InputAdapter, detect_adapter, get_adapter, register_adapter
3
+ from .curated import CuratedDatasetAdapter, schema_from_output
4
+ from .hdf5 import HDF5Adapter
5
+ from .huggingface import HuggingFaceAdapter
6
+ from .jsonl import JsonlAdapter
7
+ from .npz import NpzAdapter
8
+ from .parquet_dataset import ParquetDatasetAdapter
9
+ from .timeseries import TimeseriesAdapter
10
+ from .zarr_store import ZarrAdapter
11
+ from ..media import JoinedMediaAdapter, VideoAdapter
12
+
13
+ __all__ = ["InputAdapter", "register_adapter", "get_adapter", "detect_adapter",
14
+ "TimeseriesAdapter",
15
+ "HDF5Adapter", "NpzAdapter", "JsonlAdapter", "CuratedDatasetAdapter",
16
+ "ParquetDatasetAdapter", "HuggingFaceAdapter", "ZarrAdapter",
17
+ "schema_from_output"]
18
+ __all__ += ["VideoAdapter", "JoinedMediaAdapter"]
@@ -0,0 +1,248 @@
1
+ """`InputAdapter` — the input seam. The one place domain knowledge about a raw format
2
+ enters the SDK.
3
+
4
+ An adapter's contract is deliberately tiny: turn a raw source into an ordered stream of
5
+ canonical `Tick`s (parse + group-by-timestep). Everything after that — building the
6
+ `(T,E,D)` buffer, segmentation, metrics, selection — is generic SDK code that never knows
7
+ which dataset it's looking at. Streaming-capable adapters expose `iter_ticks`; the compatible
8
+ `ticks` path materializes a list for callers that need random access.
9
+
10
+ Adapters are registered by name for declarative job construction. The parallel feature pass
11
+ pickles the configured adapter into spawned workers.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Callable, Iterable, Protocol, runtime_checkable
16
+
17
+ import numpy as np
18
+
19
+ from ..buffer import Tick
20
+ from ..schema import SchemaSpec
21
+
22
+
23
+ @runtime_checkable
24
+ class InputAdapter(Protocol):
25
+ schema: SchemaSpec
26
+ def ticks(self, source_uri: str) -> Iterable[Tick]: ...
27
+
28
+
29
+ def validate_adapter_availability(adapter, schema: SchemaSpec, *, define: dict | None = None,
30
+ tags_spec: dict | None = None, output_spec=None) -> None:
31
+ """Reject plans that depend on schema values a configured adapter cannot emit.
32
+
33
+ Third-party adapters that do not declare ``available_fields``/``available_globals`` retain
34
+ the protocol's historical promise that they can emit the full schema. Built-in configured
35
+ adapters expose these sets, allowing a typo or partial mapping to fail before data is read
36
+ instead of becoming an all-missing buffer that reduces to a plausible zero.
37
+ """
38
+ available_fields = getattr(adapter, "available_fields", None)
39
+ available_globals = getattr(adapter, "available_globals", None)
40
+ if available_fields is None and available_globals is None:
41
+ return
42
+ available_fields = set(schema.fields if available_fields is None else available_fields)
43
+ available_globals = set(schema.globals if available_globals is None else available_globals)
44
+ required_fields: set[str] = set()
45
+ required_globals: set[str] = set()
46
+ for spec in (define or {}).values():
47
+ if not isinstance(spec, dict):
48
+ continue
49
+ source = spec.get("source")
50
+ if isinstance(source, str):
51
+ parts = source.split(".")
52
+ if schema.entity_axis is not None and len(parts) >= 2 and parts[0] == schema.entity_axis:
53
+ required_fields.add(parts[1])
54
+ elif source in schema.globals:
55
+ required_globals.add(source)
56
+ filter_expr = spec.get("filter")
57
+ if isinstance(filter_expr, str):
58
+ from ..dsl.grammar import _parse_filter
59
+ field = _parse_filter(filter_expr)[1]
60
+ if field in schema.fields:
61
+ required_fields.add(field)
62
+ for source in ((tags_spec or {}).get("from") or ()):
63
+ if source in schema.fields:
64
+ required_fields.add(source)
65
+ elif source in schema.globals:
66
+ required_globals.add(source)
67
+ if output_spec is not None:
68
+ for modality in output_spec.modalities:
69
+ if modality.src_kind == "field":
70
+ required_fields.add(modality.src_arg)
71
+ missing_fields = sorted(required_fields - available_fields)
72
+ missing_globals = sorted(required_globals - available_globals)
73
+ if missing_fields or missing_globals:
74
+ details = []
75
+ if missing_fields:
76
+ details.append(f"fields {missing_fields}")
77
+ if missing_globals:
78
+ details.append(f"globals {missing_globals}")
79
+ raise ValueError(f"{type(adapter).__name__} does not map required " + " and ".join(details))
80
+
81
+
82
+ def iter_arrays_to_ticks(schema: SchemaSpec, arrs: dict, gl: dict = None):
83
+ """Lazily build single-entity ticks from indexable per-field arrays."""
84
+ gl = gl or {}
85
+ overlap = sorted(set(arrs) & set(gl))
86
+ if overlap:
87
+ raise ValueError(f"arrays_to_ticks: names cannot be both fields and globals: {overlap}")
88
+ arrays = {**arrs, **gl}
89
+
90
+ def step_count(array) -> int:
91
+ # Zarr 3 arrays deliberately do not implement ``len``. Array stores do expose
92
+ # ``shape`` as metadata, while ordinary Python sequences still use ``len``.
93
+ shape = getattr(array, "shape", None)
94
+ return int(shape[0]) if shape else (0 if shape == () else len(array))
95
+
96
+ n = step_count(next(iter(arrays.values()))) if arrays else 0
97
+ mismatched = {k: step_count(a) for k, a in arrays.items() if step_count(a) != n}
98
+ if mismatched: # silent truncation to the first field's length is worse
99
+ raise ValueError(f"arrays_to_ticks: length mismatch — expected {n} steps, got "
100
+ f"{mismatched}")
101
+ import math
102
+ for fn, arr in arrs.items():
103
+ if fn not in schema.fields:
104
+ raise ValueError(f"arrays_to_ticks: undeclared schema field {fn!r}")
105
+ shape = tuple(getattr(arr, "shape", np.asarray(arr).shape))
106
+ width = math.prod(shape[1:]) if shape else 0
107
+ expected = schema.fields[fn].width
108
+ if width != expected:
109
+ raise ValueError(f"arrays_to_ticks: field {fn!r} has per-step width {width}, "
110
+ f"expected {expected} for {schema.fields[fn].kind}")
111
+ for gn, arr in gl.items():
112
+ if gn not in schema.globals:
113
+ raise ValueError(f"arrays_to_ticks: undeclared schema global {gn!r}")
114
+ shape = tuple(getattr(arr, "shape", np.asarray(arr).shape))
115
+ width = math.prod(shape[1:]) if shape else 0
116
+ if width != 1:
117
+ raise ValueError(f"arrays_to_ticks: global {gn!r} has per-step width {width}, "
118
+ "expected 1")
119
+ for t in range(n):
120
+ ent: dict[str, object] = {}
121
+ for fn, arr in arrs.items():
122
+ fs = schema.fields[fn]
123
+ x = arr[t]
124
+ if fs.kind == "vector":
125
+ ent[fn] = tuple(float(v) for v in np.ravel(x))
126
+ elif fs.kind == "categorical":
127
+ ent[fn] = x.decode() if isinstance(x, bytes) else str(x)
128
+ else:
129
+ ent[fn] = float(np.ravel(x)[0]) if np.ndim(x) else float(x)
130
+ g = {gn: float(np.ravel(arr[t])[0]) for gn, arr in gl.items()}
131
+ yield Tick(time=t, entities={0: ent}, globals=g)
132
+
133
+
134
+ def arrays_to_ticks(schema: SchemaSpec, arrs: dict, gl: dict = None) -> list:
135
+ """Materialized counterpart to `iter_arrays_to_ticks` for existing callers."""
136
+ return list(iter_arrays_to_ticks(schema, arrs, gl))
137
+
138
+
139
+ def check_mapped_arrays(schema: SchemaSpec, fields: dict, globals_: dict, source_uri: str,
140
+ shape_of: Callable) -> None:
141
+ """Metadata-level mirror of `arrays_to_ticks`'s run-time checks, shared by the array-store
142
+ adapters' `check_source` hooks. `shape_of(key)` returns the stored array's shape, or None
143
+ if the key doesn't exist — shapes are store metadata, so no array data is read. Checks:
144
+ every mapped key exists, all first dims agree (step count), and each field's per-step
145
+ width matches the schema (a wide array mapped to a scalar would silently truncate to its
146
+ first component at run time — loud beats masked)."""
147
+ import math
148
+ overlap = sorted(set(fields) & set(globals_))
149
+ if overlap:
150
+ raise ValueError(f"source {source_uri!r}: names cannot be both fields and globals: "
151
+ f"{overlap}")
152
+ keys = {**fields, **globals_} # mapped name -> store key
153
+ shapes, missing = {}, []
154
+ for key in keys.values():
155
+ shp = shape_of(key)
156
+ if shp is None:
157
+ missing.append(key)
158
+ else:
159
+ shapes[key] = tuple(shp)
160
+ if missing:
161
+ raise ValueError(f"source {source_uri!r} is missing mapped key(s) {sorted(missing)}")
162
+ lens = {k: (s[0] if s else 0) for k, s in shapes.items()}
163
+ if len(set(lens.values())) > 1:
164
+ raise ValueError(f"source {source_uri!r}: mapped arrays disagree on step count: {lens}")
165
+ for f, key in fields.items():
166
+ fs = schema.fields[f]
167
+ width = math.prod(shapes[key][1:])
168
+ if width != fs.width:
169
+ raise ValueError(f"source {source_uri!r}: array {key!r} has per-step width {width}, "
170
+ f"but field {f!r} ({fs.kind}) expects {fs.width}")
171
+
172
+
173
+ _ADAPTERS: dict[str, Callable[..., InputAdapter]] = {}
174
+
175
+
176
+ def register_adapter(name: str, factory: Callable[..., InputAdapter], **metadata) -> None:
177
+ """Register an input-adapter factory under a unique name.
178
+
179
+ ``metadata`` becomes its component descriptor. Duplicate names raise ``ValueError``.
180
+ """
181
+ from ..registry import default_registry
182
+ default_registry.register("adapter", name, factory, **metadata)
183
+ _ADAPTERS[name] = factory
184
+
185
+
186
+ def get_adapter(name: str, *, registry=None, **kwargs) -> InputAdapter:
187
+ """Construct a named adapter, loading plugins on demand.
188
+
189
+ ``kwargs`` are passed to the factory. An explicit ``registry`` bypasses the legacy global
190
+ adapter table; an unknown name raises ``ValueError``.
191
+ """
192
+ from ..registry import default_registry
193
+ active_registry = registry or default_registry
194
+ if name not in active_registry.names("adapter") and active_registry is default_registry:
195
+ from ..plugins import load_plugins
196
+ load_plugins()
197
+ return active_registry.get("adapter", name)(**kwargs)
198
+
199
+
200
+ def detect_adapter(source_uri: str, *, registry=None) -> str:
201
+ """Return the uniquely highest-confidence registered adapter for a source.
202
+
203
+ Adapter factories may expose ``detect_source(uri) -> bool | number``. A numeric result is
204
+ a confidence score; ``True`` is score 1 and falsey values mean no match. Detection is
205
+ intentionally separate from construction because many adapters need inferred schema and
206
+ mapping parameters before they can be instantiated.
207
+ """
208
+ from ..registry import default_registry
209
+
210
+ active_registry = registry or default_registry
211
+ if active_registry is default_registry:
212
+ from ..plugins import load_plugins
213
+
214
+ load_plugins()
215
+ matches: list[tuple[float, str]] = []
216
+ for name in active_registry.names("adapter"):
217
+ factory = active_registry.get("adapter", name)
218
+ detector = getattr(factory, "detect_source", None)
219
+ if not callable(detector):
220
+ continue
221
+ confidence = detector(str(source_uri))
222
+ if isinstance(confidence, bool):
223
+ score = 1.0 if confidence else 0.0
224
+ elif isinstance(confidence, (int, float)) and not isinstance(confidence, bool):
225
+ score = float(confidence)
226
+ else:
227
+ raise ValueError(
228
+ f"adapter {name!r} detect_source() must return a boolean or number"
229
+ )
230
+ if not np.isfinite(score) or score < 0:
231
+ raise ValueError(
232
+ f"adapter {name!r} detect_source() returned invalid confidence {score!r}"
233
+ )
234
+ if score > 0:
235
+ matches.append((score, name))
236
+ if not matches:
237
+ raise ValueError(
238
+ f"no registered input adapter recognized {source_uri!r}; "
239
+ "pass adapter= explicitly or install/register a matching adapter"
240
+ )
241
+ best = max(score for score, _name in matches)
242
+ winners = sorted(name for score, name in matches if score == best)
243
+ if len(winners) > 1:
244
+ raise ValueError(
245
+ f"input adapter detection for {source_uri!r} is ambiguous at confidence "
246
+ f"{best:g}: {winners}; pass adapter= explicitly"
247
+ )
248
+ return winners[0]