precisionai-agritune 1.0.0__tar.gz

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 (179) hide show
  1. precisionai_agritune-1.0.0/.env.example +10 -0
  2. precisionai_agritune-1.0.0/.gitattributes +13 -0
  3. precisionai_agritune-1.0.0/.gitignore +152 -0
  4. precisionai_agritune-1.0.0/.pre-commit-config.yaml +48 -0
  5. precisionai_agritune-1.0.0/CHANGELOG.md +336 -0
  6. precisionai_agritune-1.0.0/CLAUDE.md +353 -0
  7. precisionai_agritune-1.0.0/CODE_OF_CONDUCT.md +83 -0
  8. precisionai_agritune-1.0.0/CONTRIBUTING.md +83 -0
  9. precisionai_agritune-1.0.0/LICENSE.md +201 -0
  10. precisionai_agritune-1.0.0/MANIFEST.in +13 -0
  11. precisionai_agritune-1.0.0/PKG-INFO +518 -0
  12. precisionai_agritune-1.0.0/README.md +246 -0
  13. precisionai_agritune-1.0.0/RELEASE_NOTES.md +152 -0
  14. precisionai_agritune-1.0.0/SECURITY.md +63 -0
  15. precisionai_agritune-1.0.0/assets/logo.svg +13 -0
  16. precisionai_agritune-1.0.0/precisionai/__init__.py +2 -0
  17. precisionai_agritune-1.0.0/precisionai/agritune/__init__.py +22 -0
  18. precisionai_agritune-1.0.0/precisionai/agritune/api/__init__.py +7 -0
  19. precisionai_agritune-1.0.0/precisionai/agritune/api/app.py +86 -0
  20. precisionai_agritune-1.0.0/precisionai/agritune/api/config.py +24 -0
  21. precisionai_agritune-1.0.0/precisionai/agritune/api/paths.py +47 -0
  22. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/__init__.py +19 -0
  23. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/dataset.py +37 -0
  24. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/encoder.py +52 -0
  25. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/evaluate.py +48 -0
  26. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/features.py +101 -0
  27. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/predict.py +45 -0
  28. precisionai_agritune-1.0.0/precisionai/agritune/api/routes/train.py +32 -0
  29. precisionai_agritune-1.0.0/precisionai/agritune/api/schemas.py +261 -0
  30. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/__init__.py +3 -0
  31. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/feature/__init__.py +22 -0
  32. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/feature/pipeline.py +102 -0
  33. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/feature/transforms.py +224 -0
  34. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/image/__init__.py +29 -0
  35. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/image/pipeline.py +479 -0
  36. precisionai_agritune-1.0.0/precisionai/agritune/augmentations/image/seeding.py +113 -0
  37. precisionai_agritune-1.0.0/precisionai/agritune/cli/__init__.py +10 -0
  38. precisionai_agritune-1.0.0/precisionai/agritune/cli/config.py +264 -0
  39. precisionai_agritune-1.0.0/precisionai/agritune/cli/handlers.py +327 -0
  40. precisionai_agritune-1.0.0/precisionai/agritune/cli/main.py +365 -0
  41. precisionai_agritune-1.0.0/precisionai/agritune/configs/augmentation/hybrid.yaml +12 -0
  42. precisionai_agritune-1.0.0/precisionai/agritune/configs/augmentation/none.yaml +2 -0
  43. precisionai_agritune-1.0.0/precisionai/agritune/configs/augmentation/offline.yaml +12 -0
  44. precisionai_agritune-1.0.0/precisionai/agritune/configs/augmentation/online.yaml +10 -0
  45. precisionai_agritune-1.0.0/precisionai/agritune/configs/config.yaml +54 -0
  46. precisionai_agritune-1.0.0/precisionai/agritune/configs/dataset/agricultural_segmentation.yaml +3 -0
  47. precisionai_agritune-1.0.0/precisionai/agritune/configs/decoder/aspp.yaml +6 -0
  48. precisionai_agritune-1.0.0/precisionai/agritune/configs/decoder/mask_former.yaml +9 -0
  49. precisionai_agritune-1.0.0/precisionai/agritune/configs/decoder/mlp_probe.yaml +5 -0
  50. precisionai_agritune-1.0.0/precisionai/agritune/configs/decoder/ppm.yaml +6 -0
  51. precisionai_agritune-1.0.0/precisionai/agritune/configs/decoder/segmenter.yaml +8 -0
  52. precisionai_agritune-1.0.0/precisionai/agritune/configs/decoder/token_fpn.yaml +8 -0
  53. precisionai_agritune-1.0.0/precisionai/agritune/configs/encoder/fake.yaml +6 -0
  54. precisionai_agritune-1.0.0/precisionai/agritune/configs/encoder/remote.yaml +7 -0
  55. precisionai_agritune-1.0.0/precisionai/agritune/configs/feature_augmentation/light.yaml +9 -0
  56. precisionai_agritune-1.0.0/precisionai/agritune/configs/feature_augmentation/none.yaml +8 -0
  57. precisionai_agritune-1.0.0/precisionai/agritune/configs/feature_provider/cached.yaml +3 -0
  58. precisionai_agritune-1.0.0/precisionai/agritune/configs/feature_provider/hybrid.yaml +3 -0
  59. precisionai_agritune-1.0.0/precisionai/agritune/configs/feature_provider/online.yaml +3 -0
  60. precisionai_agritune-1.0.0/precisionai/agritune/configs/optimizer/adam.yaml +3 -0
  61. precisionai_agritune-1.0.0/precisionai/agritune/configs/optimizer/adamw.yaml +3 -0
  62. precisionai_agritune-1.0.0/precisionai/agritune/configs/optimizer/sgd.yaml +4 -0
  63. precisionai_agritune-1.0.0/precisionai/agritune/configs/scheduler/cosine.yaml +3 -0
  64. precisionai_agritune-1.0.0/precisionai/agritune/configs/scheduler/cosine_warmup.yaml +4 -0
  65. precisionai_agritune-1.0.0/precisionai/agritune/configs/scheduler/linear_warmup.yaml +4 -0
  66. precisionai_agritune-1.0.0/precisionai/agritune/configs/scheduler/none.yaml +2 -0
  67. precisionai_agritune-1.0.0/precisionai/agritune/configs/scheduler/plateau.yaml +4 -0
  68. precisionai_agritune-1.0.0/precisionai/agritune/configs/scheduler/polynomial.yaml +4 -0
  69. precisionai_agritune-1.0.0/precisionai/agritune/configs/task/segmentation.yaml +7 -0
  70. precisionai_agritune-1.0.0/precisionai/agritune/configs/templates/full_config.yaml +193 -0
  71. precisionai_agritune-1.0.0/precisionai/agritune/configs/templates/manifest.csv +4 -0
  72. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/comet.yaml +3 -0
  73. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/jsonl.yaml +1 -0
  74. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/local.yaml +2 -0
  75. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/mlflow.yaml +4 -0
  76. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/neptune.yaml +3 -0
  77. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/null.yaml +1 -0
  78. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/tensorboard.yaml +1 -0
  79. precisionai_agritune-1.0.0/precisionai/agritune/configs/tracking/wandb.yaml +3 -0
  80. precisionai_agritune-1.0.0/precisionai/agritune/data/__init__.py +28 -0
  81. precisionai_agritune-1.0.0/precisionai/agritune/data/dataset.py +77 -0
  82. precisionai_agritune-1.0.0/precisionai/agritune/data/manifest.py +111 -0
  83. precisionai_agritune-1.0.0/precisionai/agritune/data/split.py +236 -0
  84. precisionai_agritune-1.0.0/precisionai/agritune/data/validation.py +170 -0
  85. precisionai_agritune-1.0.0/precisionai/agritune/encoder/__init__.py +53 -0
  86. precisionai_agritune-1.0.0/precisionai/agritune/encoder/base.py +16 -0
  87. precisionai_agritune-1.0.0/precisionai/agritune/encoder/batching.py +41 -0
  88. precisionai_agritune-1.0.0/precisionai/agritune/encoder/errors.py +47 -0
  89. precisionai_agritune-1.0.0/precisionai/agritune/encoder/fake.py +167 -0
  90. precisionai_agritune-1.0.0/precisionai/agritune/encoder/gateway.py +207 -0
  91. precisionai_agritune-1.0.0/precisionai/agritune/encoder/rate_limiter.py +122 -0
  92. precisionai_agritune-1.0.0/precisionai/agritune/encoder/remote.py +277 -0
  93. precisionai_agritune-1.0.0/precisionai/agritune/encoder/retry.py +156 -0
  94. precisionai_agritune-1.0.0/precisionai/agritune/encoder/validation.py +93 -0
  95. precisionai_agritune-1.0.0/precisionai/agritune/features/__init__.py +50 -0
  96. precisionai_agritune-1.0.0/precisionai/agritune/features/errors.py +13 -0
  97. precisionai_agritune-1.0.0/precisionai/agritune/features/integrity.py +54 -0
  98. precisionai_agritune-1.0.0/precisionai/agritune/features/keys.py +120 -0
  99. precisionai_agritune-1.0.0/precisionai/agritune/features/manifest.py +67 -0
  100. precisionai_agritune-1.0.0/precisionai/agritune/features/memory.py +334 -0
  101. precisionai_agritune-1.0.0/precisionai/agritune/features/migrate.py +83 -0
  102. precisionai_agritune-1.0.0/precisionai/agritune/features/precompute.py +126 -0
  103. precisionai_agritune-1.0.0/precisionai/agritune/features/prefetch.py +181 -0
  104. precisionai_agritune-1.0.0/precisionai/agritune/features/provider.py +252 -0
  105. precisionai_agritune-1.0.0/precisionai/agritune/features/store.py +521 -0
  106. precisionai_agritune-1.0.0/precisionai/agritune/logging/__init__.py +27 -0
  107. precisionai_agritune-1.0.0/precisionai/agritune/logging/progress.py +53 -0
  108. precisionai_agritune-1.0.0/precisionai/agritune/logging/provenance.py +202 -0
  109. precisionai_agritune-1.0.0/precisionai/agritune/logging/redaction.py +102 -0
  110. precisionai_agritune-1.0.0/precisionai/agritune/logging/setup.py +58 -0
  111. precisionai_agritune-1.0.0/precisionai/agritune/metrics/__init__.py +3 -0
  112. precisionai_agritune-1.0.0/precisionai/agritune/optimization/__init__.py +18 -0
  113. precisionai_agritune-1.0.0/precisionai/agritune/optimization/optimizers.py +66 -0
  114. precisionai_agritune-1.0.0/precisionai/agritune/optimization/registry.py +12 -0
  115. precisionai_agritune-1.0.0/precisionai/agritune/optimization/schedulers.py +99 -0
  116. precisionai_agritune-1.0.0/precisionai/agritune/schemas/__init__.py +35 -0
  117. precisionai_agritune-1.0.0/precisionai/agritune/schemas/augmentation.py +45 -0
  118. precisionai_agritune-1.0.0/precisionai/agritune/schemas/features.py +417 -0
  119. precisionai_agritune-1.0.0/precisionai/agritune/schemas/protocols.py +201 -0
  120. precisionai_agritune-1.0.0/precisionai/agritune/schemas/samples.py +71 -0
  121. precisionai_agritune-1.0.0/precisionai/agritune/services/__init__.py +48 -0
  122. precisionai_agritune-1.0.0/precisionai/agritune/services/benchmark_service.py +201 -0
  123. precisionai_agritune-1.0.0/precisionai/agritune/services/config_template_service.py +53 -0
  124. precisionai_agritune-1.0.0/precisionai/agritune/services/dataset_service.py +104 -0
  125. precisionai_agritune-1.0.0/precisionai/agritune/services/encoder_selection.py +70 -0
  126. precisionai_agritune-1.0.0/precisionai/agritune/services/evaluation_service.py +226 -0
  127. precisionai_agritune-1.0.0/precisionai/agritune/services/feature_service.py +202 -0
  128. precisionai_agritune-1.0.0/precisionai/agritune/services/prediction_service.py +265 -0
  129. precisionai_agritune-1.0.0/precisionai/agritune/services/segmentation_common.py +896 -0
  130. precisionai_agritune-1.0.0/precisionai/agritune/services/tracking_selection.py +109 -0
  131. precisionai_agritune-1.0.0/precisionai/agritune/services/training_service.py +927 -0
  132. precisionai_agritune-1.0.0/precisionai/agritune/tasks/__init__.py +3 -0
  133. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/__init__.py +26 -0
  134. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/__init__.py +20 -0
  135. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/aspp.py +103 -0
  136. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/mask_former.py +117 -0
  137. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/mlp_probe.py +74 -0
  138. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/pyramid_pooling.py +101 -0
  139. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/segmenter.py +100 -0
  140. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/decoders/token_fpn.py +136 -0
  141. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/losses.py +216 -0
  142. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/metrics.py +131 -0
  143. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/postprocessing.py +42 -0
  144. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/task.py +37 -0
  145. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/upsample.py +107 -0
  146. precisionai_agritune-1.0.0/precisionai/agritune/tasks/segmentation/visualization.py +140 -0
  147. precisionai_agritune-1.0.0/precisionai/agritune/tracking/__init__.py +27 -0
  148. precisionai_agritune-1.0.0/precisionai/agritune/tracking/comet_tracker.py +89 -0
  149. precisionai_agritune-1.0.0/precisionai/agritune/tracking/jsonl.py +45 -0
  150. precisionai_agritune-1.0.0/precisionai/agritune/tracking/mlflow_tracker.py +71 -0
  151. precisionai_agritune-1.0.0/precisionai/agritune/tracking/multi.py +57 -0
  152. precisionai_agritune-1.0.0/precisionai/agritune/tracking/neptune_tracker.py +62 -0
  153. precisionai_agritune-1.0.0/precisionai/agritune/tracking/null.py +25 -0
  154. precisionai_agritune-1.0.0/precisionai/agritune/tracking/tensorboard.py +58 -0
  155. precisionai_agritune-1.0.0/precisionai/agritune/tracking/wandb_tracker.py +58 -0
  156. precisionai_agritune-1.0.0/precisionai/agritune/training/__init__.py +36 -0
  157. precisionai_agritune-1.0.0/precisionai/agritune/training/batch.py +45 -0
  158. precisionai_agritune-1.0.0/precisionai/agritune/training/checkpointing.py +234 -0
  159. precisionai_agritune-1.0.0/precisionai/agritune/training/distributed.py +50 -0
  160. precisionai_agritune-1.0.0/precisionai/agritune/training/evaluator.py +82 -0
  161. precisionai_agritune-1.0.0/precisionai/agritune/training/precision.py +80 -0
  162. precisionai_agritune-1.0.0/precisionai/agritune/training/prefetch.py +135 -0
  163. precisionai_agritune-1.0.0/precisionai/agritune/training/state.py +87 -0
  164. precisionai_agritune-1.0.0/precisionai/agritune/training/trainer.py +460 -0
  165. precisionai_agritune-1.0.0/precisionai/agritune/utils/__init__.py +11 -0
  166. precisionai_agritune-1.0.0/precisionai/agritune/utils/env.py +98 -0
  167. precisionai_agritune-1.0.0/precisionai/agritune/utils/scaffold.py +58 -0
  168. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/PKG-INFO +518 -0
  169. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/SOURCES.txt +177 -0
  170. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/dependency_links.txt +1 -0
  171. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/entry_points.txt +2 -0
  172. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/requires.txt +48 -0
  173. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/scm_file_list.json +309 -0
  174. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/scm_version.json +8 -0
  175. precisionai_agritune-1.0.0/precisionai_agritune.egg-info/top_level.txt +1 -0
  176. precisionai_agritune-1.0.0/pyproject.toml +204 -0
  177. precisionai_agritune-1.0.0/requirements-dev.txt +20 -0
  178. precisionai_agritune-1.0.0/requirements.txt +20 -0
  179. precisionai_agritune-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,10 @@
