modosaic 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 (130) hide show
  1. modosaic/__init__.py +43 -0
  2. modosaic/cli/__init__.py +3 -0
  3. modosaic/cli/app.py +451 -0
  4. modosaic/cli/cli.py +7 -0
  5. modosaic/cli/config.py +420 -0
  6. modosaic/cli/pipeline.py +425 -0
  7. modosaic/cli/summary.py +94 -0
  8. modosaic/core/__init__.py +45 -0
  9. modosaic/core/boundary_alignment_stats.py +21 -0
  10. modosaic/core/configured_modality.py +59 -0
  11. modosaic/core/constants.py +13 -0
  12. modosaic/core/experiment_artifact.py +68 -0
  13. modosaic/core/hf_model_spec.py +20 -0
  14. modosaic/core/modalities.py +15 -0
  15. modosaic/core/modality.py +121 -0
  16. modosaic/core/modality_generator.py +22 -0
  17. modosaic/core/pipeline.py +183 -0
  18. modosaic/core/postprocessor.py +48 -0
  19. modosaic/core/record.py +31 -0
  20. modosaic/core/score_functions.py +16 -0
  21. modosaic/core/validation_constraint.py +44 -0
  22. modosaic/core/validation_result.py +23 -0
  23. modosaic/core/validator.py +24 -0
  24. modosaic/core/validator_step.py +89 -0
  25. modosaic/depth/__init__.py +7 -0
  26. modosaic/depth/generators/__init__.py +3 -0
  27. modosaic/depth/generators/base/__init__.py +3 -0
  28. modosaic/depth/generators/base/generator.py +38 -0
  29. modosaic/depth/generators/factory.py +57 -0
  30. modosaic/depth/generators/impl/__init__.py +15 -0
  31. modosaic/depth/generators/impl/depth_anything_small.py +48 -0
  32. modosaic/depth/generators/impl/depth_anything_v2_metric_small.py +48 -0
  33. modosaic/depth/generators/impl/depth_anything_v2_small.py +48 -0
  34. modosaic/depth/generators/impl/depth_pro.py +72 -0
  35. modosaic/depth/generators/impl/dpt_hybrid_midas.py +48 -0
  36. modosaic/depth/generators/impl/marigold_depth_v1_1.py +45 -0
  37. modosaic/depth/postprocessor.py +33 -0
  38. modosaic/depth/preconfigured_modality.py +47 -0
  39. modosaic/depth/validators/__init__.py +3 -0
  40. modosaic/depth/validators/base/__init__.py +3 -0
  41. modosaic/depth/validators/base/validator.py +19 -0
  42. modosaic/depth/validators/impl/__init__.py +9 -0
  43. modosaic/depth/validators/impl/depth_seg_boundary_consistency_validator.py +74 -0
  44. modosaic/depth/validators/impl/imagebind.py +196 -0
  45. modosaic/depth/visualization.py +95 -0
  46. modosaic/image/__init__.py +9 -0
  47. modosaic/image/generator.py +17 -0
  48. modosaic/image/postprocessor.py +29 -0
  49. modosaic/image/preconfigured_modality.py +17 -0
  50. modosaic/log/__init__.py +0 -0
  51. modosaic/log/log.json +54 -0
  52. modosaic/log/logger.py +188 -0
  53. modosaic/normals/__init__.py +11 -0
  54. modosaic/normals/generators/__init__.py +3 -0
  55. modosaic/normals/generators/base/__init__.py +3 -0
  56. modosaic/normals/generators/base/generator.py +31 -0
  57. modosaic/normals/generators/factory.py +41 -0
  58. modosaic/normals/generators/impl/__init__.py +7 -0
  59. modosaic/normals/generators/impl/midas_d2n.py +72 -0
  60. modosaic/normals/generators/impl/omnidata.py +72 -0
  61. modosaic/normals/normal_agreement_stats.py +22 -0
  62. modosaic/normals/normal_field_stats.py +21 -0
  63. modosaic/normals/postprocessor.py +33 -0
  64. modosaic/normals/preconfigured_modality.py +70 -0
  65. modosaic/normals/validators/__init__.py +3 -0
  66. modosaic/normals/validators/base/__init__.py +3 -0
  67. modosaic/normals/validators/base/validator.py +19 -0
  68. modosaic/normals/validators/impl/__init__.py +9 -0
  69. modosaic/normals/validators/impl/depth_normals_agreement.py +119 -0
  70. modosaic/normals/visualization.py +20 -0
  71. modosaic/providers/__init__.py +11 -0
  72. modosaic/providers/adapters/__init__.py +9 -0
  73. modosaic/providers/adapters/adapter.py +17 -0
  74. modosaic/providers/adapters/local_folder.py +61 -0
  75. modosaic/providers/adapters/parquet.py +205 -0
  76. modosaic/providers/image_dataset.py +119 -0
  77. modosaic/segmentation/__init__.py +9 -0
  78. modosaic/segmentation/generators/__init__.py +3 -0
  79. modosaic/segmentation/generators/base/__init__.py +3 -0
  80. modosaic/segmentation/generators/base/generator.py +31 -0
  81. modosaic/segmentation/generators/factory.py +45 -0
  82. modosaic/segmentation/generators/impl/__init__.py +9 -0
  83. modosaic/segmentation/generators/impl/sam_2_hiera_small.py +53 -0
  84. modosaic/segmentation/generators/impl/sam_3.py +53 -0
  85. modosaic/segmentation/generators/impl/sam_b.py +69 -0
  86. modosaic/segmentation/mask_validation_stats.py +18 -0
  87. modosaic/segmentation/postprocessor.py +66 -0
  88. modosaic/segmentation/preconfigured_modality.py +59 -0
  89. modosaic/segmentation/validators/__init__.py +3 -0
  90. modosaic/segmentation/validators/base/__init__.py +3 -0
  91. modosaic/segmentation/validators/base/validator.py +19 -0
  92. modosaic/segmentation/validators/impl/__init__.py +9 -0
  93. modosaic/segmentation/validators/impl/boundary_rgb_edge_overlap.py +64 -0
  94. modosaic/segmentation/validators/impl/mask_statistics.py +76 -0
  95. modosaic/segmentation/validators/impl/normals_field_quality.py +131 -0
  96. modosaic/segmentation/visualization.py +79 -0
  97. modosaic/services/__init__.py +11 -0
  98. modosaic/services/boundary.py +131 -0
  99. modosaic/services/device.py +19 -0
  100. modosaic/services/edge.py +74 -0
  101. modosaic/services/experiment.py +219 -0
  102. modosaic/services/extension.py +50 -0
  103. modosaic/services/image.py +66 -0
  104. modosaic/services/logging.py +81 -0
  105. modosaic/services/seeding.py +109 -0
  106. modosaic/services/tolerance.py +47 -0
  107. modosaic/text/__init__.py +13 -0
  108. modosaic/text/constants.py +6 -0
  109. modosaic/text/generators/__init__.py +3 -0
  110. modosaic/text/generators/base/__init__.py +3 -0
  111. modosaic/text/generators/base/generator.py +33 -0
  112. modosaic/text/generators/factory.py +49 -0
  113. modosaic/text/generators/impl/__init__.py +11 -0
  114. modosaic/text/generators/impl/internvl_3_2b.py +71 -0
  115. modosaic/text/generators/impl/internvl_3_5_2b.py +68 -0
  116. modosaic/text/generators/impl/qwen_2_2b.py +90 -0
  117. modosaic/text/generators/impl/qwen_2_5_3b.py +107 -0
  118. modosaic/text/postprocessor.py +27 -0
  119. modosaic/text/preconfigured_modality.py +32 -0
  120. modosaic/text/validators/__init__.py +3 -0
  121. modosaic/text/validators/base/__init__.py +3 -0
  122. modosaic/text/validators/base/validator.py +17 -0
  123. modosaic/text/validators/impl/__init__.py +3 -0
  124. modosaic/text/validators/impl/siglip_2.py +70 -0
  125. modosaic-0.1.0.dist-info/METADATA +405 -0
  126. modosaic-0.1.0.dist-info/RECORD +130 -0
  127. modosaic-0.1.0.dist-info/WHEEL +5 -0
  128. modosaic-0.1.0.dist-info/entry_points.txt +2 -0
  129. modosaic-0.1.0.dist-info/licenses/LICENSE +21 -0
  130. modosaic-0.1.0.dist-info/top_level.txt +1 -0
