-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
375 lines (358 loc) · 19.1 KB
/
Copy pathpyproject.toml
File metadata and controls
375 lines (358 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "syncai-hydranet"
# Single source of truth: src/syncai_hydranet/__init__.py
dynamic = ["version"]
description = "Multi-head perception network for fixed store CCTV: person and product detection, pose, and terrain segmentation in a single forward pass."
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0"
license-files = ["LICENSE"]
authors = [{ name = "SYNCROBOTIC" }]
# Keywords are what a PyPI or GitHub search actually matches on, so they name the
# things someone would type when they do not yet know this project exists: the task,
# the architecture parts, and the hardware it deploys to.
keywords = [
"computer-vision",
"perception",
"multi-task-learning",
"semantic-segmentation",
"object-detection",
"pose-estimation",
"keypoint-detection",
"terrain-classification",
"cctv",
"retail-analytics",
"loss-prevention",
"video-surveillance",
"hydranet",
"bifpn",
"fcos",
"pytorch",
"onnx",
"tensorrt",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: GPU :: NVIDIA CUDA",
"Intended Audience :: Science/Research",
"Intended Audience :: Developers",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Scientific/Engineering :: Image Processing",
]
dependencies = [
"torch>=2.1",
"torchvision>=0.16",
"numpy>=1.24",
"pyyaml>=6.0",
"pillow>=10.0",
"pycocotools>=2.0.7",
"tqdm>=4.66",
"tensorboard>=2.15",
"pyarrow>=25.0.1",
]
[project.optional-dependencies]
# The `<1.23` cap is gone with the 3.10 floor it existed for. 1.23 dropped cp310 wheels
# and 1.24 needs cp311+, so the cap and `.github/dependabot.yml`'s matching ignore were
# both holding one constraint: that a bump onnxruntime had already made could not be
# taken while `requires-python` said 3.10. Both said "raise it when the floor moves to
# 3.11" and the floor has moved.
export = ["onnx>=1.15", "onnxsim>=0.4", "onnxruntime>=1.17"]
# SAM 3 pre-labelling (scripts/sam3_prelabel.py). An annotation-time tool, never a
# runtime dependency: nothing under src/ imports transformers, and an inference box must
# not carry a 6.5 GB checkpoint it never runs. `facebook/sam3` is a gated repo, so this
# installs the code and leaves the weights to `hf auth login`.
annotate = ["transformers>=5.0"]
# Benchmarking on an NVIDIA box. Two of these were installed by hand and imported by
# `scripts/` for weeks without ever being declared, which `uv sync` is entitled to undo:
# `bench_trt.py`'s fp16 conversion needs `onnxconverter_common`, and `bench_e2e.py`'s
# NVDEC leg needs `PyNvVideoCodec` -- the decode backend PLAN §7 decision 8 chose on
# 2026-08-25, which took decode from 63-69 streams to ~520 and stopped it being the
# binding leg. An undeclared dependency that a sync can prune is a measurement that
# stops reproducing for no reason anyone can see.
bench = [
"matplotlib>=3.10.9",
"scipy>=1.15.3",
"tensorrt>=11.2.1.2",
"onnxconverter-common>=1.16",
"pynvvideocodec>=2.2.1",
]
# The per-camera commissioning toolchain (`tools/commissioning/`, `scripts/propose_zones.py`,
# `scripts/hm3d_render.py`). Three imports that were reachable only by accident:
#
# trimesh `scene_mesh.py` and `hm3d_render.py` build the GLB/OBJ scene, and it was
# declared in the **dev group** -- so the wheel's own mesh export needed a
# contributor's test environment to run.
# contourpy `service_zones.py` traces the zone polygons with it and it was declared
# **nowhere**; it happened to be installed as a matplotlib transitive.
# scipy used by 11 files under `tools/` and 10 under `scripts/`, and its only
# declaration was in `bench` -- which also pulls `tensorrt`, so getting
# scipy for `propose_zones.py` meant installing several GB of CUDA runtime.
#
# `scipy` and `trimesh` stay listed where they already were as well. An extra is a set,
# not a location, and moving them would break an environment somebody has synced today
# for no gain; naming them here is what makes `uv sync --extra commission` sufficient.
commission = [
"trimesh>=5.0.0",
"contourpy>=1.3",
"scipy>=1.15.3",
]
[project.scripts]
hydranet-train = "syncai_hydranet.cli.train:main"
hydranet-eval = "syncai_hydranet.cli.evaluate:main"
hydranet-infer-image = "syncai_hydranet.cli.infer_image:main"
hydranet-infer-video = "syncai_hydranet.cli.infer_video:main"
hydranet-scene = "syncai_hydranet.cli.scene:main"
hydranet-export-onnx = "syncai_hydranet.cli.export_onnx:main"
hydranet-prepare-ade20k = "syncai_hydranet.cli.prepare_ade20k:main"
hydranet-prepare-cocostuff = "syncai_hydranet.cli.prepare_cocostuff:main"
hydranet-annotation = "syncai_hydranet.cli.annotation:main"
hydranet-report = "syncai_hydranet.cli.report:main"
[project.urls]
Homepage = "https://github.com/Syncrobotic/SyncAI-Lib-HydraNet"
Repository = "https://github.com/Syncrobotic/SyncAI-Lib-HydraNet"
Documentation = "https://github.com/Syncrobotic/SyncAI-Lib-HydraNet/tree/main/docs"
Issues = "https://github.com/Syncrobotic/SyncAI-Lib-HydraNet/issues"
# Dev tooling lives in a PEP 735 dependency group: installed by `uv sync`,
# excluded from the published wheel and from `pip install syncai-hydranet`.
[dependency-groups]
dev = [
"pytest>=8.0",
"pytest-cov>=5.0",
"ruff>=0.6",
"pre-commit>=3.7",
# Pinned through uv.lock rather than run as `uv tool run ty`, because CI ratchets
# on the diagnostic count and a floating pre-1.0 checker would move that number on
# its own schedule. Dependabot bumps it; the count moving is then a reviewed event.
"ty>=0.0.72",
# `trimesh` and `scipy` are here because the *test suite imports them*, which is a
# different claim from the `commission` extra's and is why both are listed twice.
# `tests/test_scene_mesh_caption.py` imports `src/syncai_bev3d/scene_mesh.py` and
# `tests/test_measurement_instruments.py` imports `scripts/track_endings.py`; both of
# those start with `from scipy import ndimage`, at module scope, so a missing scipy is
# a collection ERROR and pytest exits non-zero having run nothing at all.
#
# scipy was the half that never got added. `trimesh` reached this group when
# `scene_mesh.py` needed it; when the same file gained its scipy import (`7a83e0e`)
# and `track_endings.py` gained its own (`a697820`), nothing followed, because the
# only environment anyone ran the suite in already had scipy from `--extra bench` or,
# after `f322935`, `--extra commission`. CI syncs `--group dev --extra export` and has
# neither, so its `test` job died at collection on all three matrix rows while every
# local run stayed green -- the exact shape `.github/workflows/ci.yml` records for
# `e2dfb3d`, a red tree reported green because the passing command was not the gate's.
#
# A test-only dependency belongs in the dev group even when an extra also names it.
# Reaching it through `--extra commission` would make the suite's requirements a
# property of an optional feature set, so dropping that extra would silently stop
# running two tests rather than fail.
"trimesh>=5.0.0",
"scipy>=1.15.3",
# Security gates. Both were quoted in this project's notes as passing figures long
# before either was installed anywhere -- "bandit HIGH 0, pip-audit no CVE" came from
# ad-hoc `uvx` runs, so nothing standing produced them and nobody could have noticed
# them going wrong. Pinned through uv.lock for the reason `ty` above is: a floating
# scanner changes what the gate means on its own schedule, and Dependabot bumping it
# makes that a reviewed event.
#
# `pip-audit` has a trap worth naming here, because it looks like it passed either
# way. Run bare under `uvx` it audits *its own ephemeral environment* -- it reported
# "No known vulnerabilities found" over 28 packages that are not this project's. The
# CI job feeds it `uv export`'s requirements instead, which is the locked set a
# contributor actually installs (70 packages on 2026-08-29, no known vulnerabilities).
"bandit>=1.8",
"pip-audit>=2.7",
]
[tool.hatch.version]
path = "src/syncai_hydranet/__init__.py"
[tool.hatch.build.targets.wheel]
packages = ["src/syncai_hydranet", "src/syncai_bev3d"]
# ---------------------------------------------------------------------------
# Ruff replaces black + isort + flake8 in a single pass.
# ---------------------------------------------------------------------------
[tool.ruff]
line-length = 96
# Matches `requires-python` above. It said py310 until 2026-09-04, three days after
# the floor moved to 3.11, so every 3.11-only modernisation UP knows about was
# invisible -- six `datetime.timezone.utc` sites that `datetime.UTC` replaces.
# A target below the floor is a linter told to check an interpreter nobody runs.
target-version = "py311"
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E", "W", # pycodestyle
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"UP", # pyupgrade
"C4", # flake8-comprehensions
"SIM", # flake8-simplify
"RUF", # ruff-specific
"N", # pep8-naming
"PIE", # flake8-pie
"RET", # flake8-return
"PTH", # prefer pathlib over os.path/open
"ARG", # unused arguments, which usually mean a stale signature
# Needs naming explicitly -- ruff keeps it out of a bare "RUF" on purpose, because
# under a partial rule selection a `# noqa` for an unselected rule looks unused when
# it is merely irrelevant. Named here it means: a suppression must suppress
# something. The 41 stale `# noqa`s this appears to find on a `--select RUF100` run
# are that artefact and not real; run it as `--extend-select` to see the true zero.
"RUF100", # a `# noqa` that no longer suppresses anything
]
ignore = [
"E741", # ambiguous names: l/t/r/b are the standard FCOS distance symbols
"B008", # function call in default argument
"N812", # `import torch.nn.functional as F` is universal PyTorch convention
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["E501"]
[tool.ruff.lint.isort]
# BOTH shipped packages: `[tool.hatch.build.targets.wheel]` above lists
# `syncai_bev3d` too, and omitting it here sorted every cross-package import --
# `cli/scene.py` has three -- into the third-party block, which is where a reader
# looks to find out what this project depends on.
known-first-party = ["syncai_hydranet", "syncai_bev3d"]
[tool.ruff.format]
quote-style = "double"
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Type checking. 89% of functions here already carry annotations; until something
# reads them they are documentation that cannot go stale loudly, which is the worst
# kind. `ty` is the same vendor and the same speed class as ruff, so it costs one
# tool rather than a new toolchain.
#
# Wired into CI as a RATCHET, not a pass/fail gate: the `types` job in ci.yml holds src/
# and scripts/ to baseline counts that may fall and never rise. Run it the same way
# locally, and run it through the script rather than by hand:
# ./scripts/ty_ratchet.sh src/
#
# **Do not run `uv run ty check src/` and compare the number to the baseline.** The count
# depends on which interpreter `uv` resolves, because `uv.lock` selects a different numpy
# per Python version and the checker reports what the *stubs* say. The same tree at the
# same commit measured 8 on python 3.10 / numpy 2.2 and 12 on python 3.12 / numpy 2.5.
# `ty_ratchet.sh` pins the interpreter for exactly that reason, and its header carries the
# three-environment table.
#
# The baselines and what is behind them live in that script, not here. An earlier version
# of this block enumerated the diagnostics by cause and named "src/ sits at 17"; the count
# had since moved to 8 and then to 12 without anyone touching the prose, which is this
# project's own recurring failure -- a sentence describing a state with nothing checking
# it. A count belongs where it is enforced.
#
# What survives that deletion, because it is the policy rather than the number: most of
# this debt is not this project's code. It is torch's stubs (`Module.to(memory_format=)`
# has no matching overload, `nn.init` and `list.__init__` reject what torch itself hands
# them), pycocotools, which ships none, and numpy, whose 2.5 stubs reject `arr.min()` on a
# float64 array. None is a live bug, which is why the set is ratcheted rather than
# blocking -- turning the gate red on all of it at once is how a checker gets deleted a
# week later.
# ---------------------------------------------------------------------------
[tool.ty.rules]
# onnx, onnxruntime and onnxsim live in the `export` extra, so a base install cannot
# resolve them and should not be told about it every run.
unresolved-import = "ignore"
# ---------------------------------------------------------------------------
# Pyright is NOT a second CI checker -- ty holds that job, and two gates disagreeing
# about the same tree is how both get ignored. This block exists because Pyright is
# already running whether the repo configures it or not: it is the engine inside
# Pylance, so every VS Code contributor sees its output in the editor. Configured, it
# reads the project venv and the `scripts/` path the way the tests do; unconfigured,
# it cannot resolve numpy or torch and buries the real findings under import noise.
#
# What it still reports on a plain `uv sync --group dev`: onnx, onnxsim, onnxruntime
# and transformers, which live in the `export` and `annotate` extras. Those are left
# speaking up on purpose -- they resolve the moment the extra is installed, so
# `uv sync --all-extras` clears them and a suppression here would just hide which
# extra you are missing.
#
# This block once also covered ROS and TensorRT imports that could not resolve on a dev
# box, tagged per-line with `# pyright: ignore[reportMissingImports]` rather than muted
# here so a genuinely misspelled import in the same file was still caught. Both went with
# the product lines that needed them -- the quadruped in `eca3814`, the Orin scripts on
# 2026-08-28 -- and no such tag is left in the tree.
# ---------------------------------------------------------------------------
[tool.pyright]
venvPath = "."
venv = ".venv"
include = ["src", "scripts", "tests"]
# Six tests import a script by module name the way they do at runtime, after inserting
# scripts/ on sys.path -- `test_measurement_instruments.py`, `test_text_embeddings.py`,
# `test_sam3_sessions.py`, `test_site_split_report.py`, `test_track_review.py` and
# `test_export_argmax.py`. Without this, Pylance reports every one as unresolved.
extraPaths = ["scripts"]
# Matches the Pylance default, so the editor and a CLI run agree on what counts.
typeCheckingMode = "basic"
[tool.ty.environment]
root = ["./src"]
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
addopts = "-ra --strict-markers"
# `error` first: a warning is a failure unless something below says otherwise, and each
# exception names what it is and why it is allowed. The list this replaced was two broad
# ignores -- `ignore::DeprecationWarning:torch.onnx` and `ignore::UserWarning:torch` --
# and the second is the one that mattered: it silenced *every* UserWarning torch raises,
# including the ones that mean this code is calling a deprecated API. A suppression wide
# enough to hide a future bug is not a suppression, it is a blind spot.
#
# Everything the repo's own code raised was fixed rather than listed: two unclosed-file
# leaks in `cli/annotation.py` (one on the path a dataset of RGB masks takes on *every*
# file), an all-NaN `nanmean` in `engine/evaluator.py`, a `requires_grad` tensor cast to
# float in a test, and a `Recorder` left open by five tests -- which is why `Recorder`
# is now a context manager. What is left below is not this repo's to fix.
filterwarnings = [
"error",
# The ONNX exporter, and a decision rather than an oversight. Torch 2.9 made the
# torch.export-based exporter the default and deprecated the TorchScript one;
# `cli/export_onnx.py` still asks for the legacy path.
#
# The reason used to be that the Orin's TensorRT build was validated against what it
# produces, and the Orin stopped being a target on 2026-08-28. What replaced it is not
# weaker: **every engine this project has measured came out of this path.** The 1,494
# f/s that clears the delivery target, the four rows of the resolution trade, the
# parity PASS on each -- all of it is `exports/`, all built from TorchScript-exported
# ONNX. Switching exporters changes the graph's structure, so it invalidates that
# comparison rather than inheriting it.
#
# So this is a re-measure and not a flag flip, and the decision is the user's:
# export both ways from one checkpoint, diff the ONNX ops, re-run `--check-parity`,
# and re-run `scripts/bench_trt.py` before anything is believed. Until then the
# warning stays visible in the run log.
"ignore:.*legacy TorchScript-based ONNX export.*:DeprecationWarning",
"ignore:The feature will be removed.*:DeprecationWarning",
# `models/heads/segmentation.py` branches on a tensor's shape during tracing, so the
# trace fixes the input size -- which is exactly what the exported graph is for: one
# engine per canvas, built for the resolution it will run at. Every shipped export is
# a fixed size and the shipped one is 640x1120 (this comment said "the Orin runs one
# fixed 512x640 input", which named the wrong board and the wrong canvas). A
# dynamic-shape export would need this branch removed and would buy nothing the
# deployment uses.
"ignore::torch.jit.TracerWarning",
]
markers = [
"slow: tests that train or download data (deselect with '-m \"not slow\"')",
]
# Both packages the wheel ships, which it was not. `source` named `syncai_hydranet`
# alone while `[tool.hatch.build.targets.wheel]` above ships `syncai_bev3d` as well, so
# CI's `--cov-fail-under` could not see a line of it.
#
# **No figures here.** This comment carried three -- bev3d at 77%, `plate_calibration.py`
# at 10% and `floorplan.py` at 0%, named as "the two least covered" -- and all three were
# obsolete within two days: the tests for both landed on 2026-08-30 and they now read 97%
# and 95%, while the tree's actual floor is elsewhere entirely. It went unnoticed until
# 2026-09-04, which is precisely what the `[tool.ty.rules]` block ninety lines up warns
# about in its own words: *a count belongs where it is enforced*. The enforced number is
# `--cov-fail-under` in `ci.yml` and `ci-promote.yml`; what is covered and what is not is
# `--cov-report=term-missing`, which prints it per module on every run.
[tool.coverage.run]
source = ["src/syncai_hydranet", "src/syncai_bev3d"]
branch = true