1
+ # Copy to .env and fill in — .env is gitignored and never committed.
2
+ #
3
+ # Loaded into the environment before Hydra/OmegaConf composes the config, so
4
+ # `${oc.env:AGRITUNE_ENCODER_API_KEY,null}` in configs/encoder/remote.yaml resolves from here.
5
+ # Requires python-dotenv: pip install -e ".[dotenv]"
6
+ #
7
+ # Anything already exported in your shell wins over the values below.
8
+
9
+ # API key for the hosted encoder embeddings API (encoder=remote).
10
+ AGRITUNE_ENCODER_API_KEY=
@@ -0,0 +1,13 @@
1
+ # Normalize line endings to LF on commit, regardless of editor/OS settings.
2
+ * text=auto eol=lf
3
+
4
+ # Treat binary files as binary (no line-ending conversion).
5
+ *.png binary
6
+ *.jpg binary
7
+ *.jpeg binary
8
+ *.JPG binary
9
+ *.JPEG binary
10
+ *.svg binary
11
+ *.pdf binary
12
+ *.safetensors binary
13
+ *.ipynb text eol=lf
@@ -0,0 +1,152 @@
1
+ # Local environment / secrets — .env holds the encoder API key, .env.example is committed
2
+ .env
3
+ .env.local
4
+
5
+ # Virtual environments
6
+ .venv/
7
+ env/
8
+ venv/
9
+ ENV/
10
+ env.bak/
11
+ venv.bak/
12
+
13
+ # Byte-compiled / optimized / DLL files
14
+ __pycache__/
15
+ *.py[cod]
16
+ *$py.class
17
+
18
+ # C extensions
19
+ *.so
20
+
21
+ # Distribution / packaging
22
+ .Python
23
+ build/
24
+ develop-eggs/
25
+ dist/
26
+ downloads/
27
+ eggs/
28
+ .eggs/
29
+ lib/
30
+ lib64/
31
+ parts/
32
+ sdist/
33
+ var/
34
+ wheels/
35
+ share/python-wheels/
36
+ *.egg-info/
37
+ .installed.cfg
38
+ *.egg
39
+ MANIFEST
40
+
41
+ # PyInstaller
42
+ *.manifest
43
+ *.spec
44
+
45
+ # Installer logs
46
+ pip-log.txt
47
+ pip-delete-this-directory.txt
48
+
49
+ # Unit test / coverage reports
50
+ htmlcov/
51
+ .tox/
52
+ .nox/
53
+ .coverage
54
+ .coverage.*
55
+ .cache
56
+ nosetests.xml
57
+ coverage.xml
58
+ *.cover
59
+ *.py,cover
60
+ .hypothesis/
61
+ .pytest_cache/
62
+ cover/
63
+
64
+ # Sphinx documentation
65
+ docs/_build/
66
+ docs/readme.md
67
+
68
+ # Jupyter Notebook
69
+ .ipynb_checkpoints
70
+
71
+ # IPython
72
+ profile_default/
73
+ ipython_config.py
74
+
75
+ # pyenv
76
+ .python-version
77
+
78
+ # pipenv
79
+ Pipfile.lock
80
+
81
+ # pdm
82
+ .pdm.toml
83
+
84
+ # PEP 582
85
+ __pypackages__/
86
+
87
+ # mypy
88
+ .mypy_cache/
89
+ .dmypy.json
90
+ dmypy.json
91
+
92
+ # Pyre type checker
93
+ .pyre/
94
+
95
+ # pytype static type analyzer
96
+ .pytype/
97
+
98
+ # Cython debug symbols
99
+ cython_debug/
100
+
101
+ # PyCharm
102
+ .idea/
103
+
104
+ # VS Code
105
+ .vscode/
106
+
107
+ # PyTorch / Machine Learning artefacts
108
+ *.pth
109
+ *.pt
110
+ *.ckpt
111
+ *.pkl
112
+ *.h5
113
+ *.hdf5
114
+ *.onnx
115
+ *.pb
116
+ *.tflite
117
+ *.tfl
118
+ checkpoints/
119
+ models/
120
+ weights/
121
+ wandb/
122
+ mlruns/
123
+ .neptune/
124
+
125
+ # AgriTune run artefacts
126
+ runs/
127
+ feature_store/
128
+ *.safetensors
129
+
130
+ # Downloaded / prepared example datasets (see examples/datasets/)
131
+ examples/datasets/cwfid/
132
+ examples/datasets/phenobench/
133
+ sample_data/
134
+
135
+ # Claude Code local runtime state — session-local, changes every run
136
+ .claude/scheduled_tasks.lock
137
+
138
+ # OS files
139
+ .DS_Store
140
+ .DS_Store?
141
+ ._*
142
+ .Spotlight-V100
143
+ .Trashes
144
+ ehthumbs.db
145
+ Thumbs.db
146
+ *~
147
+
148
+ # Temporary files
149
+ *.tmp
150
+ *.temp
151
+ *.swp
152
+ *.swo
@@ -0,0 +1,48 @@
1
+ repos:
2
+ # ── File hygiene ────────────────────────────────────────────────────────────
3
+ - repo: https://github.com/pre-commit/pre-commit-hooks
4
+ rev: v6.0.0
5
+ hooks:
6
+ - id: check-added-large-files
7
+ args: ["--maxkb=1800"]
8
+ - id: check-yaml
9
+ - id: check-json
10
+ - id: check-toml
11
+ - id: end-of-file-fixer
12
+ exclude: ^\.github/badges/
13
+ - id: trailing-whitespace
14
+ args: ["--markdown-linebreak-ext=md"]
15
+ exclude: ^\.github/badges/
16
+ - id: check-merge-conflict
17
+ - id: detect-private-key
18
+ - id: debug-statements
19
+ - id: fix-byte-order-marker
20
+
21
+ # ── Ruff: lint + format + import sorting ───────────────────────────────────
22
+ - repo: https://github.com/astral-sh/ruff-pre-commit
23
+ rev: v0.15.17
24
+ hooks:
25
+ - id: ruff
26
+ args: ["--fix"]
27
+ - id: ruff-format
28
+
29
+ # ── Type checking (pyright / pylance engine) ────────────────────────────────
30
+ - repo: local
31
+ hooks:
32
+ - id: pyright
33
+ name: pyright
34
+ entry: python -m pyright
35
+ language: system
36
+ types: [python]
37
+ pass_filenames: false
38
+
39
+ # ── Tests + coverage (≥ 90%) ────────────────────────────────────────────────
40
+ - repo: local
41
+ hooks:
42
+ - id: pytest
43
+ name: pytest (coverage ≥ 90%)
44
+ entry: python -m pytest
45
+ language: system
46
+ args: ["tests/", "-q", "--tb=short", "--no-header", "-m", "not integration and not distributed"]
47
+ pass_filenames: false
48
+ always_run: true
@@ -0,0 +1,336 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Performance
11
+
12
+ - `feature_preload: host | device` (new `TrainingRunConfig` field, default `none`): reads the
13
+ training and validation splits' cached features once, before training, into one contiguous
14
+ buffer in RAM or GPU memory (new `precisionai.agritune.features.memory.InMemoryFeatureProvider`),
15
+ and decodes every mask once into a compact `uint8` cache (new
16
+ `segmentation_common.PreloadedBatches`). Each batch then becomes one gather, instead of a
17
+ per-sample store read, validation, and concatenation plus a PNG decode per mask every epoch.
18
+ Measured on an A100 with 113,584 training and 37,707 validation samples and a linear probe,
19
+ with every change in this release, epoch times including validation went from 1,921 s (reading
20
+ a sharded store) to 51–62 s with 57x38 grids in host RAM and 7.5 s with 16x16 grids on the GPU.
21
+ A 100-epoch run at 798x532 masks that took about 80 hours now takes 3 h 45 min end to end, and
22
+ reaches the same test metrics (mean IoU within 0.05 pp). Requires `feature_provider: cached`.
23
+ - Preloading reads each shard once and copies entries into the buffer on a small thread pool
24
+ (5.4 minutes for 252 GB of `float16` features, 3.2x faster than a single thread), and
25
+ `InMemoryFeatureProvider` resolves a sample's row by object identity after its first lookup, so
26
+ cache keys are not rehashed every epoch.
27
+ - `feature_preload_dtype: float16 | bfloat16 | float32` stores preloaded tokens in a smaller
28
+ dtype (cast back to `float32` on the device before the decoder); a value that would overflow
29
+ is rejected while preloading.
30
+ - `shuffle: true` (requires `feature_preload`) reshuffles training samples every epoch in an order
31
+ derived from `seed` and the epoch index, so a resumed run replays the same order.
32
+ - Every decoder (and the loss/metric resize fallbacks) now resizes logits with
33
+ `tasks.segmentation.upsample.bilinear_resize`, which computes exactly what
34
+ `F.interpolate(mode="bilinear", align_corners=False)` does as two small matrix products.
35
+ PyTorch's CUDA backward for bilinear upsampling scatters gradients with atomic adds, which took
36
+ 48 of 61 ms per linear-probe step when upsampling a 16x16 grid to 224x224; the matrix form takes
37
+ 8 ms for the whole step. Values match `F.interpolate` to floating-point rounding.
38
+ - `PrefetchingFeatureLoader` now issues its copies on a dedicated CUDA stream, so the next batch's
39
+ host-to-device transfer actually overlaps the current step instead of queueing behind it on the
40
+ default stream.
41
+ - `CachedFeatureProvider` keys no longer re-read and re-hash every image file on every epoch:
42
+ `build_image_hash_fn` memoizes each sample's image hash for the life of the run.
43
+ - `ShardedFeatureStore.read()` looks entries up in a per-shard index instead of scanning every
44
+ tensor name in the shard for each read, and both stores gain `read_many()`, which loads each
45
+ shard file once no matter what order keys are requested in.
46
+ - `EncoderFeatures.to()` no longer re-runs full validation (a non-finite scan plus host reads,
47
+ which force a GPU sync on every batch); new `EncoderFeatures.trusted()` builds an instance from
48
+ already-validated tensors.
49
+
50
+ ### Security
51
+
52
+ - Every API request field naming a filesystem path (`manifest_path`, `store`, `checkpoint_path`,
53
+ `output_dir`, `config_path`, `augmentation_config_path`) is now resolved through a new
54
+ `precisionai.agritune.api.paths.resolve_under_root` helper and rejected with HTTP 400 if it
55
+ would resolve outside the configured API root (`AGRITUNE_API_ROOT`, defaulting to the server's
56
+ current working directory) — closing a path-traversal gap where an absolute path or a `..`
57
+ segment let a request read or write anywhere the server process could access (e.g.
58
+ `checkpoint_path` into `torch.load`, `output_dir` for prediction PNGs, `config_path` into
59
+ training). This API still has no built-in authentication by design — see `SECURITY.md`.
60
+ - Pull-request CI now requests `contents: read` only, reusable unit-test/pre-commit jobs declare
61
+ the same, and their checkouts set `persist-credentials: false` so pull-request code cannot run
62
+ with a repository write token.
63
+ - Logging redacts API keys, Authorization values, sensitive Hydra/dotlist overrides, and URL
64
+ userinfo/query credentials before emit, including DEBUG override dumps and encoder base URLs.
65
+
66
+ ### Documentation
67
+
68
+ - `docs/datasets.md` explains that `agritune train` splits its own manifest into training and
69
+ validation (`val_fraction`, per sample, ignoring any `split` column), so test samples belong in a
70
+ separate manifest for `agritune evaluate`, with a worked example that holds out whole fields via
71
+ `grouped_split`. `docs/configuration.md`, `docs/reproducibility.md`, `docs/segmentation.md`,
72
+ `docs/feature-caching.md`, and the README cover preloading, shuffling, seeding, and output
73
+ resolution.
74
+
75
+ ### Fixed
76
+
77
+ - Two `tests/unit/test_features.py` tests failed on Python 3.13 (and only there) with `Boolean value
78
+ of Tensor with more than one value is ambiguous`: they compared `EncoderFeatures` instances with
79
+ `==`, which the generated dataclass `__eq__` can only answer when both sides hold the same tensor
80
+ objects, and 3.13 now compares field by field. They compare the fields explicitly. No library
81
+ behavior changed.
82
+ - Training under `augmentation.mode: none` with `geometric.resize` built the decoder at the
83
+ dataset's *native* mask size, because the output-size probe skipped the resize that every
84
+ training batch still receives. The decoder then upsampled to the native size (8.5x more pixels
85
+ for 798x532 masks resized to 224x224), and the loss and metric resized every batch's logits back
86
+ down: three full-size resizes per step. The probe now applies `resize` exactly like
87
+ `build_training_batches` (and like `prediction_service` already did). On an A100 this alone cut
88
+ a 20,000-sample, two-epoch linear-probe run from 36.1 s to 4.7 s.
89
+ - `EncoderFeatures.uniform_patch_grid` reads the batch's patch grids in one host transfer instead
90
+ of one per sample (each a GPU synchronization when the grid is on a CUDA device).
91
+ - The decoder's initial weights are now drawn right after seeding the global RNG from
92
+ `trainer.seed`. Previously they depended on whatever had advanced the RNG earlier in the process
93
+ (every `DataLoader` pass draws a seed from it), so the same config could start from different
94
+ weights depending on unrelated code paths. New runs therefore start from different (but now
95
+ seed-determined) weights than earlier versions did for the same config; resuming an existing
96
+ checkpoint is unaffected.
97
+ - `SegmentationLoss` and `SegmentationMetric` accept class-index targets in any integer dtype
98
+ (widened to `int64` internally); a `uint8` target used to overflow the metric's
99
+ `targets * num_classes + predictions` index.
100
+ - Pyright generic-typing error in `segmentation_common._IndexDataset`.
101
+ - Pyright optional-palette subscript in `colorize_predictions` and live-test API-key narrowing
102
+ after `pytest.skip`.
103
+ - `SegmentationMetric.update` now follows `outputs`/`targets` onto whatever device they're
104
+ actually on (its confusion-matrix accumulator used to stay fixed on CPU, raising a
105
+ device-mismatch error the first time `Trainer`/`evaluate()` moved a batch to CUDA).
106
+ - Feature-space augmentation (`augmentations.feature.transforms`) now moves every random
107
+ mask/noise tensor it draws onto the same device as the features it perturbs, instead of
108
+ leaving it on the CPU-based checkpointed RNG's device — the same class of device-mismatch bug,
109
+ triggered whenever `feature_augmentation` is enabled on a CUDA training run.
110
+
111
+ ### Added
112
+
113
+ - `TrainingRunConfig.device` (`"cpu"`, `"cuda"`, or `"cuda:N"`): the decoder and every batch's
114
+ features/targets now actually move to the configured device before `forward`/`compute_loss`
115
+ (previously nothing in the training pipeline ever called `.to()`, so `trainer.precision: fp16`
116
+ silently never engaged CUDA's gradient scaler). Rejected up front via a new
117
+ `_validate_device_config` if it names a CUDA device that is unavailable or out of range for the
118
+ machine's GPU count — never a silent fallback to CPU. Added `EncoderFeatures.to(device)` and a
119
+ `device` parameter on `evaluate()` to support this.
120
+ - `TrainingRunConfig.feature_read_workers`: exposes `CachedFeatureProvider`'s internal read
121
+ thread-pool size (previously a fixed constant) so it can be tuned independently of the
122
+ `DataLoader`'s `num_workers` — the dominant per-batch cost once `feature_provider: cached` skips
123
+ image loading entirely.
124
+ - `precisionai.agritune.training.prefetch.PrefetchingFeatureLoader`: overlaps one batch's feature
125
+ fetch (a `FeatureProvider.get_features` call — a disk/store read for `cached`/`hybrid`) with the
126
+ previous batch's model compute in a single background thread, wired into both `Trainer`'s
127
+ training loop and `evaluate()`, so the GPU is no longer idle waiting on a synchronous feature
128
+ read every batch.
129
+ - Info/debug/warning logging across previously-silent modules (services, encoder, evaluator),
130
+ plus `tqdm` progress bars over every long-running loop (training/validation batches,
131
+ evaluation, prediction, feature precompute, encoder benchmarking, and the CWFID/PhenoBench prep
132
+ scripts) via a new `precisionai.agritune.logging.progress_iter` helper. Progress bars are
133
+ opt-in per call (`show_progress`, default `False`) so the API stays headless; `Trainer`/
134
+ `evaluate()` gate them on `DistributedContext.is_main_process`, ready for the eventual DDP
135
+ rollout. The FastAPI app now calls `configure_logging()` on startup
136
+ (`AGRITUNE_LOG_LEVEL`, default `INFO`), and its Rich console handler now writes to stderr
137
+ instead of stdout so log lines never interleave with a CLI command's own JSON output.
138
+ - CWFID example: `examples/datasets/prepare_cwfid.py` downloads the public Crop/Weed Field Image
139
+ Dataset, writes an AgriTune manifest, and `examples/SANITY_CHECK.md` runs the full hosted-encoder
140
+ pipeline against it. A `--full` flag downloads all 60 frames without needing to know the exact
141
+ count.
142
+ - PhenoBench example: `examples/datasets/prepare_phenobench.py` downloads the public PhenoBench
143
+ sugar-beet dataset (1,407 train / 772 val real UAV field images, vs. CWFID's 60) and writes an
144
+ AgriTune manifest, collapsing its 5-way partial-visibility labels into AgriTune's 3-class
145
+ background/crop/weed convention.
146
+ - Optional `.env` support (`pip install precisionai-agritune[dotenv]`): a `.env` file is loaded into the
147
+ environment before Hydra composition, so `${oc.env:AGRITUNE_ENCODER_API_KEY,null}` in
148
+ `encoder/remote.yaml` resolves from it. Exported shell variables still take precedence, and
149
+ `AGRITUNE_ENV_FILE` selects a file outside the working directory.
150
+ - Repository foundations: packaging, CI, pre-commit, structured logging, CLI entrypoint (`agritune --help`).
151
+ - Core schemas/protocols, agricultural dataset/manifest/split system, deterministic augmentation
152
+ pipeline, encoder abstraction (fake + remote) with a rate-limited/retrying gateway, feature
153
+ cache and resumable precomputation, `CachedFeatureProvider`, segmentation task (linear + TokenFPN
154
+ decoders, losses, metrics), training engine (AMP, gradient accumulation, checkpointing/exact
155
+ resume, provenance), optimizer/scheduler registries, and JSONL/TensorBoard/MLflow/W&B tracking.
156
+ - `agritune features verify/inspect/clean`, `agritune evaluate`, `agritune predict`, and
157
+ `agritune encoder benchmark` commands, backed by their own services.
158
+ - `OnlineFeatureProvider` and `HybridFeatureProvider`, bridging an `EncoderGateway` into the
159
+ synchronous `FeatureProvider` interface (the v0.2 online/hybrid feature path).
160
+ - Feature-space augmentation (`augmentations.feature`): patch dropout, token masking, Gaussian
161
+ feature noise, CLS dropout, and channel dropout, wired into `Trainer` as an optional train-only
162
+ step.
163
+ - Per-class precision/recall in `SegmentationMetric`; a configurable `checkpoint_top_k` for
164
+ validation-ranked periodic checkpoints; a `temporal_split` dataset split strategy; a prediction
165
+ visualization module (`--overlays` on `agritune predict`); `NeptuneTracker` and `CometTracker`.
166
+ - Agricultural domain-specific image augmentations: sensor channel dropout, ground-sample-distance
167
+ (GSD) jitter, a red/green vegetation-index-balance jitter, and a seasonal/growth-stage hue shift.
168
+ - `augmentation.mode: hybrid` — deterministically reuses an offline variant most of the time, and
169
+ derives a fresh online seed for a configurable fraction of `(sample, epoch, occurrence)` combinations.
170
+ - `PrefetchingFeatureProvider` (`precisionai.agritune.features.prefetch`): overlaps encoding with
171
+ training via a bounded background queue, so an online/hybrid feature provider's network latency
172
+ is hidden behind the GPU training step instead of blocking it. Cancellable mid-consumption (e.g.
173
+ early stopping) without deadlocking `close()`.
174
+ - The API layer (`precisionai.agritune.api`): a thin FastAPI app (`create_app()`) with one route
175
+ per CLI command (`dataset validate/inspect`, `features build/verify/inspect/clean`, `train`,
176
+ `evaluate`, `predict`, `encoder benchmark`), calling the exact same `services/` functions as the
177
+ CLI. `ValueError`/`FileNotFoundError`/`FeatureNotCachedError`/`CheckpointMismatchError` map to
178
+ 400/404/404/409 respectively; everything else surfaces as a 500, same as an uncaught CLI
179
+ traceback. Encoder-backend selection was extracted into `services.encoder_selection`, shared by
180
+ both entry points.
181
+ - Real Hydra config-group composition (`precisionai/agritune/configs/`): named YAML variants under
182
+ `dataset/`, `encoder/`, `augmentation/`, `feature_provider/`, `task/`, `decoder/`, `optimizer/`,
183
+ `scheduler/`, and `tracking/`, composed via a `defaults:` list in the packaged `config.yaml`.
184
+ `cli.config.load_training_run_config` resolves either shape: a plain flat YAML file (unchanged,
185
+ original behavior) or a `defaults:`-bearing file resolved through `hydra.compose()`, with the
186
+ same `key=value` override list also able to swap group variants (`decoder=token_fpn`,
187
+ `augmentation=online`). `feature_provider` (`cached`/`online`/`hybrid`), `augmentation`
188
+ (`none`/`offline`/`online`/`hybrid`), and `tracking` selection are now wired end-to-end into
189
+ `run_training`, which rejects the `augmentation=online|hybrid` + `feature_provider=cached`
190
+ combination before training starts.
191
+ - Four new segmentation decoders, alongside a generalized MLP probe replacing the old
192
+ linear-only baseline: `ASPPDecoder` (Atrous Spatial Pyramid Pooling, DeepLabv3-style),
193
+ `PyramidPoolingDecoder` (PSPNet-style pyramid pooling), `SegmenterMaskTransformerDecoder`
194
+ (Segmenter-style joint patch/class-token transformer with a scaled dot-product mask head), and
195
+ `MaskFormerDecoder` (MaskFormer/Mask2Former-style query-based mask classification, combined into
196
+ dense per-pixel scores rather than trained with the original papers' bipartite-matching loss).
197
+ `LinearProbeDecoder` is replaced by `MLPProbeDecoder` (`decoder=mlp_probe`): with
198
+ `hidden_dims=()` (the default) it is architecturally identical to the old linear probe;
199
+ `hidden_dims=(256,)` etc. adds non-linear depth. `build_decoder` now accepts a generic
200
+ `decoder_kwargs` passthrough forwarded to any decoder's constructor (also newly closing a gap
201
+ for `TokenFPNDecoder`'s `cls_fusion`/`hidden_dim`, previously unconfigurable outside direct
202
+ Python construction) — set via each decoder's own Hydra config-group variant
203
+ (`configs/decoder/{mlp_probe,token_fpn,aspp,ppm,segmenter,mask_former}.yaml`).
204
+ - `Trainer.fit`'s optional `train_metric` parameter: a `Metric` accumulated over every training
205
+ batch each epoch (reset at the epoch's start) and logged as `train_{name}` (e.g. `train_mean_iou`)
206
+ alongside the existing `train_loss`/`lr`, exposed via `Trainer.last_train_metrics`. `run_training`
207
+ now always passes one, so `TrainingRunResult.train_metrics` / `agritune train`'s printed summary
208
+ / `POST /train`'s response report full segmentation metrics for the training set too, not just
209
+ loss — useful for spotting a train/val gap, though noisier than validation since it reflects a
210
+ moving model on (possibly augmented) training batches.
211
+ - `Evaluator.evaluate` (shared by `Trainer`'s validation pass and `agritune evaluate`) now also
212
+ computes a batch-size-weighted mean loss and adds it to its returned dict as `"loss"`, so
213
+ `val_metric_name: loss` (with `higher_is_better: false`) works as a checkpoint/early-stopping
214
+ signal. `EvaluationRunConfig`/`agritune evaluate --loss-*`/`POST /evaluate`'s `loss` field let the
215
+ loss config used for this be set to match what a checkpoint was actually trained under.
216
+ - `agritune features build --augmentation-config <path> --seed <int>` (and `POST /features/build`'s
217
+ matching `augmentation_config_path`/`seed` fields): precomputes a deterministic offline-augmented
218
+ variant alongside the unaugmented features, closing a gap where `feature_service.build_features`
219
+ already supported `augmentation_mode=OFFLINE` but neither entry point exposed it. The
220
+ `--augmentation-config` file is the same `mode`/`variant`/`geometric`/`photometric` shape as
221
+ `configs/augmentation/*.yaml` — point both the build and the training config's `augmentation:`
222
+ block at the same file so their cache keys match. New `cli.config.load_augmentation_selection`
223
+ shares the parsing between the CLI and the API route.
224
+ - `TrainingRunConfig.feature_augmentation` (`feature_augmentation:` in a flat YAML config, or the
225
+ new `feature_augmentation` Hydra group — `none`/`light` — in the composed one), closing a gap
226
+ where `FeatureAugmentationPipeline`/`FeatureAugmentationConfig` (patch dropout, token masking,
227
+ Gaussian feature noise, CLS dropout, channel dropout) were fully implemented and accepted by
228
+ `Trainer`'s constructor but never reachable from `agritune train`/`POST /train` — only from
229
+ direct Python construction. Included in the run's critical-config reproducibility fingerprint,
230
+ alongside image-space `augmentation`.
231
+ - `agritune config init --output <path> [--force]`: writes a fully-commented, flat training config
232
+ template spelling out every field `TrainingRunConfig` accepts at its default value, with the
233
+ dataset-specific fields marked `REQUIRED` as placeholders — the same shape as
234
+ `examples/segmentation/cwfid.yaml` without the CWFID-specific values. Backed by the new
235
+ `services.config_template_service`.
236
+ - `agritune dataset init --output <path> [--force]`: writes an example manifest CSV (a header row
237
+ plus a few placeholder samples) to start a new dataset's manifest from. Backed by
238
+ `services.dataset_service.write_manifest_template`, sharing its file-writing/overwrite-guard
239
+ logic with `config init` via the new `utils.scaffold` helper.
240
+
241
+ ### Changed
242
+
243
+ - The PyPI distribution is now named `precisionai-agritune` (it was `pai-agritune`, which was never
244
+ published), matching `precisionai-agrieval`. Install with `pip install precisionai-agritune`; the
245
+ import package (`precisionai.agritune`) and the `agritune` command are unchanged. `release.yml`,
246
+ the README (including its PyPI and Python-version badges), `SECURITY.md`, the bug-report
247
+ template, install hints in error messages and tracking configs, and the tests that assert them
248
+ now use the new name.
249
+ - README: primary header is now "Precision AI AgriTune"; badges moved below the description; the
250
+ stale "Status" section (internal build-phase notes) was removed; `Installation` no longer opens
251
+ the document — a runnable `Quick Start` does, followed by `Project layout`, with a new
252
+ "FastAPI server" subsection under `Installation` covering the `[api]` extra and `uvicorn`.
253
+ `Quick Start` walks the packaged CWFID example end-to-end (dataset prep, validate, feature
254
+ build, train, evaluate) instead of a synthetic placeholder manifest, with a `fake-encoder`
255
+ fallback for anyone without hosted-encoder access yet.
256
+ - `pyproject.toml`'s `api` extra now also installs `uvicorn`, so `pip install "precisionai-agritune[api]"`
257
+ is sufficient to run the FastAPI server (previously required a separate manual `pip install
258
+ uvicorn`).
259
+ - The image augmentation pipeline (`augmentations.image`) now composes
260
+ [albumentations](https://albumentations.ai/) transforms instead of hand-rolled PIL/numpy code,
261
+ using `Compose.set_random_seed` for the same exact-reproducibility guarantee as before.
262
+
263
+ ### Fixed
264
+
265
+ - `requirements.txt` no longer hard-pins a CUDA build of `torch` (`torch==2.14.0+cu130` via
266
+ `--extra-index-url .../cu130`) — that install fails outright on CPU-only/non-Linux machines,
267
+ contradicting this repo's own CPU-only CI. Now a plain `torch>=2.2`, matching `pyproject.toml`;
268
+ a comment (and a matching README note) shows the two-step CPU-only install
269
+ (`pip install torch --index-url https://download.pytorch.org/whl/cpu`, same as
270
+ `unit-test.yml`) needed on Linux, where PyPI's default wheel still bundles the full CUDA
271
+ runtime even on a machine with no GPU, plus how to opt into a specific CUDA build instead.
272
+ - `CODE_OF_CONDUCT.md` was missing the "Enforcement Guidelines" section (Correction/Warning/
273
+ Temporary Ban/Permanent Ban) from the official Contributor Covenant v2.1 text — restored
274
+ verbatim.
275
+ - `LICENSE.md` had several small wording drifts from the canonical Apache 2.0 text — replaced with
276
+ the exact upstream text.
277
+ - `.github/dependabot.yml` was missing the `groups: python-dependencies: patterns: ["*"]` block on
278
+ its `pip` update, so routine dependency bumps opened one PR per package instead of one grouped
279
+ weekly PR.
280
+ - `examples/SANITY_CHECK.md` and `examples/FEATURE_TEST_COMMANDS.md` pointed evaluate/predict at
281
+ `runs/cwfid-sanity/checkpoints/...`, a run_id `examples/segmentation/cwfid.yaml` never actually
282
+ produces (it sets `run_id: cwfid-sanity-mlp_probe-feature_aug`) — the checkpoint path in both
283
+ docs never existed. Corrected to the run_id the config actually writes.
284
+ - Flush partial gradient-accumulation windows instead of silently dropping their gradients.
285
+ - Execute gateway-split encoder batches concurrently while preserving response order.
286
+ - Preserve augmentation fingerprints through training batches so hybrid cache keys cannot reuse
287
+ stale features across different augmented images.
288
+ - Harden variable-grid feature masking, remote-response validation, checkpoint fingerprints,
289
+ secret-safe run provenance, and tracker-failure isolation.
290
+ - Persist early-stopping progress across resume so training cannot run extra epochs after a stop.
291
+ - Derive feature-cache keys from a canonical JSON payload so delimiter characters in sample IDs
292
+ cannot collide with adjacent fingerprint fields.
293
+ - Decode CLS embeddings from little-endian float32 base64, matching `encoding_format="base64"` on
294
+ the hosted encoder API — a float-list-only parser crashed on the real `pai-embedding` response.
295
+ - Defer `comet_ml`'s import to `CometTracker` construction instead of importing it at module load.
296
+ `comet_ml` auto-instruments other frameworks in the process (notably `mlflow`) merely by being
297
+ imported; since `tracking_selection.py` imports every tracker module up front, selecting any
298
+ *other* backend (e.g. `mlflow`) silently created an uninvited offline Comet experiment whenever
299
+ `comet_ml` happened to be installed alongside it — which it always is, since both ship in the
300
+ `precisionai-agritune[tracking]` extra.
301
+ - Derive a training run's decoder `output_size` from the actually-augmented probe sample instead
302
+ of the raw pre-augmentation one, so `augmentation.geometric.resize`/`random_crop` (which change
303
+ spatial size) no longer builds a decoder upsampling to the wrong resolution.
304
+ `SegmentationLoss.forward`/`SegmentationMetric.update` now also bilinearly resize logits to the
305
+ target's spatial size when they differ, since validation targets are never augmented and can
306
+ therefore legitimately disagree with training's working resolution under one fixed decoder.
307
+ - Log every encoder retry attempt (`EncoderGateway`) at `WARNING` instead of only counting it in
308
+ metrics — a bounded retry-then-fail run and an actual hang were previously indistinguishable from
309
+ the terminal, since nothing was printed during the backoff sleeps between attempts.
310
+ - CI (`unit-test.yml`/`pre-commit.yml`): install the CPU-only `torch` wheel
311
+ (`--index-url https://download.pytorch.org/whl/cpu`) before `pip install -e ".[dev]"` — the
312
+ default Linux PyPI wheel bundles the full CUDA runtime (several GB of `nvidia-*`/`triton`
313
+ packages) that a CPU-only CI runner never uses, and was exhausting the runner's disk.
314
+ - `evaluation_service.py`/`prediction_service.py`/`training_service.py`: build a decoder's
315
+ `output_size` via a new `segmentation_common.mask_output_size` helper that returns a concrete
316
+ `tuple[int, int]`, instead of `tuple(np.array(mask).shape)` (typed as a variable-length
317
+ `tuple[int, ...]`) — fixes a `pyright` `reportArgumentType` error that a newer `numpy` type-stub
318
+ resolution surfaces on some Python versions.
319
+ - `trailing-whitespace` pre-commit hook: pass `--markdown-linebreak-ext=md` so it stops stripping
320
+ intentional Markdown hard-break trailing spaces (it was rewriting `examples/SANITY_CHECK.md` on
321
+ every run).
322
+ - Fix the remaining pre-existing `pyright` errors, none introduced by recent work but all blocking
323
+ a clean `pre-commit`/CI run: `LossConfigRequest.name` (API schema) now uses the same `LossName`
324
+ literal as `SegmentationLossConfig` instead of a bare `str`; `ImageAugmentationPipeline` builds
325
+ its transform list typed as albumentations' own `TransformsSeqType` instead of the invariant-
326
+ incompatible `list[BasicTransform]`; `PrecisionContext.autocast()` is typed to yield `Any`
327
+ (`nullcontext`/`torch.autocast` disagree on what `__enter__` returns, but no caller binds it);
328
+ `cli/config.py`'s `_config_from_dict(resolved)` call gets the same `# type: ignore[arg-type]`
329
+ treatment the file already uses for `OmegaConf.to_container`'s deliberately-broad return type;
330
+ and several tests narrow an `Optional`/generic-`nn.Module` value with an `assert ... is not None`
331
+ / `isinstance` check before using it, instead of relying on runtime knowledge pyright can't see.
332
+ - Untrack `.claude/scheduled_tasks.lock` (a Claude Code runtime lock file — session ID/PID/
333
+ timestamp, changes every session) and gitignore it; it was flapping `end-of-file-fixer` on
334
+ unrelated commits.
335
+
336
+ [Unreleased]: https://github.com/Precision-AI-Inc/agritune/compare/v0.1.0...HEAD