auroraomics 0.1.0.dev0__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.
- auroraomics/__init__.py +74 -0
- auroraomics/_contracts/MANIFEST.json +11 -0
- auroraomics/_contracts/genes/gene-table.2026-09-05.json +19368 -0
- auroraomics/_contracts/public-api/golden-16-tiles.manifest.json +308 -0
- auroraomics/_contracts/public-api-counters.tokens.json +28 -0
- auroraomics/_contracts/public-api-input-kinds.tokens.json +71 -0
- auroraomics/_contracts/public-api.tokens.json +345 -0
- auroraomics/_contracts/qc-thresholds.tokens.json +10 -0
- auroraomics/contracts.py +155 -0
- auroraomics/h5ad.py +398 -0
- auroraomics/pack.py +667 -0
- auroraomics/py.typed +0 -0
- auroraomics/qc.py +288 -0
- auroraomics/subsample.py +140 -0
- auroraomics-0.1.0.dev0.dist-info/METADATA +134 -0
- auroraomics-0.1.0.dev0.dist-info/RECORD +19 -0
- auroraomics-0.1.0.dev0.dist-info/WHEEL +5 -0
- auroraomics-0.1.0.dev0.dist-info/licenses/LICENSE +131 -0
- auroraomics-0.1.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Public-API vocabularies — the SINGLE SOURCE OF TRUTH shared by packages/contracts/src/public-api.ts (validated in public-api.test.ts), the webapp backend (tests/test_public_api_contracts.py validates these bytes; the key and job services load them at import once they exist) and the SDK (vendored at build time, guarded to equal the repo's). Generated from PUBLIC_API_FIXTURES in the module — never hand-edit; run the contracts suite with UPDATE_PUBLIC_API_FIXTURES=1 to regenerate.",
|
|
3
|
+
"_visibility": "public",
|
|
4
|
+
"version": "v1",
|
|
5
|
+
"pathPrefix": "/v1",
|
|
6
|
+
"apiKey": {
|
|
7
|
+
"prefix": "aok_",
|
|
8
|
+
"prefixChars": 8,
|
|
9
|
+
"secretChars": 32,
|
|
10
|
+
"defaultTtlDays": 365,
|
|
11
|
+
"scopes": [
|
|
12
|
+
"submit",
|
|
13
|
+
"read",
|
|
14
|
+
"manage:keys"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"idPrefixes": {
|
|
18
|
+
"job": "job_",
|
|
19
|
+
"upload": "upl_",
|
|
20
|
+
"key": "key_",
|
|
21
|
+
"model": "mdl_"
|
|
22
|
+
},
|
|
23
|
+
"headers": {
|
|
24
|
+
"idempotencyKey": "Idempotency-Key",
|
|
25
|
+
"requestId": "X-Request-Id",
|
|
26
|
+
"problemJson": "application/problem+json",
|
|
27
|
+
"rateLimit": [
|
|
28
|
+
"RateLimit-Limit",
|
|
29
|
+
"RateLimit-Remaining",
|
|
30
|
+
"RateLimit-Reset",
|
|
31
|
+
"Retry-After"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"jobs": {
|
|
35
|
+
"states": [
|
|
36
|
+
"queued",
|
|
37
|
+
"validating",
|
|
38
|
+
"running",
|
|
39
|
+
"succeeded",
|
|
40
|
+
"failed",
|
|
41
|
+
"canceled",
|
|
42
|
+
"expired"
|
|
43
|
+
],
|
|
44
|
+
"terminalStates": [
|
|
45
|
+
"succeeded",
|
|
46
|
+
"failed",
|
|
47
|
+
"canceled",
|
|
48
|
+
"expired"
|
|
49
|
+
],
|
|
50
|
+
"lanes": [
|
|
51
|
+
"gpu",
|
|
52
|
+
"cpu"
|
|
53
|
+
],
|
|
54
|
+
"events": [
|
|
55
|
+
"validated",
|
|
56
|
+
"qc",
|
|
57
|
+
"subsampled",
|
|
58
|
+
"model_loaded",
|
|
59
|
+
"predicted",
|
|
60
|
+
"written"
|
|
61
|
+
],
|
|
62
|
+
"idempotencyScope": "per_key_while_job_lives",
|
|
63
|
+
"idempotencyKey": {
|
|
64
|
+
"header": "Idempotency-Key",
|
|
65
|
+
"scope": "per_key_while_job_lives",
|
|
66
|
+
"min_chars": 8,
|
|
67
|
+
"max_chars": 128,
|
|
68
|
+
"charset": "^[A-Za-z0-9_.:-]+$"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"errors": {
|
|
72
|
+
"validation_failed": {
|
|
73
|
+
"status": 422,
|
|
74
|
+
"anchor": "errors#validation_failed"
|
|
75
|
+
},
|
|
76
|
+
"unsupported_modality": {
|
|
77
|
+
"status": 422,
|
|
78
|
+
"anchor": "errors#unsupported_modality"
|
|
79
|
+
},
|
|
80
|
+
"input_spec_mismatch": {
|
|
81
|
+
"status": 422,
|
|
82
|
+
"anchor": "errors#input_spec_mismatch"
|
|
83
|
+
},
|
|
84
|
+
"calibration_failed": {
|
|
85
|
+
"status": 422,
|
|
86
|
+
"anchor": "errors#calibration_failed"
|
|
87
|
+
},
|
|
88
|
+
"gene_unknown": {
|
|
89
|
+
"status": 422,
|
|
90
|
+
"anchor": "errors#gene_unknown"
|
|
91
|
+
},
|
|
92
|
+
"gene_ambiguous": {
|
|
93
|
+
"status": 422,
|
|
94
|
+
"anchor": "errors#gene_ambiguous"
|
|
95
|
+
},
|
|
96
|
+
"bulk_rna_coverage_too_low": {
|
|
97
|
+
"status": 422,
|
|
98
|
+
"anchor": "errors#bulk_rna_coverage_too_low"
|
|
99
|
+
},
|
|
100
|
+
"checksum_mismatch": {
|
|
101
|
+
"status": 422,
|
|
102
|
+
"anchor": "errors#checksum_mismatch"
|
|
103
|
+
},
|
|
104
|
+
"model_does_not_accept": {
|
|
105
|
+
"status": 422,
|
|
106
|
+
"anchor": "errors#model_does_not_accept"
|
|
107
|
+
},
|
|
108
|
+
"model_not_found": {
|
|
109
|
+
"status": 404,
|
|
110
|
+
"anchor": "errors#model_not_found"
|
|
111
|
+
},
|
|
112
|
+
"not_found": {
|
|
113
|
+
"status": 404,
|
|
114
|
+
"anchor": "errors#not_found"
|
|
115
|
+
},
|
|
116
|
+
"upload_incomplete": {
|
|
117
|
+
"status": 409,
|
|
118
|
+
"anchor": "errors#upload_incomplete"
|
|
119
|
+
},
|
|
120
|
+
"idempotency_conflict": {
|
|
121
|
+
"status": 409,
|
|
122
|
+
"anchor": "errors#idempotency_conflict"
|
|
123
|
+
},
|
|
124
|
+
"result_not_ready": {
|
|
125
|
+
"status": 409,
|
|
126
|
+
"anchor": "errors#result_not_ready"
|
|
127
|
+
},
|
|
128
|
+
"job_not_cancelable": {
|
|
129
|
+
"status": 409,
|
|
130
|
+
"anchor": "errors#job_not_cancelable"
|
|
131
|
+
},
|
|
132
|
+
"result_expired": {
|
|
133
|
+
"status": 410,
|
|
134
|
+
"anchor": "errors#result_expired"
|
|
135
|
+
},
|
|
136
|
+
"payload_too_large": {
|
|
137
|
+
"status": 413,
|
|
138
|
+
"anchor": "errors#payload_too_large"
|
|
139
|
+
},
|
|
140
|
+
"quota_exceeded": {
|
|
141
|
+
"status": 429,
|
|
142
|
+
"anchor": "errors#quota_exceeded"
|
|
143
|
+
},
|
|
144
|
+
"rate_limited": {
|
|
145
|
+
"status": 429,
|
|
146
|
+
"anchor": "errors#rate_limited"
|
|
147
|
+
},
|
|
148
|
+
"unauthorized": {
|
|
149
|
+
"status": 401,
|
|
150
|
+
"anchor": "errors#unauthorized"
|
|
151
|
+
},
|
|
152
|
+
"forbidden": {
|
|
153
|
+
"status": 403,
|
|
154
|
+
"anchor": "errors#forbidden"
|
|
155
|
+
},
|
|
156
|
+
"not_approved": {
|
|
157
|
+
"status": 403,
|
|
158
|
+
"anchor": "errors#not_approved"
|
|
159
|
+
},
|
|
160
|
+
"terms_not_accepted": {
|
|
161
|
+
"status": 403,
|
|
162
|
+
"anchor": "errors#terms_not_accepted"
|
|
163
|
+
},
|
|
164
|
+
"queue_full": {
|
|
165
|
+
"status": 503,
|
|
166
|
+
"anchor": "errors#queue_full"
|
|
167
|
+
},
|
|
168
|
+
"lane_disabled": {
|
|
169
|
+
"status": 503,
|
|
170
|
+
"anchor": "errors#lane_disabled"
|
|
171
|
+
},
|
|
172
|
+
"internal": {
|
|
173
|
+
"status": 500,
|
|
174
|
+
"anchor": "errors#internal"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"containers": {
|
|
178
|
+
"patchArchive": {
|
|
179
|
+
"manifest": "tiles.csv",
|
|
180
|
+
"manifest_columns": [
|
|
181
|
+
"tile_id",
|
|
182
|
+
"file",
|
|
183
|
+
"x",
|
|
184
|
+
"y",
|
|
185
|
+
"width",
|
|
186
|
+
"height",
|
|
187
|
+
"mpp"
|
|
188
|
+
],
|
|
189
|
+
"thumbnail": "thumbnail.jpg",
|
|
190
|
+
"tile_extensions": [
|
|
191
|
+
".png",
|
|
192
|
+
".jpg",
|
|
193
|
+
".jpeg"
|
|
194
|
+
],
|
|
195
|
+
"tile_name_pattern": "^tile_[0-9]{6}\\.(png|jpe?g)$",
|
|
196
|
+
"max_member_bytes": 4194304,
|
|
197
|
+
"geometry_sample": 3
|
|
198
|
+
},
|
|
199
|
+
"embeddingsNpz": {
|
|
200
|
+
"keys": [
|
|
201
|
+
"emb",
|
|
202
|
+
"px_col",
|
|
203
|
+
"px_row",
|
|
204
|
+
"passes",
|
|
205
|
+
"foreground_ratio",
|
|
206
|
+
"hsv_ratio",
|
|
207
|
+
"laplacian_var",
|
|
208
|
+
"calibration"
|
|
209
|
+
],
|
|
210
|
+
"attrs": [
|
|
211
|
+
"encoder",
|
|
212
|
+
"revision",
|
|
213
|
+
"patch_px",
|
|
214
|
+
"pooling",
|
|
215
|
+
"commit"
|
|
216
|
+
],
|
|
217
|
+
"dtypes": [
|
|
218
|
+
"float16",
|
|
219
|
+
"float32"
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
"bulkRna": {
|
|
223
|
+
"units": [
|
|
224
|
+
"counts",
|
|
225
|
+
"tpm"
|
|
226
|
+
],
|
|
227
|
+
"min_coverage_fraction": 0.6,
|
|
228
|
+
"layouts": [
|
|
229
|
+
"long",
|
|
230
|
+
"wide"
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"extensions": {
|
|
234
|
+
"zip": [
|
|
235
|
+
".zip"
|
|
236
|
+
],
|
|
237
|
+
"npz": [
|
|
238
|
+
".npz",
|
|
239
|
+
".npy"
|
|
240
|
+
],
|
|
241
|
+
"table": [
|
|
242
|
+
".csv",
|
|
243
|
+
".tsv",
|
|
244
|
+
".txt"
|
|
245
|
+
],
|
|
246
|
+
"enum": []
|
|
247
|
+
},
|
|
248
|
+
"magic": {
|
|
249
|
+
".zip": [
|
|
250
|
+
"504b0304"
|
|
251
|
+
],
|
|
252
|
+
".npz": [
|
|
253
|
+
"504b0304"
|
|
254
|
+
],
|
|
255
|
+
".npy": [
|
|
256
|
+
"934e554d5059"
|
|
257
|
+
],
|
|
258
|
+
".csv": [],
|
|
259
|
+
".tsv": [],
|
|
260
|
+
".txt": []
|
|
261
|
+
},
|
|
262
|
+
"tableDelimiters": [
|
|
263
|
+
",",
|
|
264
|
+
"\t"
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"uploads": {
|
|
268
|
+
"chunk_size_bytes": 8388608,
|
|
269
|
+
"chunk_min_bytes": 524288,
|
|
270
|
+
"chunk_max_bytes": 94371840,
|
|
271
|
+
"chunk_digest_header": "Content-SHA256",
|
|
272
|
+
"offset_header": "X-Upload-Offset",
|
|
273
|
+
"complete_header": "X-Upload-Complete",
|
|
274
|
+
"tree_hash": {
|
|
275
|
+
"algorithm": "sha256",
|
|
276
|
+
"encoding": "lowercase-hex",
|
|
277
|
+
"leaf": "sha256(chunk bytes), the 32 RAW digest bytes, one leaf per Content-Range chunk in offset order",
|
|
278
|
+
"root": "sha256(leaf_0 || leaf_1 || ... || leaf_n-1)",
|
|
279
|
+
"single_chunk": "a whole-file PUT is ONE chunk, so the root is sha256(sha256(file)) — never sha256(file)"
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"result": {
|
|
283
|
+
"format": "h5ad",
|
|
284
|
+
"var_index": "ensembl_gene_id",
|
|
285
|
+
"var_columns": [
|
|
286
|
+
"feature_name",
|
|
287
|
+
"hgnc_id",
|
|
288
|
+
"biotype",
|
|
289
|
+
"measured_in_training",
|
|
290
|
+
"n_train_datasets",
|
|
291
|
+
"model_token"
|
|
292
|
+
],
|
|
293
|
+
"layers": [
|
|
294
|
+
"bulk_calibrated",
|
|
295
|
+
"image_only"
|
|
296
|
+
],
|
|
297
|
+
"uns_keys": [
|
|
298
|
+
"model",
|
|
299
|
+
"input_spec",
|
|
300
|
+
"job"
|
|
301
|
+
]
|
|
302
|
+
},
|
|
303
|
+
"rateBuckets": {
|
|
304
|
+
"read": {
|
|
305
|
+
"per_minute": 600
|
|
306
|
+
},
|
|
307
|
+
"write": {
|
|
308
|
+
"per_minute": 60
|
|
309
|
+
},
|
|
310
|
+
"submit": {
|
|
311
|
+
"per_minute": 10
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"retention": {
|
|
315
|
+
"uploads_days_after_completion": 7,
|
|
316
|
+
"results_days": 90,
|
|
317
|
+
"embeddings_retain_default": false
|
|
318
|
+
},
|
|
319
|
+
"modelCards": {
|
|
320
|
+
"schemaVersion": 2,
|
|
321
|
+
"visibilities": [
|
|
322
|
+
"public",
|
|
323
|
+
"internal"
|
|
324
|
+
],
|
|
325
|
+
"hostedRuntimes": [
|
|
326
|
+
"gpu",
|
|
327
|
+
"cpu"
|
|
328
|
+
],
|
|
329
|
+
"encoderGates": [
|
|
330
|
+
"none",
|
|
331
|
+
"auto",
|
|
332
|
+
"manual"
|
|
333
|
+
],
|
|
334
|
+
"poolings": [
|
|
335
|
+
"cls",
|
|
336
|
+
"mean_patch_tokens",
|
|
337
|
+
"cls_plus_mean",
|
|
338
|
+
"token_grid"
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
"fixtureVisibilities": [
|
|
342
|
+
"public",
|
|
343
|
+
"internal"
|
|
344
|
+
]
|
|
345
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Patch-QC threshold VALUES — the SINGLE SOURCE OF TRUTH for the three floors a tile must reach to be fed to a model, and the PRODUCTION numbers both pipelines run. patch-qc.vectors.json single-sources the per-pixel tissue RULE; these are the floors that rule is compared against, and they used to name four different things: 55/30/20 in both config.yaml files, 50/20/50 in the Python predicate defaults, and 20 for blur in filter_patches' own signature. Retuning one copy silently changes which tiles a model is fed relative to which tiles a reviewer approved. A model may override them on its card (input_spec.qc); absent that these are what everything uses. Loaded by the SDK (vendored) and validated against both pipeline configs by qc-thresholds.test.ts, which greps them because a container COPYs only its own tree. foregroundWhiteLevel is the OTHER half of the foreground rule: the grey level at or below which a pixel counts as tissue, which foreground_ratio is then measured against. It sits beside `thresholds` rather than inside it because that object is 1:1 with the QC_THRESHOLDS block in both config.yaml files and with the QcThresholds interface. It was hand-typed in three places — the SDK's qc.WHITE_LEVEL and a bare 210 in each pipeline's patch_qc.py — and was the last unpinned half of the QC rule: retune one and the SDK filters tiles the service would keep, which is exactly what the SDK exists not to do.",
|
|
3
|
+
"_visibility": "public",
|
|
4
|
+
"thresholds": {
|
|
5
|
+
"foreground_ratio": 55,
|
|
6
|
+
"hsv_tissue_ratio": 30,
|
|
7
|
+
"blur_laplacian_var": 20
|
|
8
|
+
},
|
|
9
|
+
"foregroundWhiteLevel": 210
|
|
10
|
+
}
|
auroraomics/contracts.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""The shared contract values, as data.
|
|
2
|
+
|
|
3
|
+
Container layouts, input-kind caps and the result layout are agreed between
|
|
4
|
+
this package and the service that runs the same models. Re-typing one of those
|
|
5
|
+
numbers here would create a second source for it, and the copy that drifts is
|
|
6
|
+
always the one nobody is looking at — so the agreed values ship *as the JSON
|
|
7
|
+
they are defined in*, vendored into the wheel at ``_contracts/``.
|
|
8
|
+
|
|
9
|
+
The vendored copies are byte-for-byte what the definition side holds, and each
|
|
10
|
+
one carries its sha256 in ``_contracts/MANIFEST.json``. Only documents marked
|
|
11
|
+
public are vendored; nothing else is readable from here, by construction.
|
|
12
|
+
|
|
13
|
+
>>> from auroraomics import contracts
|
|
14
|
+
>>> contracts.patch_archive()["manifest"]
|
|
15
|
+
'tiles.csv'
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
from functools import lru_cache
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
_DIR = Path(__file__).resolve().parent / "_contracts"
|
|
27
|
+
_MANIFEST = "MANIFEST.json"
|
|
28
|
+
|
|
29
|
+
#: The documents this package reads by name. Spelled here so a missing or
|
|
30
|
+
#: renamed vendored file is an error at the call site rather than a KeyError
|
|
31
|
+
#: deep inside a validator.
|
|
32
|
+
PUBLIC_API = "public-api.tokens.json"
|
|
33
|
+
#: The service's patch-QC floors. A model may override them on its card; absent
|
|
34
|
+
#: that, these are what the service runs, and therefore what this package filters at.
|
|
35
|
+
QC_THRESHOLDS = "qc-thresholds.tokens.json"
|
|
36
|
+
INPUT_KINDS = "public-api-input-kinds.tokens.json"
|
|
37
|
+
COUNTERS = "public-api-counters.tokens.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ContractError(RuntimeError):
|
|
41
|
+
"""A vendored contract document is missing, unreadable or not itself."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@lru_cache(maxsize=1)
|
|
45
|
+
def manifest() -> dict[str, Any]:
|
|
46
|
+
"""The vendored index: every document's relative name and its sha256."""
|
|
47
|
+
path = _DIR / _MANIFEST
|
|
48
|
+
try:
|
|
49
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
50
|
+
except FileNotFoundError as exc: # pragma: no cover - a broken install
|
|
51
|
+
raise ContractError(
|
|
52
|
+
f"{_MANIFEST} is missing from this installation, so no contract "
|
|
53
|
+
"value can be trusted; reinstall the package."
|
|
54
|
+
) from exc
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def names() -> tuple[str, ...]:
|
|
58
|
+
"""Every vendored document name, sorted."""
|
|
59
|
+
return tuple(sorted(manifest()["files"]))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def path_of(name: str) -> Path:
|
|
63
|
+
"""The absolute path of one vendored document.
|
|
64
|
+
|
|
65
|
+
``name`` must be one the index lists. Resolving an arbitrary string here
|
|
66
|
+
would let a caller read any file the process can reach through a
|
|
67
|
+
``../..`` name, so the index is the allow-list rather than a hint.
|
|
68
|
+
"""
|
|
69
|
+
if name not in manifest()["files"]:
|
|
70
|
+
raise ContractError(
|
|
71
|
+
f"{name!r} is not a vendored contract document; the ones present "
|
|
72
|
+
f"are {', '.join(names())}"
|
|
73
|
+
)
|
|
74
|
+
return _DIR / name
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@lru_cache(maxsize=None)
|
|
78
|
+
def load(name: str) -> dict[str, Any]:
|
|
79
|
+
"""One vendored document, parsed. Cached: these are read-only constants."""
|
|
80
|
+
return json.loads(path_of(name).read_text(encoding="utf-8"))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def verify() -> None:
|
|
84
|
+
"""Re-hash every vendored document and compare with the index.
|
|
85
|
+
|
|
86
|
+
Raises :class:`ContractError` on the first mismatch. Not called on import —
|
|
87
|
+
hashing on every process start would tax every caller for a check that only
|
|
88
|
+
a damaged install can fail — but cheap enough to call from a diagnostic.
|
|
89
|
+
"""
|
|
90
|
+
for name, expected in sorted(manifest()["files"].items()):
|
|
91
|
+
actual = hashlib.sha256(path_of(name).read_bytes()).hexdigest()
|
|
92
|
+
if actual != expected:
|
|
93
|
+
raise ContractError(
|
|
94
|
+
f"vendored contract {name} does not match its recorded "
|
|
95
|
+
f"sha256 ({actual} != {expected}) — the installation is damaged"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── the values this package's own modules read ──────────────────────────────
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def qc_thresholds() -> dict[str, float]:
|
|
103
|
+
"""The three patch-QC floors the service runs.
|
|
104
|
+
|
|
105
|
+
Read from the shipped contract rather than written here: a client that filters
|
|
106
|
+
tiles differently from the server it sends them to would drop tiles the
|
|
107
|
+
service would have kept, or keep tiles it will discard, and every number
|
|
108
|
+
downstream would still look plausible.
|
|
109
|
+
"""
|
|
110
|
+
return {k: float(v) for k, v in load(QC_THRESHOLDS)["thresholds"].items()}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def foreground_white_level() -> int:
|
|
114
|
+
"""The grey level at or below which a pixel counts as foreground.
|
|
115
|
+
|
|
116
|
+
Read from the contract for the same reason :func:`qc_thresholds` is: this is
|
|
117
|
+
the other half of the foreground rule, and a client that cuts foreground at
|
|
118
|
+
a different grey level than the service does filters a different set of
|
|
119
|
+
tiles — while every number downstream still looks plausible.
|
|
120
|
+
"""
|
|
121
|
+
return int(load(QC_THRESHOLDS)["foregroundWhiteLevel"])
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def patch_archive() -> dict[str, Any]:
|
|
125
|
+
"""The ``patches`` container layout: manifest name, columns, tile names."""
|
|
126
|
+
return load(PUBLIC_API)["containers"]["patchArchive"]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def result_layout() -> dict[str, Any]:
|
|
130
|
+
"""The result file's agreed layout: format, ``var`` index, layer names."""
|
|
131
|
+
return load(PUBLIC_API)["result"]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def input_kind(kind: str) -> dict[str, Any]:
|
|
135
|
+
"""One input kind's registry row (container, cost unit, caps)."""
|
|
136
|
+
kinds = load(INPUT_KINDS)["kinds"]
|
|
137
|
+
if kind not in kinds:
|
|
138
|
+
raise ContractError(
|
|
139
|
+
f"{kind!r} is not an input kind; the registry declares "
|
|
140
|
+
f"{', '.join(sorted(kinds))}"
|
|
141
|
+
)
|
|
142
|
+
return kinds[kind]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def max_bytes(kind: str) -> int:
|
|
146
|
+
"""The largest an input of ``kind`` may be, uncompressed.
|
|
147
|
+
|
|
148
|
+
A cap is what makes it safe to look at an archive at all: the sizes a zip
|
|
149
|
+
declares are written by whoever produced it, so they are checked against
|
|
150
|
+
this number *before* anything is decompressed.
|
|
151
|
+
"""
|
|
152
|
+
value = input_kind(kind)["max_bytes"]
|
|
153
|
+
if not isinstance(value, int) or value <= 0:
|
|
154
|
+
raise ContractError(f"input kind {kind!r} declares no byte cap")
|
|
155
|
+
return value
|