modosaic/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """Public Modosaic API.
2
+
3
+ The package exports the lightweight core interfaces used to compose dataset
4
+ providers, modality generators, validators, postprocessors, pipelines, and
5
+ experiment services without importing heavyweight model dependencies at the top
6
+ level.
7
+ """
8
+
9
+ from modosaic.core.configured_modality import ConfiguredModality
10
+ from modosaic.core.experiment_artifact import ExperimentArtifact, ExperimentArtifactKind
11
+ from modosaic.core.modalities import Modalities
12
+ from modosaic.core.modality import Modality
13
+ from modosaic.core.modality_generator import ModalityGenerator
14
+ from modosaic.core.pipeline import Pipeline, PipelineSampleResult
15
+ from modosaic.core.postprocessor import ModalityPostprocessor
16
+ from modosaic.core.record import ImageRecord
17
+ from modosaic.core.validation_constraint import ValidationConstraint
18
+ from modosaic.core.validation_result import ValidationResult
19
+ from modosaic.core.validator import ModalityValidator
20
+ from modosaic.core.validator_step import ValidatorStep
21
+ from modosaic.providers.image_dataset import ImageDataset
22
+ from modosaic.services.experiment import ExperimentService
23
+ from modosaic.services.logging import LoggingService
24
+
25
+ __all__ = (
26
+ "ConfiguredModality",
27
+ "ExperimentArtifact",
28
+ "ExperimentArtifactKind",
29
+ "ExperimentService",
30
+ "ImageDataset",
31
+ "ImageRecord",
32
+ "Modalities",
33
+ "Modality",
34
+ "ModalityGenerator",
35
+ "ModalityPostprocessor",
36
+ "ModalityValidator",
37
+ "Pipeline",
38
+ "PipelineSampleResult",
39
+ "ValidationConstraint",
40
+ "ValidationResult",
41
+ "ValidatorStep",
42
+ "LoggingService",
43
+ )
@@ -0,0 +1,3 @@
1
+ from modosaic.cli.app import app
2
+
3
+ __all__ = ("app",)
modosaic/cli/app.py ADDED
@@ -0,0 +1,451 @@
1
+ """Typer command definitions for the Modosaic CLI."""
2
+
3
+ from collections.abc import Sequence
4
+ from dataclasses import replace
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ from typing import Annotated
8
+
9
+ from typer import Argument, Exit, Option, Typer, echo
10
+
11
+ from modosaic.cli.config import (
12
+ DEFAULT_LOG_PATH,
13
+ DatasetConfig,
14
+ DatasetKind,
15
+ DepthModelName,
16
+ ModalityName,
17
+ ModelConfig,
18
+ NormalsModelName,
19
+ RunConfig,
20
+ SegmentationModelName,
21
+ TextModelName,
22
+ ConstraintConfig,
23
+ ValidatorConfig,
24
+ load_config_mapping,
25
+ normalize_modalities,
26
+ run_config_from_mapping,
27
+ )
28
+ from modosaic.cli.pipeline import execute_run
29
+ from modosaic.cli.summary import format_summary
30
+
31
+ app = Typer(no_args_is_help=True, name="modosaic")
32
+
33
+
34
+ @app.command(
35
+ help="Run the default Modosaic pipeline on a local image folder.",
36
+ short_help="Run defaults on local images.",
37
+ )
38
+ def simple(
39
+ root: Annotated[
40
+ Path,
41
+ Argument(
42
+ exists=True,
43
+ file_okay=False,
44
+ help="Path to a local folder containing source images.",
45
+ ),
46
+ ],
47
+ limit: Annotated[
48
+ int | None,
49
+ Option("--limit", "-n", help="Maximum number of samples to process."),
50
+ ] = None,
51
+ experiment_root: Annotated[
52
+ Path,
53
+ Option(
54
+ "--experiment-root",
55
+ file_okay=False,
56
+ help="Directory where experiment artifacts will be written.",
57
+ ),
58
+ ] = Path("experiments"),
59
+ experiment_name: Annotated[
60
+ str | None,
61
+ Option("--experiment-name", help="Optional experiment folder name."),
62
+ ] = None,
63
+ log_path: Annotated[
64
+ Path | None,
65
+ Option(
66
+ "--log-path",
67
+ file_okay=False,
68
+ help="Directory where logs will be stored.",
69
+ ),
70
+ ] = DEFAULT_LOG_PATH,
71
+ seed: Annotated[
72
+ int,
73
+ Option("--seed", help="Global seed for reproducible model execution."),
74
+ ] = 42,
75
+ json_summary: Annotated[
76
+ bool,
77
+ Option("--json", help="Print the run summary as JSON."),
78
+ ] = False,
79
+ ) -> None:
80
+ """Run the default pipeline on a local image folder."""
81
+ config = RunConfig(
82
+ dataset=DatasetConfig(kind=DatasetKind.LOCAL, root=root),
83
+ limit=limit,
84
+ experiment_root=experiment_root,
85
+ experiment_name=experiment_name,
86
+ log_path=log_path,
87
+ seed=seed,
88
+ json_summary=json_summary,
89
+ )
90
+ _run_or_exit(config)
91
+
92
+
93
+ @app.command(
94
+ help=(
95
+ "Run the Modosaic pipeline with configurable dataset, modalities, "
96
+ "models, validators, and quality-gate constraints."
97
+ ),
98
+ short_help="Run with CLI configuration.",
99
+ )
100
+ def run(
101
+ dataset: Annotated[
102
+ DatasetKind,
103
+ Option("--dataset", help="Dataset adapter to use."),
104
+ ] = DatasetKind.LOCAL,
105
+ root: Annotated[
106
+ Path | None,
107
+ Option(
108
+ "--root",
109
+ exists=True,
110
+ file_okay=False,
111
+ help="Local image folder. Required when --dataset local.",
112
+ ),
113
+ ] = None,
114
+ parquet_path: Annotated[
115
+ Path | None,
116
+ Option(
117
+ "--parquet-path",
118
+ exists=True,
119
+ help="Parquet file or directory. Required when --dataset parquet.",
120
+ ),
121
+ ] = None,
122
+ recursive: Annotated[
123
+ bool,
124
+ Option(
125
+ "--recursive/--top-level",
126
+ help="Scan local folders recursively or only at the top level.",
127
+ ),
128
+ ] = True,
129
+ extensions: Annotated[
130
+ list[str] | None,
131
+ Option(
132
+ "--extension",
133
+ "-e",
134
+ help="Local image extension to include. Repeat to include many.",
135
+ ),
136
+ ] = None,
137
+ image_column: Annotated[
138
+ str,
139
+ Option("--image-column", help="Parquet column containing image bytes or paths."),
140
+ ] = "image",
141
+ id_column: Annotated[
142
+ str | None,
143
+ Option("--id-column", help="Optional Parquet sample-id column."),
144
+ ] = None,
145
+ extension_column: Annotated[
146
+ str | None,
147
+ Option("--extension-column", help="Optional Parquet image-extension column."),
148
+ ] = None,
149
+ metadata_columns: Annotated[
150
+ list[str] | None,
151
+ Option(
152
+ "--metadata-column",
153
+ help="Parquet metadata column to keep. Repeat to include many.",
154
+ ),
155
+ ] = None,
156
+ batch_size: Annotated[
157
+ int,
158
+ Option("--batch-size", help="Parquet read batch size."),
159
+ ] = 512,
160
+ modalities: Annotated[
161
+ list[ModalityName] | None,
162
+ Option(
163
+ "--modality",
164
+ "-m",
165
+ help=(
166
+ "Modality to generate. Repeat to select many; omitted means all "
167
+ "modalities in dependency-safe order."
168
+ ),
169
+ ),
170
+ ] = None,
171
+ text_model: Annotated[
172
+ TextModelName,
173
+ Option("--text-model", help="Captioning model for the text modality."),
174
+ ] = TextModelName.QWEN_2_2B,
175
+ segmentation_model: Annotated[
176
+ SegmentationModelName,
177
+ Option("--segmentation-model", help="Mask generator for segmentation."),
178
+ ] = SegmentationModelName.SAM3,
179
+ depth_model: Annotated[
180
+ DepthModelName,
181
+ Option("--depth-model", help="Depth generator for depth maps."),
182
+ ] = DepthModelName.DEPTH_ANYTHING_V2_SMALL,
183
+ normals_model: Annotated[
184
+ NormalsModelName,
185
+ Option("--normals-model", help="Normal-field generator for normals."),
186
+ ] = NormalsModelName.OMNIDATA,
187
+ validators: Annotated[
188
+ bool,
189
+ Option("--validators/--no-validators", help="Run modality validators."),
190
+ ] = True,
191
+ constraints: Annotated[
192
+ bool,
193
+ Option(
194
+ "--constraints/--no-constraints",
195
+ help="Use validator constraints as quality gates.",
196
+ ),
197
+ ] = True,
198
+ text_siglip_minimum: Annotated[
199
+ float,
200
+ Option("--text-siglip-min", help="Minimum SIGLIP text-image score."),
201
+ ] = 0.60,
202
+ segmentation_mask_quality_minimum: Annotated[
203
+ float,
204
+ Option("--segmentation-mask-quality-min", help="Minimum weighted mask quality."),
205
+ ] = 0.75,
206
+ segmentation_boundary_minimum: Annotated[
207
+ float,
208
+ Option("--segmentation-boundary-min", help="Minimum RGB boundary F1."),
209
+ ] = 0.20,
210
+ depth_imagebind_minimum: Annotated[
211
+ float,
212
+ Option("--depth-imagebind-min", help="Minimum ImageBind depth-image score."),
213
+ ] = 0.55,
214
+ depth_segmentation_boundary_minimum: Annotated[
215
+ float,
216
+ Option(
217
+ "--depth-segmentation-boundary-min",
218
+ help="Minimum depth-vs-segmentation boundary F1.",
219
+ ),
220
+ ] = 0.20,
221
+ normals_depth_agreement_minimum: Annotated[
222
+ float,
223
+ Option(
224
+ "--normals-depth-agreement-min",
225
+ help="Minimum depth/normal angular agreement score.",
226
+ ),
227
+ ] = 0.35,
228
+ normals_field_quality_minimum: Annotated[
229
+ float,
230
+ Option("--normals-field-quality-min", help="Minimum normal-field quality score."),
231
+ ] = 0.50,
232
+ segmentation_boundary_thickness: Annotated[
233
+ int,
234
+ Option("--segmentation-boundary-thickness", help="Mask boundary thickness in pixels."),
235
+ ] = 1,
236
+ segmentation_tolerance_radius: Annotated[
237
+ int,
238
+ Option("--segmentation-tolerance-radius", help="RGB edge matching tolerance in pixels."),
239
+ ] = 2,
240
+ segmentation_rgb_edge_quantile: Annotated[
241
+ float,
242
+ Option("--segmentation-rgb-edge-quantile", help="RGB edge quantile for boundary checks."),
243
+ ] = 0.90,
244
+ depth_boundary_thickness: Annotated[
245
+ int,
246
+ Option("--depth-boundary-thickness", help="Segmentation boundary thickness for depth checks."),
247
+ ] = 1,
248
+ depth_tolerance_radius: Annotated[
249
+ int,
250
+ Option("--depth-tolerance-radius", help="Depth edge matching tolerance in pixels."),
251
+ ] = 2,
252
+ depth_edge_quantile: Annotated[
253
+ float,
254
+ Option("--depth-edge-quantile", help="Depth edge quantile for boundary checks."),
255
+ ] = 0.90,
256
+ normals_eps: Annotated[
257
+ float,
258
+ Option("--normals-eps", help="Epsilon for normal-vector normalization."),
259
+ ] = 1e-6,
260
+ normals_nz_min: Annotated[
261
+ float,
262
+ Option("--normals-nz-min", help="Minimum |nz| used by normals integrability scoring."),
263
+ ] = 0.1,
264
+ limit: Annotated[
265
+ int | None,
266
+ Option("--limit", "-n", help="Maximum number of samples to process."),
267
+ ] = None,
268
+ experiment_root: Annotated[
269
+ Path,
270
+ Option(
271
+ "--experiment-root",
272
+ file_okay=False,
273
+ help="Directory where experiment artifacts will be written.",
274
+ ),
275
+ ] = Path("experiments"),
276
+ experiment_name: Annotated[
277
+ str | None,
278
+ Option("--experiment-name", help="Optional experiment folder name."),
279
+ ] = None,
280
+ log_path: Annotated[
281
+ Path | None,
282
+ Option("--log-path", file_okay=False, help="Directory where logs will be stored."),
283
+ ] = DEFAULT_LOG_PATH,
284
+ seed: Annotated[
285
+ int,
286
+ Option("--seed", help="Global seed for reproducible model execution."),
287
+ ] = 42,
288
+ json_summary: Annotated[
289
+ bool,
290
+ Option("--json", help="Print the run summary as JSON."),
291
+ ] = False,
292
+ ) -> None:
293
+ """Run a configurable pipeline from CLI options."""
294
+ config = RunConfig(
295
+ dataset=DatasetConfig(
296
+ kind=dataset,
297
+ root=root,
298
+ parquet_path=parquet_path,
299
+ recursive=recursive,
300
+ extensions=tuple(extensions or ()),
301
+ image_column=image_column,
302
+ id_column=id_column,
303
+ extension_column=extension_column,
304
+ metadata_columns=tuple(metadata_columns or ()),
305
+ batch_size=batch_size,
306
+ ),
307
+ modalities=normalize_modalities(modalities),
308
+ models=ModelConfig(
309
+ text=text_model,
310
+ segmentation=segmentation_model,
311
+ depth=depth_model,
312
+ normals=normals_model,
313
+ ),
314
+ validators=ValidatorConfig(
315
+ enabled=validators,
316
+ constraints=ConstraintConfig(
317
+ enabled=constraints,
318
+ text_siglip_minimum=text_siglip_minimum,
319
+ segmentation_mask_quality_minimum=segmentation_mask_quality_minimum,
320
+ segmentation_boundary_minimum=segmentation_boundary_minimum,
321
+ depth_imagebind_minimum=depth_imagebind_minimum,
322
+ depth_segmentation_boundary_minimum=depth_segmentation_boundary_minimum,
323
+ normals_depth_agreement_minimum=normals_depth_agreement_minimum,
324
+ normals_field_quality_minimum=normals_field_quality_minimum,
325
+ ),
326
+ segmentation_boundary_thickness=segmentation_boundary_thickness,
327
+ segmentation_tolerance_radius=segmentation_tolerance_radius,
328
+ segmentation_rgb_edge_quantile=segmentation_rgb_edge_quantile,
329
+ depth_boundary_thickness=depth_boundary_thickness,
330
+ depth_tolerance_radius=depth_tolerance_radius,
331
+ depth_edge_quantile=depth_edge_quantile,
332
+ normals_eps=normals_eps,
333
+ normals_nz_min=normals_nz_min,
334
+ ),
335
+ limit=limit,
336
+ experiment_root=experiment_root,
337
+ experiment_name=experiment_name,
338
+ log_path=log_path,
339
+ seed=seed,
340
+ json_summary=json_summary,
341
+ )
342
+ _run_or_exit(config)
343
+
344
+
345
+ @app.command(
346
+ help=(
347
+ "Run the Modosaic pipeline from a JSON, TOML, or YAML configuration file. "
348
+ "YAML requires PyYAML to be installed."
349
+ ),
350
+ short_help="Run from a config file.",
351
+ )
352
+ def pipeline(
353
+ config_path: Annotated[
354
+ Path,
355
+ Argument(
356
+ exists=True,
357
+ dir_okay=False,
358
+ help="Path to a JSON, TOML, YAML, or YML pipeline configuration file.",
359
+ ),
360
+ ],
361
+ log_path: Annotated[
362
+ Path | None,
363
+ Option("--log-path", file_okay=False, help="Override the config log directory."),
364
+ ] = None,
365
+ seed: Annotated[
366
+ int | None,
367
+ Option("--seed", help="Override the config seed."),
368
+ ] = None,
369
+ limit: Annotated[
370
+ int | None,
371
+ Option("--limit", "-n", help="Override the config sample limit."),
372
+ ] = None,
373
+ json_summary: Annotated[
374
+ bool,
375
+ Option("--json", help="Print the run summary as JSON."),
376
+ ] = False,
377
+ ) -> None:
378
+ """Run a configurable pipeline from a config file."""
379
+ try:
380
+ config = run_config_from_mapping(load_config_mapping(config_path))
381
+ except ValueError as exc:
382
+ _abort(str(exc))
383
+
384
+ if log_path is not None:
385
+ config = replace(config, log_path=log_path)
386
+ if seed is not None:
387
+ config = replace(config, seed=seed)
388
+ if limit is not None:
389
+ config = replace(config, limit=limit)
390
+ if json_summary:
391
+ config = replace(config, json_summary=True)
392
+
393
+ _run_or_exit(config)
394
+
395
+
396
+ @app.command(
397
+ help="Print valid modality and model names for CLI options and config files.",
398
+ short_help="List choices.",
399
+ )
400
+ def models() -> None:
401
+ """Print valid modality and model names."""
402
+ groups: dict[str, Sequence[Enum]] = {
403
+ "modalities": tuple(ModalityName),
404
+ "text models": tuple(TextModelName),
405
+ "segmentation models": tuple(SegmentationModelName),
406
+ "depth models": tuple(DepthModelName),
407
+ "normals models": tuple(NormalsModelName),
408
+ }
409
+ for title, values in groups.items():
410
+ echo(f"{title}:")
411
+ for value in values:
412
+ default = _default_label(value)
413
+ echo(f" {value.value}{default}")
414
+
415
+
416
+ def _run_or_exit(config: RunConfig) -> None:
417
+ """Execute a run and convert user-facing failures into CLI exits."""
418
+ try:
419
+ run_result = execute_run(config)
420
+ except (FileNotFoundError, ValueError) as exc:
421
+ _abort(str(exc))
422
+
423
+ echo(
424
+ format_summary(
425
+ run_result.results,
426
+ run_result.experiment_path,
427
+ as_json=config.json_summary,
428
+ )
429
+ )
430
+
431
+
432
+ def _default_label(value: Enum) -> str:
433
+ """Return the default-marker suffix for CLI choice output."""
434
+ defaults = {
435
+ ModalityName.IMAGE,
436
+ ModalityName.TEXT,
437
+ ModalityName.SEGMENTATION,
438
+ ModalityName.DEPTH,
439
+ ModalityName.NORMALS,
440
+ TextModelName.QWEN_2_2B,
441
+ SegmentationModelName.SAM3,
442
+ DepthModelName.DEPTH_ANYTHING_V2_SMALL,
443
+ NormalsModelName.OMNIDATA,
444
+ }
445
+ return " (default)" if value in defaults else ""
446
+
447
+
448
+ def _abort(message: str, code: int = 2) -> None:
449
+ """Print an error message and exit with the provided status code."""
450
+ echo(f"Error: {message}", err=True)
451
+ raise Exit(code)
modosaic/cli/cli.py ADDED
@@ -0,0 +1,7 @@
1
+ """Module entry point for the Modosaic CLI."""
2
+
3
+ from modosaic.cli.app import app
4
+
5
+
6
+ if __name__ == "__main__":
7
+ app()