zorya-classifier 0.1.0 → 0.2.1
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.
- package/NOTICE.md +2 -0
- package/README.md +37 -9
- package/dist/app/cli-arguments.d.ts +40 -0
- package/dist/app/cli-arguments.js +67 -0
- package/dist/app/create-classifier.d.ts +24 -0
- package/dist/app/create-classifier.js +226 -14
- package/dist/app/extraction-files.d.ts +9 -0
- package/dist/app/extraction-files.js +28 -0
- package/dist/cli.js +58 -14
- package/dist/entities/boundary-view.d.ts +29 -0
- package/dist/entities/boundary-view.js +1 -0
- package/dist/entities/contracts.d.ts +81 -1
- package/dist/entities/contracts.js +7 -0
- package/dist/entities/cutout-raster.d.ts +7 -0
- package/dist/entities/cutout-raster.js +1 -0
- package/dist/entities/object-proposals.d.ts +56 -0
- package/dist/entities/object-proposals.js +8 -0
- package/dist/entities/representation.d.ts +225 -0
- package/dist/entities/representation.js +33 -0
- package/dist/entities/segmentation-metadata.d.ts +11 -0
- package/dist/entities/segmentation-metadata.js +1 -0
- package/dist/entities/segmentation-verification.d.ts +31 -0
- package/dist/entities/segmentation-verification.js +1 -0
- package/dist/entities/segmentation.d.ts +164 -0
- package/dist/entities/segmentation.js +5 -0
- package/dist/entities/whole-object.d.ts +132 -0
- package/dist/entities/whole-object.js +7 -0
- package/dist/features/choice.d.ts +1 -1
- package/dist/features/choice.js +2 -2
- package/dist/features/segmentation/box-check.d.ts +10 -0
- package/dist/features/segmentation/box-check.js +299 -0
- package/dist/features/segmentation/mask.d.ts +6 -0
- package/dist/features/segmentation/mask.js +89 -0
- package/dist/features/segmentation/packed-mask.d.ts +5 -0
- package/dist/features/segmentation/packed-mask.js +53 -0
- package/dist/index.d.ts +6 -1
- package/dist/infrastructure/boundary-view.d.ts +2 -0
- package/dist/infrastructure/boundary-view.js +263 -0
- package/dist/infrastructure/openrouter-common.d.ts +8 -8
- package/dist/infrastructure/openrouter-vision.d.ts +3 -0
- package/dist/infrastructure/openrouter-vision.js +5 -0
- package/dist/infrastructure/raster.d.ts +10 -1
- package/dist/infrastructure/raster.js +99 -4
- package/dist/infrastructure/segmentation/box-presentation.d.ts +2 -0
- package/dist/infrastructure/segmentation/box-presentation.js +136 -0
- package/dist/infrastructure/segmentation/cutout-raster.d.ts +3 -0
- package/dist/infrastructure/segmentation/cutout-raster.js +79 -0
- package/dist/infrastructure/segmentation/grabcut/index.d.ts +17 -0
- package/dist/infrastructure/segmentation/grabcut/index.js +121 -0
- package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +47 -0
- package/dist/infrastructure/segmentation/grabcut/worker-inference.js +260 -0
- package/dist/infrastructure/segmentation/index.d.ts +2 -0
- package/dist/infrastructure/segmentation/index.js +2 -0
- package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +66 -0
- package/dist/infrastructure/segmentation/mobile-sam/index.js +478 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +149 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +845 -0
- package/dist/infrastructure/segmentation/native-child.d.ts +23 -0
- package/dist/infrastructure/segmentation/native-child.js +297 -0
- package/dist/infrastructure/segmentation/native-protocol.d.ts +923 -0
- package/dist/infrastructure/segmentation/native-protocol.js +242 -0
- package/dist/infrastructure/segmentation/native-worker.d.ts +1 -0
- package/dist/infrastructure/segmentation/native-worker.js +93 -0
- package/dist/infrastructure/segmentation/probe-backend.d.ts +1 -0
- package/dist/infrastructure/segmentation/probe-backend.js +31 -0
- package/dist/infrastructure/segmentation/proposal-presentation.d.ts +3 -0
- package/dist/infrastructure/segmentation/proposal-presentation.js +371 -0
- package/dist/infrastructure/segmentation/verify-asset.d.ts +4 -0
- package/dist/infrastructure/segmentation/verify-asset.js +32 -0
- package/dist/infrastructure/text-representation.d.ts +23 -0
- package/dist/infrastructure/text-representation.js +226 -0
- package/dist/infrastructure/vision-chat.d.ts +1 -1
- package/dist/infrastructure/vision-chat.js +13 -4
- package/dist/openai-compatible.d.ts +3 -0
- package/dist/openai-compatible.js +4 -0
- package/dist/openrouter.d.ts +2 -2
- package/dist/openrouter.js +1 -1
- package/dist/processes/boundary-state.d.ts +12 -0
- package/dist/processes/boundary-state.js +196 -0
- package/dist/processes/classify.d.ts +2 -1
- package/dist/processes/classify.js +21 -3
- package/dist/processes/extract-from-region.d.ts +19 -0
- package/dist/processes/extract-from-region.js +415 -0
- package/dist/processes/extract.d.ts +25 -0
- package/dist/processes/extract.js +223 -0
- package/dist/processes/locate.d.ts +2 -1
- package/dist/processes/locate.js +84 -11
- package/dist/processes/proposal-location.d.ts +19 -0
- package/dist/processes/proposal-location.js +396 -0
- package/dist/processes/whole-object.d.ts +7 -0
- package/dist/processes/whole-object.js +272 -0
- package/dist/segmentation.d.ts +10 -0
- package/dist/segmentation.js +4 -0
- package/dist/shared/input-budget.d.ts +7 -0
- package/dist/shared/input-budget.js +21 -0
- package/docs/0.2.0-plan.md +190 -0
- package/docs/README.ru.md +43 -14
- package/docs/acceptance.md +116 -3
- package/docs/api.md +12 -6
- package/docs/architecture.md +22 -7
- package/docs/benchmarks.md +10 -0
- package/docs/categorical-presence-port.md +26 -0
- package/docs/evidence/v02/baseline.json +1043 -0
- package/docs/evidence/v02/boundary-variants.md +17 -0
- package/docs/evidence/v02/box-confirmation-dev40/README.md +15 -0
- package/docs/evidence/v02/box-confirmation-dev40/component-refinement.json +246 -0
- package/docs/evidence/v02/box-confirmation-dev40/evaluation.json +1663 -0
- package/docs/evidence/v02/box-confirmation-dev40/provenance.json +130 -0
- package/docs/evidence/v02/box-confirmation-dev40/report.json +127 -0
- package/docs/evidence/v02/box-confirmation-dev40-freeze-20261020-v1.json +230 -0
- package/docs/evidence/v02/box-confirmation-method-selection-20261020-v1.json +85 -0
- package/docs/evidence/v02/box-heldout-cancelled-before-dispatch-20261021-v1.json +32 -0
- package/docs/evidence/v02/box-heldout-combined-seal-20261021-v1.json +89 -0
- package/docs/evidence/v02/box-heldout-evaluator-truth-seal-20261021-v1.json +61 -0
- package/docs/evidence/v02/box-heldout-exclusion-audit-20261020-v1.json +81 -0
- package/docs/evidence/v02/box-heldout-map-seal-20261021-v1.json +48 -0
- package/docs/evidence/v02/box-heldout-photo-seal-20261020-v1.json +62 -0
- package/docs/evidence/v02/box-heldout-run-freeze-20261021-v1.json +61 -0
- package/docs/evidence/v02/box-heldout-source-review-20261020-v1.json +134 -0
- package/docs/evidence/v02/box-heldout-ui-seal-20261020-v1.json +44 -0
- package/docs/evidence/v02/calculator-proof-root-review-20261020-v1.json +63 -0
- package/docs/evidence/v02/categorical-dev40-freeze.json +94 -0
- package/docs/evidence/v02/categorical-dev40-preserve.json +460 -0
- package/docs/evidence/v02/categorical-dev40-target-only.json +460 -0
- package/docs/evidence/v02/categorical-heldout-admission-20261020-v3.json +215 -0
- package/docs/evidence/v02/categorical-heldout-admission-20261020-v4.json +270 -0
- package/docs/evidence/v02/categorical-heldout-dispatch-authorization-20261020-v4.json +79 -0
- package/docs/evidence/v02/categorical-heldout-predispatch-20261020-v3.json +57 -0
- package/docs/evidence/v02/categorical-heldout-producer-source-map-v4.json +262 -0
- package/docs/evidence/v02/categorical-heldout-producer-source-map.json +263 -0
- package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v3.json +35 -0
- package/docs/evidence/v02/categorical-heldout-run-freeze-20261020-v4.json +35 -0
- package/docs/evidence/v02/categorical-heldout-v4/candidate-evaluation.json +2812 -0
- package/docs/evidence/v02/categorical-heldout-v4/candidate-physical-receipts.json +15261 -0
- package/docs/evidence/v02/categorical-heldout-v4/candidate-provenance.json +131 -0
- package/docs/evidence/v02/categorical-heldout-v4/control-evaluation.json +855 -0
- package/docs/evidence/v02/categorical-heldout-v4/control-physical-receipts.json +4402 -0
- package/docs/evidence/v02/categorical-heldout-v4/control-provenance.json +31 -0
- package/docs/evidence/v02/categorical-heldout-v4/mask-normalized-exploratory.json +682 -0
- package/docs/evidence/v02/categorical-heldout-v4/paired-qualification.json +21 -0
- package/docs/evidence/v02/categorical-heldout-v4/report.json +258 -0
- package/docs/evidence/v02/categorical-presence-dev6.json +144 -0
- package/docs/evidence/v02/categorical-presence-dev6.md +18 -0
- package/docs/evidence/v02/categorical-vision-method-selection.json +90 -0
- package/docs/evidence/v02/checked-region-component-v1/README.md +7 -0
- package/docs/evidence/v02/checked-region-component-v1/evidence.json +311 -0
- package/docs/evidence/v02/checked-region-resource-interrupted-2b0d09f.json +102 -0
- package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-receipt.json +167 -0
- package/docs/evidence/v02/checked-region-segmentation-resource-4df544c-root-review.json +58 -0
- package/docs/evidence/v02/checked-region-segmentation-resource-987ac32-receipt.json +158 -0
- package/docs/evidence/v02/ci-012b935.json +17 -0
- package/docs/evidence/v02/ci-0c365cb.json +372 -0
- package/docs/evidence/v02/ci-185bde8.json +32 -0
- package/docs/evidence/v02/ci-24f849f.json +95 -0
- package/docs/evidence/v02/ci-25c19cd.json +375 -0
- package/docs/evidence/v02/ci-2922caf.json +375 -0
- package/docs/evidence/v02/ci-29357d7.json +29 -0
- package/docs/evidence/v02/ci-32aa90d.json +24 -0
- package/docs/evidence/v02/ci-35e3064.json +376 -0
- package/docs/evidence/v02/ci-4331b1b.json +384 -0
- package/docs/evidence/v02/ci-43b7426.json +376 -0
- package/docs/evidence/v02/ci-450439c.json +373 -0
- package/docs/evidence/v02/ci-46283fe.json +29 -0
- package/docs/evidence/v02/ci-4c9c09f.json +34 -0
- package/docs/evidence/v02/ci-5579646.json +27 -0
- package/docs/evidence/v02/ci-6f9cc15.json +127 -0
- package/docs/evidence/v02/ci-7c1051f.json +27 -0
- package/docs/evidence/v02/ci-9837888.json +34 -0
- package/docs/evidence/v02/ci-987ac32.json +29 -0
- package/docs/evidence/v02/ci-9d00fe5.json +25 -0
- package/docs/evidence/v02/ci-9f85acc.json +127 -0
- package/docs/evidence/v02/ci-be275a6.json +201 -0
- package/docs/evidence/v02/ci-c107ddf.json +372 -0
- package/docs/evidence/v02/ci-c35f67d.json +127 -0
- package/docs/evidence/v02/ci-c5eddd8.json +32 -0
- package/docs/evidence/v02/ci-d2042e0.json +372 -0
- package/docs/evidence/v02/ci-d684259.json +375 -0
- package/docs/evidence/v02/ci-e0c190b.json +127 -0
- package/docs/evidence/v02/ci-e551253.json +372 -0
- package/docs/evidence/v02/ci-f9d4633.json +29 -0
- package/docs/evidence/v02/dev1-proposal-evaluation.md +13 -0
- package/docs/evidence/v02/dev2-mobile-sam-proposal-oracle.md +21 -0
- package/docs/evidence/v02/dev2-proposal-evaluation.md +21 -0
- package/docs/evidence/v02/fresh-development.md +16 -0
- package/docs/evidence/v02/fresh-native-numeric/artifact-hashes.json +9 -0
- package/docs/evidence/v02/fresh-native-numeric/evaluation-root-v1.json +568 -0
- package/docs/evidence/v02/fresh-native-numeric/run-manifest.json +342 -0
- package/docs/evidence/v02/fresh-native-numeric/summary.json +5654 -0
- package/docs/evidence/v02/fresh-native-outline-axis/artifact-hashes.json +9 -0
- package/docs/evidence/v02/fresh-native-outline-axis/evaluation-root-v1.json +568 -0
- package/docs/evidence/v02/fresh-native-outline-axis/run-manifest.json +378 -0
- package/docs/evidence/v02/fresh-native-outline-axis/summary.json +5605 -0
- package/docs/evidence/v02/fresh-text/README.md +23 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/ascii-contours-v1/original-summary.json +4416 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/ascii-luma-v1/original-summary.json +4647 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/classify-followup-summary.json +846 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/ascii-svg-layered-v1/original-summary.json +1789 -0
- package/docs/evidence/v02/fresh-text/no-image-control/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/no-image-control/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/no-image-control/original-summary.json +3953 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/palette-grid-v1/original-summary.json +3974 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/classify-followup-summary.json +1646 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/pixel-rle-v1/original-summary.json +4945 -0
- package/docs/evidence/v02/fresh-text/report.json +284 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-run-manifest.json +405 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/classify-followup-summary.json +846 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/evaluation.json +568 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/original-run-manifest.json +404 -0
- package/docs/evidence/v02/fresh-text/svg-contours-v1/original-summary.json +2189 -0
- package/docs/evidence/v02/heldout-evaluation-20261020.md +37 -0
- package/docs/evidence/v02/heldout-input-derivation.md +18 -0
- package/docs/evidence/v02/integration-0c365cb.json +90 -0
- package/docs/evidence/v02/integration-25c19cd.json +52 -0
- package/docs/evidence/v02/integration-35e3064.json +41 -0
- package/docs/evidence/v02/integration-4331b1b.json +42 -0
- package/docs/evidence/v02/integration-4c9c09f.json +46 -0
- package/docs/evidence/v02/integration-8010aad.json +48 -0
- package/docs/evidence/v02/integration-c1aa640.json +41 -0
- package/docs/evidence/v02/integration-eea1790.json +46 -0
- package/docs/evidence/v02/integration-fa82e65.json +42 -0
- package/docs/evidence/v02/jpeg-transport-offline.json +268 -0
- package/docs/evidence/v02/jpeg-transport-offline.md +7 -0
- package/docs/evidence/v02/local-archive-cleanup.json +59 -0
- package/docs/evidence/v02/native-base64-after.json +30 -0
- package/docs/evidence/v02/native-base64-before.json +32 -0
- package/docs/evidence/v02/native-checked-region-windows-46283fe.json +248 -0
- package/docs/evidence/v02/native-checked-region-windows-987ac32.json +141 -0
- package/docs/evidence/v02/native-grabcut-windows-4c494a3-run2.json +148 -0
- package/docs/evidence/v02/native-grabcut-windows-4c494a3-run3.json +148 -0
- package/docs/evidence/v02/native-grabcut-windows-4c494a3.json +56 -0
- package/docs/evidence/v02/native-proposal-windows-0c365cb.json +148 -0
- package/docs/evidence/v02/native-proposal-windows-450439c.json +156 -0
- package/docs/evidence/v02/native-proposal-windows-4c9c09f-retry1.json +147 -0
- package/docs/evidence/v02/native-proposal-windows-4c9c09f.json +52 -0
- package/docs/evidence/v02/native-region-windows-efc9318.json +163 -0
- package/docs/evidence/v02/native-windows-eea1790-baseline.json +198 -0
- package/docs/evidence/v02/native-windows-eea1790.json +228 -0
- package/docs/evidence/v02/ordinary-luna-axis/run-manifest.json +99 -0
- package/docs/evidence/v02/ordinary-luna-axis/summary.json +811 -0
- package/docs/evidence/v02/owned-temp-cleanup-20261020-v1.json +28 -0
- package/docs/evidence/v02/presentation-paired-20261020.json +1438 -0
- package/docs/evidence/v02/proposal-resource-soak-261dc23.json +201 -0
- package/docs/evidence/v02/proposal-resource-soak-450439c-receipt.md +13 -0
- package/docs/evidence/v02/reconstructed-c03-box.json +41 -0
- package/docs/evidence/v02/region-seeded-dev-v1/README.md +17 -0
- package/docs/evidence/v02/region-seeded-dev-v1/evaluation.json +260 -0
- package/docs/evidence/v02/region-seeded-dev-v2/README.md +15 -0
- package/docs/evidence/v02/region-seeded-dev-v2/evaluation.json +232 -0
- package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/README.md +9 -0
- package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/control-identity-failure.json +22 -0
- package/docs/evidence/v02/region-seeded-dev-v3-candidate-only/evaluation.json +160 -0
- package/docs/evidence/v02/region-seeded-dev-v3-control-r2/README.md +9 -0
- package/docs/evidence/v02/region-seeded-dev-v3-control-r2/attempt-accounting.json +55 -0
- package/docs/evidence/v02/region-seeded-dev-v3-control-r2/evaluation.json +136 -0
- package/docs/evidence/v02/region-seeded-development-control-r2-freeze-20261021.json +132 -0
- package/docs/evidence/v02/region-seeded-development-freeze-20261021-v1.json +167 -0
- package/docs/evidence/v02/region-seeded-development-freeze-20261021-v2.json +186 -0
- package/docs/evidence/v02/region-seeded-development-freeze-20261021-v3.json +205 -0
- package/docs/evidence/v02/region-seeded-heldout-cancelled-before-dispatch-20261021-v2.json +20 -0
- package/docs/evidence/v02/region-seeded-heldout-dispatch-authorization-20261021-v3.json +47 -0
- package/docs/evidence/v02/region-seeded-heldout-readiness-audit-20261021-v3.json +125 -0
- package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v2.json +234 -0
- package/docs/evidence/v02/region-seeded-heldout-run-freeze-20261021-v3.json +357 -0
- package/docs/evidence/v02/region-seeded-heldout-v3/README.md +19 -0
- package/docs/evidence/v02/region-seeded-heldout-v3/evaluation-summary.json +445 -0
- package/docs/evidence/v02/region-seeded-method-selection-20261021-v2.json +66 -0
- package/docs/evidence/v02/region-seeded-method-selection-20261021-v3.json +120 -0
- package/docs/evidence/v02/region-segmentation-resource-0ba4c87-receipt.json +156 -0
- package/docs/evidence/v02/resource-soak-linux-8010aad.json +125 -0
- package/docs/evidence/v02/screen-v2-terminal-evaluation.md +22 -0
- package/docs/evidence/v02/seed-outline/run-manifest.json +59 -0
- package/docs/evidence/v02/seed-outline/summary.json +487 -0
- package/docs/evidence/v02/seed-unbounded/run-manifest.json +80 -0
- package/docs/evidence/v02/seed-unbounded/summary.json +730 -0
- package/docs/evidence/v02/segmentation/README.md +21 -0
- package/docs/evidence/v02/segmentation/component-comparison-summary.json +240 -0
- package/docs/evidence/v02/segmentation/provenance/MOBILE-SAM-LICENSE.txt +201 -0
- package/docs/evidence/v02/segmentation/provenance/OPENCV-LICENSE.txt +202 -0
- package/docs/evidence/v02/segmentation/provenance/mobile-sam-download-receipt.json +29 -0
- package/docs/evidence/v02/segmentation/provenance/mobile-sam-graph-audit.json +214 -0
- package/docs/evidence/v02/segmentation/provenance/mobile-sam-model-metadata.json +166 -0
- package/docs/evidence/v02/split-choice-noul-dev6.json +72 -0
- package/docs/evidence/v02/split-choice-noul-dev6.md +11 -0
- package/docs/evidence/v02/supported-vision-screen-20261020.md +65 -0
- package/docs/evidence/v02/wesnoth-1.16.11-minimap-projection-source-audit.md +56 -0
- package/docs/evidence/v02/wesnoth-proof-root-review-20261020-v1.json +76 -0
- package/docs/operations.md +8 -2
- package/docs/plan.md +2 -0
- package/docs/publishing.md +16 -1
- package/docs/research/text-transport-audit.md +66 -0
- package/docs/v02-usage.md +164 -0
- package/docs/vision-results.md +31 -2
- package/package.json +30 -5
package/NOTICE.md
CHANGED
|
@@ -6,6 +6,8 @@ Models, hosted APIs, Jev and OpenRouter are third-party services; each selected
|
|
|
6
6
|
|
|
7
7
|
Direct runtime dependencies: Zod 3.25.76 (MIT), Sharp 0.34.5 (Apache-2.0). Sharp's separately installed native packages contain libvips and other libraries with their own notices, including LGPL terms; preserve the notices shipped with those packages when redistributing them. npm resolves and installs them separately; this archive does not bundle their binaries.
|
|
8
8
|
|
|
9
|
+
The 0.2 optional MobileSAM adapter requires separately installed ONNX Runtime Node 1.23.2 (MIT) and separately obtained pinned Acly/MobileSAM ONNX assets. The archived MobileSAM model license is Apache-2.0. The explicit GrabCut adapter uses the separately obtained OpenCV 4.13.0 JS asset under its archived Apache-2.0 license. These assets and native binaries are not bundled or downloaded on import. See docs/evidence/v02/segmentation/provenance for license texts, source revision and artifact hashes. The library's MIT license does not replace these third-party licenses.
|
|
10
|
+
|
|
9
11
|
Optional benchmark photographs are CC BY 2.0, Open Images annotations CC BY 4.0, and newly authored text fixtures MIT. Preserve benchmarks/corpus/ATTRIBUTION.md and each asset's attribution. Photographs and bulk upstream annotation CSVs are downloaded separately and excluded from the npm archive and source release archive.
|
|
10
12
|
|
|
11
13
|
The public result JSON under docs/evidence also includes derived Open Images bounding-box annotations. These annotations remain CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/); source: Open Images V7, https://storage.googleapis.com/openimages/web/index.html. Reference coordinates were normalized/joined for evaluation. Photo pixels are not included there.
|
package/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# Zorya Classifier
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/zorya-classifier)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Provider-independent text/image classification for Node.js and TypeScript, with experimental, unqualified spatial localization and object extraction. MIT application code. The library never selects or connects a model for you: create a client with an explicit `DecisionPort`, either injected by your application or built with an adapter you configure. The model and provider shown in examples are examples, not defaults. Ordinary vision language models and native decision APIs are both supported for closed classification.
|
|
6
|
+
|
|
7
|
+
Use an explicitly configured image-capable model through a supported adapter, or implement the small `DecisionPort` for another protocol. OpenRouter is an optional adapter, not a mandatory service. A more capable model may help on a task, but model strength does not guarantee a monotonic accuracy improvement. Compatibility and quality vary; see the exact [tested models/results](docs/vision-results.md), [acceptance](docs/acceptance.md), and historical [Jev baseline](docs/results.md).
|
|
6
8
|
|
|
7
9
|
[Русское руководство](docs/README.ru.md) · [API](docs/api.md) · [Architecture](docs/architecture.md) · [Benchmarks](docs/benchmarks.md)
|
|
8
10
|
|
|
11
|
+
Version 0.2 adds region-seeded segmentation, mask-derived bounds and optional transparent cutouts, with a closed completeness check and one bounded mask expansion. See the [usage guide](docs/v02-usage.md) for contracts, backend ownership and CLI.
|
|
12
|
+
|
|
13
|
+
The [frozen 80-case evaluation](docs/evidence/v02/region-seeded-heldout-v3/README.md) passed its predeclared admission criteria, but localization remains limited: 26/60 positive seed hits and 8/60 complete object boxes; no false seeds or cutouts on 20 negatives. Eight of 30 reference masks reached IoU 0.8, three failed and 19 had no predicted mask. These results describe one explicit caller-configured Luna/OpenAI + MobileSAM recipe, not every model or game.
|
|
14
|
+
|
|
15
|
+
Six experimental encodings for text-only models were also tested on fresh photos and real UI captures. None qualified for image localization; see the [measured comparison](docs/evidence/v02/fresh-text/README.md). They remain research code rather than an advertised replacement for a vision model.
|
|
16
|
+
|
|
9
17
|
## Install
|
|
10
18
|
|
|
11
19
|
Node.js 22.12+ is required. Source: [zoomplus/zorya-classifier](https://github.com/zoomplus/zorya-classifier). Package: [zorya-classifier](https://www.npmjs.com/package/zorya-classifier).
|
|
@@ -14,11 +22,27 @@ Node.js 22.12+ is required. Source: [zoomplus/zorya-classifier](https://github.c
|
|
|
14
22
|
npm install zorya-classifier
|
|
15
23
|
```
|
|
16
24
|
|
|
17
|
-
The release archive also installs with `npm install ./zorya-classifier-0.1.0.tgz`. Runtime dependencies are Zod and Sharp; model weights, Electron, a database and background services are not included.
|
|
25
|
+
The release archive also installs with `npm install ./zorya-classifier-0.1.0.tgz`. Runtime dependencies are Zod and Sharp; model weights, Electron, a database and background services are not included. Optional local segmentation backends and their model assets are a separate responsibility of the caller; the library does not download or choose them.
|
|
26
|
+
|
|
27
|
+
## Capability and status
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
Further accuracy experiments on spatial workflows are stopped; no localization, extraction, or proposal strategy is promoted for general use. Historical metrics below and in `docs/evidence/**` are preserved as recorded.
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
| Capability | Status | Notes |
|
|
32
|
+
| --------------------------------------------------------------- | ------------ | --------------------------------------------------------------------- |
|
|
33
|
+
| `classify` with an explicit `DecisionPort` / supported adapters | Supported | Closed class ID; no universal model accuracy claim |
|
|
34
|
+
| Default `locate` (region grid search) | Experimental | Hypothesized grid cell, not whole-object geometry or proof of absence |
|
|
35
|
+
| `RefinementPort` | Experimental | No general quality qualification |
|
|
36
|
+
| `locate({ mode: 'whole-object' })` | Experimental | Interval or segmentation paths; unqualified |
|
|
37
|
+
| `extract`, `SegmentationPort`, box-check composition | Experimental | Structural checks ≠ semantic object or mask quality |
|
|
38
|
+
| `objectProposals` / native segmentation factories | Experimental | Caller-owned; unqualified |
|
|
39
|
+
| `representation: { type: 'experimental-text', … }` | Rejected | `UNSUPPORTED` until a profile qualifies |
|
|
40
|
+
|
|
41
|
+
Heldout region-seeded metrics (one documented recipe, not universal): 26/60 positive coarse seed hits; 8/60 complete whole-object boxes; 0 false seeds on 20 negatives. Among 30 exact-canvas masks: 8 reached IoU 0.8, 3 failed, 19 had no predicted mask. H8 DEV reported 7/9 positives with 2/3 false outputs and did not qualify. A narrow historical admission pass is not broad spatial qualification.
|
|
42
|
+
|
|
43
|
+
## Example: Luna Decisions
|
|
44
|
+
|
|
45
|
+
This example uses the typed **Decisions API** with genuine Choice probabilities. Model and provider are explicit; there are no automatic retries or fallbacks. It illustrates one integration and is not a library default. Jev is optional and is not used by this example.
|
|
22
46
|
|
|
23
47
|
```ts
|
|
24
48
|
import { createClassifier } from 'zorya-classifier'
|
|
@@ -93,9 +117,9 @@ try {
|
|
|
93
117
|
|
|
94
118
|
Supply the **full trusted chat-completions URL**, such as your local `http://localhost:8000/v1/chat/completions`, and the actual deployed model ID. Anonymous local servers can omit the key; custom headers support other authentication. These are integration examples, not a claim that every provider/model has been live-qualified. Other wire protocols can implement the exported `DecisionPort`. Keep keys on a trusted server, outside frontend bundles and Git.
|
|
95
119
|
|
|
96
|
-
## Locate an object
|
|
120
|
+
## Locate an object (experimental)
|
|
97
121
|
|
|
98
|
-
With either configured client:
|
|
122
|
+
Default region `locate` is experimental and unqualified. With either configured client:
|
|
99
123
|
|
|
100
124
|
```ts
|
|
101
125
|
const result = await client.locate({
|
|
@@ -109,6 +133,10 @@ if (result.status === 'located') console.log(result.region)
|
|
|
109
133
|
|
|
110
134
|
The locator uses up to three sequential calls: coarse 4×4 regions, fine grids in four padded regions, then four enlarged candidates. Coordinates come from code, never free-form model coordinates. Ordinary-model rankings select candidates; independent present/absent/unknown answers govern abstention. Multiple matching physical instances are allowed; one candidate is returned. Localization requires the model/endpoint to accept multiple images per call (up to nine); single-image-only endpoints can still support `classify`.
|
|
111
135
|
|
|
136
|
+
## Extract a mask-derived object (experimental)
|
|
137
|
+
|
|
138
|
+
Extraction and caller-owned segmentation are experimental and unqualified. For a visible object box, center, or transparent cutout, use `extract` with an explicitly configured caller-owned segmenter. The existing region locator supplies a seed; the segmenter receives that seed while processing the full oriented source image. The library derives the returned box and center from actual mask pixels. After segmentation, the caller's decision port checks the original image with the mask-derived box and seed anchor. An incomplete box permits one enclosing-mask correction and one recheck; unresolved results remain unknown. This adds one or two closed checks to the normal region requests, without model-generated coordinates or an extent search. See the [0.2 usage guide](docs/v02-usage.md) and runnable [`extract-object.ts`](https://github.com/zoomplus/zorya-classifier/blob/main/examples/extract-object.ts). The classifier and segmenter are separately caller-owned and both must be disposed.
|
|
139
|
+
|
|
112
140
|
## Development and limits
|
|
113
141
|
|
|
114
142
|
```sh
|
|
@@ -123,6 +151,6 @@ CLI uses explicit `ZORYA_MODEL` and defaults to `ZORYA_MODE=decisions`. OpenRout
|
|
|
123
151
|
|
|
124
152
|
Tests are offline. Examples, CLI and explicitly authorized model benchmarks make paid calls. Default operation limits: 45-second absolute timeout, two active operations, sixteen waiters, bounded image/request/response sizes. Honor cancellation and call `dispose` on shutdown. The library has no import-time network request, hidden model switching or automatic retry.
|
|
125
153
|
|
|
126
|
-
A model must understand the input, follow instructions and produce the checked answer shape. JSON Schema/JSON-object modes are opt-in when the chosen endpoint supports them; prompt-only mode does not require structured-output support. No universal accuracy, calibration or sub-second guarantee is asserted.
|
|
154
|
+
A model must understand the input, follow instructions and produce the checked answer shape. JSON Schema/JSON-object modes are opt-in when the chosen endpoint supports them; prompt-only mode does not require structured-output support. No universal accuracy, calibration or sub-second guarantee is asserted. Legacy region refinement uses `RefinementPort`; the 0.2 explicit `extract` workflow uses a caller-owned `SegmentationPort`.
|
|
127
155
|
|
|
128
|
-
See [CONTRIBUTING](CONTRIBUTING.md), [SECURITY](SECURITY.md), [NOTICE](NOTICE.md), and [LICENSE](LICENSE).
|
|
156
|
+
See [CONTRIBUTING](https://github.com/zoomplus/zorya-classifier/blob/main/CONTRIBUTING.md), [SECURITY](https://github.com/zoomplus/zorya-classifier/blob/main/SECURITY.md), [NOTICE](NOTICE.md), and [LICENSE](LICENSE).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const schema: z.ZodObject<{
|
|
3
|
+
command: z.ZodEnum<["classify-text", "classify-image", "locate", "extract"]>;
|
|
4
|
+
value: z.ZodString;
|
|
5
|
+
target: z.ZodString;
|
|
6
|
+
mode: z.ZodOptional<z.ZodEnum<["region", "whole-object"]>>;
|
|
7
|
+
output: z.ZodOptional<z.ZodString>;
|
|
8
|
+
backend: z.ZodOptional<z.ZodEnum<["mobile-sam", "grabcut"]>>;
|
|
9
|
+
encoder: z.ZodOptional<z.ZodString>;
|
|
10
|
+
decoder: z.ZodOptional<z.ZodString>;
|
|
11
|
+
opencv: z.ZodOptional<z.ZodString>;
|
|
12
|
+
provider: z.ZodOptional<z.ZodEnum<["cpu", "dml"]>>;
|
|
13
|
+
}, "strict", z.ZodTypeAny, {
|
|
14
|
+
value: string;
|
|
15
|
+
target: string;
|
|
16
|
+
command: "classify-text" | "classify-image" | "locate" | "extract";
|
|
17
|
+
mode?: "region" | "whole-object" | undefined;
|
|
18
|
+
provider?: "cpu" | "dml" | undefined;
|
|
19
|
+
output?: string | undefined;
|
|
20
|
+
backend?: "mobile-sam" | "grabcut" | undefined;
|
|
21
|
+
encoder?: string | undefined;
|
|
22
|
+
decoder?: string | undefined;
|
|
23
|
+
opencv?: string | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
value: string;
|
|
26
|
+
target: string;
|
|
27
|
+
command: "classify-text" | "classify-image" | "locate" | "extract";
|
|
28
|
+
mode?: "region" | "whole-object" | undefined;
|
|
29
|
+
provider?: "cpu" | "dml" | undefined;
|
|
30
|
+
output?: string | undefined;
|
|
31
|
+
backend?: "mobile-sam" | "grabcut" | undefined;
|
|
32
|
+
encoder?: string | undefined;
|
|
33
|
+
decoder?: string | undefined;
|
|
34
|
+
opencv?: string | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
type CliArguments = z.infer<typeof schema>;
|
|
37
|
+
export declare function parseCliArguments(args: string[]): CliArguments | {
|
|
38
|
+
command: 'help';
|
|
39
|
+
};
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { parse } from '../entities/contracts.js';
|
|
4
|
+
import { ClassifierError } from '../entities/errors.js';
|
|
5
|
+
const schema = z
|
|
6
|
+
.object({
|
|
7
|
+
command: z.enum(['classify-text', 'classify-image', 'locate', 'extract']),
|
|
8
|
+
value: z.string().min(1),
|
|
9
|
+
target: z.string().min(1),
|
|
10
|
+
mode: z.enum(['region', 'whole-object']).optional(),
|
|
11
|
+
output: z.string().min(1).optional(),
|
|
12
|
+
backend: z.enum(['mobile-sam', 'grabcut']).optional(),
|
|
13
|
+
encoder: z.string().min(1).optional(),
|
|
14
|
+
decoder: z.string().min(1).optional(),
|
|
15
|
+
opencv: z.string().min(1).optional(),
|
|
16
|
+
provider: z.enum(['cpu', 'dml']).optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
export function parseCliArguments(args) {
|
|
20
|
+
let raw;
|
|
21
|
+
try {
|
|
22
|
+
raw = parseArgs({
|
|
23
|
+
args,
|
|
24
|
+
allowPositionals: true,
|
|
25
|
+
strict: true,
|
|
26
|
+
options: {
|
|
27
|
+
help: { type: 'boolean', short: 'h' },
|
|
28
|
+
mode: { type: 'string' },
|
|
29
|
+
output: { type: 'string' },
|
|
30
|
+
backend: { type: 'string' },
|
|
31
|
+
encoder: { type: 'string' },
|
|
32
|
+
decoder: { type: 'string' },
|
|
33
|
+
opencv: { type: 'string' },
|
|
34
|
+
provider: { type: 'string' },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new ClassifierError('INVALID_INPUT', 'Invalid CLI arguments; use --help');
|
|
40
|
+
}
|
|
41
|
+
if (raw.values['help'] || raw.positionals.length === 0)
|
|
42
|
+
return { command: 'help' };
|
|
43
|
+
if (raw.positionals.length !== 3)
|
|
44
|
+
throw new ClassifierError('INVALID_INPUT', 'Expected command, input and target/classes; use --help');
|
|
45
|
+
const { help: _help, ...values } = raw.values;
|
|
46
|
+
const parsed = parse(schema, {
|
|
47
|
+
...values,
|
|
48
|
+
command: raw.positionals[0],
|
|
49
|
+
value: raw.positionals[1],
|
|
50
|
+
target: raw.positionals[2],
|
|
51
|
+
});
|
|
52
|
+
const nativeFlags = ['backend', 'encoder', 'decoder', 'opencv', 'provider', 'output'];
|
|
53
|
+
if (parsed.command !== 'extract' && nativeFlags.some((key) => parsed[key] !== undefined))
|
|
54
|
+
throw new ClassifierError('INVALID_INPUT', 'Native/output flags apply only to extract');
|
|
55
|
+
if (parsed.mode !== undefined && !['locate', 'extract'].includes(parsed.command))
|
|
56
|
+
throw new ClassifierError('INVALID_INPUT', 'Mode applies only to image localization');
|
|
57
|
+
if (parsed.command === 'extract') {
|
|
58
|
+
if (!parsed.output || !parsed.backend || parsed.mode === 'region')
|
|
59
|
+
throw new ClassifierError('INVALID_INPUT', 'extract requires --output and explicit --backend, and uses whole-object mode');
|
|
60
|
+
if (parsed.backend === 'mobile-sam' && (!parsed.encoder || !parsed.decoder || parsed.opencv))
|
|
61
|
+
throw new ClassifierError('INVALID_INPUT', 'mobile-sam requires --encoder and --decoder');
|
|
62
|
+
if (parsed.backend === 'grabcut' &&
|
|
63
|
+
(!parsed.opencv || parsed.encoder || parsed.decoder || parsed.provider))
|
|
64
|
+
throw new ClassifierError('INVALID_INPUT', 'grabcut requires --opencv; model/provider flags do not apply');
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
@@ -1,14 +1,38 @@
|
|
|
1
1
|
import type { ClassificationResult, ClassifyInput, DecisionPort, LocalizationResult, LocateInput, RefinementPort } from '../entities/contracts.js';
|
|
2
|
+
import type { AnyLocateInput, WholeObjectLocateInput, WholeObjectLocalizationResult } from '../entities/whole-object.js';
|
|
3
|
+
import type { WholeObjectIntervalPolicy } from '../processes/whole-object.js';
|
|
4
|
+
import type { ImageRepresentationPort } from '../entities/representation.js';
|
|
5
|
+
import type { ExtractInput, ExtractionResult, SegmentationPort } from '../entities/segmentation.js';
|
|
6
|
+
import type { BoundaryViewPort } from '../entities/boundary-view.js';
|
|
7
|
+
import type { ObjectProposalPort } from '../entities/object-proposals.js';
|
|
8
|
+
import type { SegmentationBoxCheckPort } from '../entities/segmentation-verification.js';
|
|
2
9
|
export interface ClassifierOptions {
|
|
3
10
|
decision: DecisionPort;
|
|
4
11
|
timeoutMs?: number;
|
|
5
12
|
concurrency?: number;
|
|
6
13
|
maxQueue?: number;
|
|
14
|
+
maxInputBytes?: number;
|
|
15
|
+
/** @experimental Optional legacy region refinement; unqualified for general use. */
|
|
7
16
|
refinement?: RefinementPort;
|
|
17
|
+
/** Caller-owned; classifier disposal never disposes a shared segmenter. @experimental Unqualified segmentation adapter. */
|
|
18
|
+
segmentation?: SegmentationPort;
|
|
19
|
+
/** Optional caller-owned containment checker; by default reuses the injected decision port. @experimental Unqualified box-check override. */
|
|
20
|
+
segmentationBoxCheck?: SegmentationBoxCheckPort;
|
|
21
|
+
/** Explicit, caller-owned pixel-mask proposal strategy for whole-object operations. @experimental Unqualified proposal path. */
|
|
22
|
+
objectProposals?: ObjectProposalPort;
|
|
8
23
|
}
|
|
9
24
|
export interface Classifier {
|
|
10
25
|
classify(input: ClassifyInput): Promise<ClassificationResult>;
|
|
26
|
+
/** @experimental Unqualified spatial localization; see {@link LocateInput} and {@link WholeObjectLocateInput}. */
|
|
27
|
+
locate(input: WholeObjectLocateInput): Promise<WholeObjectLocalizationResult>;
|
|
28
|
+
/** @experimental Unqualified coarse region search; returns a hypothesized grid cell. */
|
|
11
29
|
locate(input: LocateInput): Promise<LocalizationResult>;
|
|
30
|
+
/** @experimental Unqualified spatial localization. */
|
|
31
|
+
locate(input: AnyLocateInput): Promise<LocalizationResult | WholeObjectLocalizationResult>;
|
|
32
|
+
/** @experimental Region-seeded extraction; unqualified for general mask or object accuracy. */
|
|
33
|
+
extract(input: ExtractInput): Promise<ExtractionResult>;
|
|
12
34
|
dispose(): Promise<void>;
|
|
13
35
|
}
|
|
14
36
|
export declare function createClassifier(options: ClassifierOptions): Classifier;
|
|
37
|
+
/** Internal benchmark composition. No research codec is exported by the npm entry points. */
|
|
38
|
+
export declare function createResearchClassifier(options: ClassifierOptions, experimental?: ImageRepresentationPort, boundaryView?: BoundaryViewPort, intervalPolicy?: WholeObjectIntervalPolicy): Classifier;
|
|
@@ -6,10 +6,28 @@ import { classifyInput } from '../processes/classify.js';
|
|
|
6
6
|
import { locateInput } from '../processes/locate.js';
|
|
7
7
|
import { OperationGate } from '../shared/gate.js';
|
|
8
8
|
import { OperationScope } from '../shared/scope.js';
|
|
9
|
+
import { InputBudget } from '../shared/input-budget.js';
|
|
10
|
+
import { wholeObjectSchema } from '../entities/whole-object.js';
|
|
11
|
+
import { locateWholeObjectInput } from '../processes/whole-object.js';
|
|
12
|
+
import { preparedTextSchema, representationLimits } from '../entities/representation.js';
|
|
13
|
+
import { extractSchema } from '../entities/segmentation.js';
|
|
14
|
+
import { extractObject } from '../processes/extract.js';
|
|
15
|
+
import { extractFromRegion } from '../processes/extract-from-region.js';
|
|
16
|
+
import { createSharpCutoutRaster } from '../infrastructure/segmentation/cutout-raster.js';
|
|
17
|
+
import { locateFromProposals } from '../processes/proposal-location.js';
|
|
18
|
+
import { createSharpObjectProposalPresentation } from '../infrastructure/segmentation/proposal-presentation.js';
|
|
19
|
+
import { createSegmentationBoxCheck } from '../features/segmentation/box-check.js';
|
|
20
|
+
import { createSharpSegmentationBoxPresentation } from '../infrastructure/segmentation/box-presentation.js';
|
|
9
21
|
const configSchema = z.object({
|
|
10
22
|
timeoutMs: z.number().int().min(1).max(120000).default(45000),
|
|
11
23
|
concurrency: z.number().int().min(1).max(4).default(2),
|
|
12
24
|
maxQueue: z.number().int().min(0).max(32).default(16),
|
|
25
|
+
maxInputBytes: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(256 * 1024 * 1024)
|
|
30
|
+
.default(64 * 1024 * 1024),
|
|
13
31
|
});
|
|
14
32
|
function signalOf(value) {
|
|
15
33
|
if (value === undefined)
|
|
@@ -19,17 +37,123 @@ function signalOf(value) {
|
|
|
19
37
|
return value;
|
|
20
38
|
}
|
|
21
39
|
export function createClassifier(options) {
|
|
40
|
+
return composeClassifier(options);
|
|
41
|
+
}
|
|
42
|
+
/** Internal benchmark composition. No research codec is exported by the npm entry points. */
|
|
43
|
+
export function createResearchClassifier(options, experimental, boundaryView, intervalPolicy) {
|
|
44
|
+
return composeClassifier(options, experimental, boundaryView, intervalPolicy);
|
|
45
|
+
}
|
|
46
|
+
function composeClassifier(options, experimental, boundaryView, intervalPolicy) {
|
|
22
47
|
if (!options || typeof options.decision?.decide !== 'function')
|
|
23
48
|
throw new ClassifierError('INVALID_INPUT', 'A DecisionPort is required');
|
|
49
|
+
const segmenter = options.segmentation;
|
|
50
|
+
const objectProposals = options.objectProposals;
|
|
51
|
+
if (segmenter !== undefined &&
|
|
52
|
+
(!segmenter ||
|
|
53
|
+
typeof segmenter.segment !== 'function' ||
|
|
54
|
+
(segmenter.expand !== undefined && typeof segmenter.expand !== 'function') ||
|
|
55
|
+
typeof segmenter.settle !== 'function' ||
|
|
56
|
+
typeof segmenter.dispose !== 'function'))
|
|
57
|
+
throw new ClassifierError('INVALID_INPUT', 'segmentation must implement SegmentationPort');
|
|
58
|
+
if (options.segmentationBoxCheck !== undefined &&
|
|
59
|
+
(!options.segmentationBoxCheck || typeof options.segmentationBoxCheck.check !== 'function'))
|
|
60
|
+
throw new ClassifierError('INVALID_INPUT', 'segmentationBoxCheck must implement its check port');
|
|
61
|
+
if (objectProposals !== undefined &&
|
|
62
|
+
(!objectProposals ||
|
|
63
|
+
typeof objectProposals.propose !== 'function' ||
|
|
64
|
+
typeof objectProposals.settle !== 'function' ||
|
|
65
|
+
typeof objectProposals.dispose !== 'function'))
|
|
66
|
+
throw new ClassifierError('INVALID_INPUT', 'objectProposals must implement ObjectProposalPort');
|
|
24
67
|
const config = parse(configSchema, {
|
|
25
68
|
timeoutMs: options.timeoutMs,
|
|
26
69
|
concurrency: options.concurrency,
|
|
27
70
|
maxQueue: options.maxQueue,
|
|
71
|
+
maxInputBytes: options.maxInputBytes,
|
|
28
72
|
});
|
|
29
73
|
const gate = new OperationGate(config.concurrency, config.maxQueue);
|
|
74
|
+
const inputBudget = new InputBudget(config.maxInputBytes);
|
|
30
75
|
const raster = createSharpRaster();
|
|
76
|
+
const cutoutRaster = createSharpCutoutRaster();
|
|
77
|
+
const boxCheck = segmenter
|
|
78
|
+
? (options.segmentationBoxCheck ??
|
|
79
|
+
createSegmentationBoxCheck({
|
|
80
|
+
decision: options.decision,
|
|
81
|
+
presentation: createSharpSegmentationBoxPresentation(),
|
|
82
|
+
imageState: raster.imageState,
|
|
83
|
+
}))
|
|
84
|
+
: undefined;
|
|
85
|
+
const proposalDependencies = objectProposals
|
|
86
|
+
? {
|
|
87
|
+
decision: options.decision,
|
|
88
|
+
raster,
|
|
89
|
+
image: cutoutRaster,
|
|
90
|
+
proposals: objectProposals,
|
|
91
|
+
presentation: createSharpObjectProposalPresentation(),
|
|
92
|
+
}
|
|
93
|
+
: undefined;
|
|
31
94
|
const owned = new Map();
|
|
32
95
|
let disposed = false;
|
|
96
|
+
const textRepresentation = experimental
|
|
97
|
+
? {
|
|
98
|
+
async prepare(input) {
|
|
99
|
+
const result = parse(preparedTextSchema, await experimental.prepare(input), 'INVALID_RESPONSE');
|
|
100
|
+
const bytes = result.state.reduce((sum, part) => sum + Buffer.byteLength(part, 'utf8'), 0);
|
|
101
|
+
if (bytes > representationLimits.totalBytes || bytes !== result.metadata.bytes)
|
|
102
|
+
throw new ClassifierError('INVALID_RESPONSE', 'Representation byte accounting is inconsistent');
|
|
103
|
+
return {
|
|
104
|
+
state: result.state,
|
|
105
|
+
metadata: {
|
|
106
|
+
...result.metadata,
|
|
107
|
+
views: result.metadata.views.map(({ content, ...view }) => ({
|
|
108
|
+
...view,
|
|
109
|
+
...(content === undefined ? {} : { content }),
|
|
110
|
+
})),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
: undefined;
|
|
116
|
+
function representationFor(value) {
|
|
117
|
+
if (value?.type !== 'experimental-text')
|
|
118
|
+
return undefined;
|
|
119
|
+
if (!textRepresentation)
|
|
120
|
+
throw new ClassifierError('UNSUPPORTED', 'No text-image profile has passed release qualification yet');
|
|
121
|
+
return textRepresentation;
|
|
122
|
+
}
|
|
123
|
+
async function locateWhole(input, signal, representation) {
|
|
124
|
+
if (segmenter) {
|
|
125
|
+
const result = await extractRegion(input, signal, representation);
|
|
126
|
+
if (result.status === 'extracted')
|
|
127
|
+
return result.location;
|
|
128
|
+
if (result.status === 'unknown' && result.reason === 'segmentation-uncertain')
|
|
129
|
+
return {
|
|
130
|
+
status: 'unknown',
|
|
131
|
+
reason: 'segmentation-uncertain',
|
|
132
|
+
metadata: 'metadata' in result ? result.metadata : result.location.metadata,
|
|
133
|
+
...(result.verification ? { verification: result.verification } : {}),
|
|
134
|
+
};
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
if (proposalDependencies) {
|
|
138
|
+
if (representation)
|
|
139
|
+
throw new ClassifierError('UNSUPPORTED', 'Mask proposal selection requires vision input');
|
|
140
|
+
return (await locateFromProposals(input, proposalDependencies, signal)).location;
|
|
141
|
+
}
|
|
142
|
+
return locateWholeObjectInput(input, options.decision, raster, signal, representation, boundaryView, intervalPolicy);
|
|
143
|
+
}
|
|
144
|
+
function extractRegion(input, signal, representation) {
|
|
145
|
+
if (!segmenter || !boxCheck)
|
|
146
|
+
throw new ClassifierError('UNSUPPORTED', 'Region extraction requires a SegmentationPort');
|
|
147
|
+
if (representation)
|
|
148
|
+
throw new ClassifierError('UNSUPPORTED', 'Checked segmentation requires vision input');
|
|
149
|
+
return extractFromRegion({ ...input, signal }, {
|
|
150
|
+
raster,
|
|
151
|
+
image: cutoutRaster,
|
|
152
|
+
segmenter,
|
|
153
|
+
boxCheck,
|
|
154
|
+
locateRegion: (source, { signal: operationSignal, ...request }) => locateInput({ ...request, image: source.bytes }, options.decision, raster, operationSignal, undefined, representation),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
33
157
|
async function run(signal, task) {
|
|
34
158
|
if (disposed)
|
|
35
159
|
throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
|
|
@@ -58,6 +182,37 @@ export function createClassifier(options) {
|
|
|
58
182
|
owned.set(scope, job);
|
|
59
183
|
return job;
|
|
60
184
|
}
|
|
185
|
+
async function locate(input) {
|
|
186
|
+
if (disposed)
|
|
187
|
+
throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
|
|
188
|
+
gate.assertAvailable();
|
|
189
|
+
if (!input || typeof input !== 'object')
|
|
190
|
+
throw new ClassifierError('INVALID_INPUT', 'Expected locate input object');
|
|
191
|
+
const { signal, ...rest } = input;
|
|
192
|
+
const parent = signalOf(signal);
|
|
193
|
+
if (rest.mode === 'whole-object') {
|
|
194
|
+
const parsed = parse(wholeObjectSchema, rest);
|
|
195
|
+
const representation = representationFor(parsed.representation);
|
|
196
|
+
const release = inputBudget.reserve(parsed.image.byteLength);
|
|
197
|
+
try {
|
|
198
|
+
const snapshot = { ...parsed, image: new Uint8Array(parsed.image) };
|
|
199
|
+
return await run(parent, (s) => locateWhole(snapshot, s, representation));
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
release();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const parsed = parse(locateSchema, rest);
|
|
206
|
+
const representation = representationFor(parsed.representation);
|
|
207
|
+
const release = inputBudget.reserve(parsed.image.byteLength);
|
|
208
|
+
try {
|
|
209
|
+
const snapshot = { ...parsed, image: new Uint8Array(parsed.image) };
|
|
210
|
+
return await run(parent, (s) => locateInput(snapshot, options.decision, raster, s, options.refinement, representation));
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
release();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
61
216
|
return {
|
|
62
217
|
async classify(input) {
|
|
63
218
|
if (disposed)
|
|
@@ -68,27 +223,84 @@ export function createClassifier(options) {
|
|
|
68
223
|
const { signal, ...rest } = input;
|
|
69
224
|
const parent = signalOf(signal);
|
|
70
225
|
const parsed = parse(classifySchema, rest);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
226
|
+
const hasImage = typeof parsed.input !== 'string' && parsed.input.image !== undefined;
|
|
227
|
+
const representation = hasImage ? representationFor(parsed.representation) : undefined;
|
|
228
|
+
const byteCount = typeof parsed.input === 'string'
|
|
229
|
+
? Buffer.byteLength(parsed.input)
|
|
230
|
+
: (parsed.input.image?.byteLength ?? 0) + Buffer.byteLength(parsed.input.text ?? '');
|
|
231
|
+
const release = inputBudget.reserve(byteCount);
|
|
232
|
+
try {
|
|
233
|
+
const media = typeof parsed.input === 'string'
|
|
234
|
+
? parsed.input
|
|
235
|
+
: {
|
|
236
|
+
...parsed.input,
|
|
237
|
+
...(parsed.input.image === undefined
|
|
238
|
+
? {}
|
|
239
|
+
: { image: new Uint8Array(parsed.input.image) }),
|
|
240
|
+
};
|
|
241
|
+
return await run(parent, (s) => classifyInput({ ...parsed, input: media }, options.decision, raster, s, representation));
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
release();
|
|
245
|
+
}
|
|
80
246
|
},
|
|
81
|
-
|
|
247
|
+
locate,
|
|
248
|
+
async extract(input) {
|
|
82
249
|
if (disposed)
|
|
83
250
|
throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
|
|
84
251
|
gate.assertAvailable();
|
|
85
252
|
if (!input || typeof input !== 'object')
|
|
86
|
-
throw new ClassifierError('INVALID_INPUT', 'Expected
|
|
253
|
+
throw new ClassifierError('INVALID_INPUT', 'Expected extract input object');
|
|
87
254
|
const { signal, ...rest } = input;
|
|
88
255
|
const parent = signalOf(signal);
|
|
89
|
-
const parsed = parse(
|
|
90
|
-
|
|
91
|
-
|
|
256
|
+
const parsed = parse(extractSchema, rest);
|
|
257
|
+
if (!segmenter && !proposalDependencies)
|
|
258
|
+
throw new ClassifierError('UNSUPPORTED', 'extract requires an explicit SegmentationPort or ObjectProposalPort');
|
|
259
|
+
const representation = representationFor(parsed.representation);
|
|
260
|
+
const release = inputBudget.reserve(parsed.image.byteLength);
|
|
261
|
+
try {
|
|
262
|
+
const snapshot = { ...parsed, image: new Uint8Array(parsed.image) };
|
|
263
|
+
return await run(parent, (s) => {
|
|
264
|
+
if (segmenter)
|
|
265
|
+
return extractRegion(snapshot, s, representation);
|
|
266
|
+
let prepared;
|
|
267
|
+
// Per-operation ownership: no global selected-mask cache or second native inference.
|
|
268
|
+
const selectedMask = {
|
|
269
|
+
async segment() {
|
|
270
|
+
if (!prepared?.mask || !prepared.segmentation)
|
|
271
|
+
throw new ClassifierError('INVALID_RESPONSE', 'Proposal selection omitted its mask');
|
|
272
|
+
return {
|
|
273
|
+
status: 'segmented',
|
|
274
|
+
mask: prepared.mask,
|
|
275
|
+
metadata: prepared.segmentation,
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
async settle() { },
|
|
279
|
+
async dispose() { },
|
|
280
|
+
};
|
|
281
|
+
return extractObject({ ...snapshot, mode: 'whole-object', signal: s }, {
|
|
282
|
+
raster,
|
|
283
|
+
image: cutoutRaster,
|
|
284
|
+
segmenter: selectedMask,
|
|
285
|
+
locateWhole: async (source, { signal: requestSignal, ...request }) => {
|
|
286
|
+
const wholeInput = {
|
|
287
|
+
...request,
|
|
288
|
+
mode: 'whole-object',
|
|
289
|
+
image: source.bytes,
|
|
290
|
+
};
|
|
291
|
+
if (!proposalDependencies)
|
|
292
|
+
throw new ClassifierError('UNSUPPORTED', 'ObjectProposalPort is required');
|
|
293
|
+
if (representation)
|
|
294
|
+
throw new ClassifierError('UNSUPPORTED', 'Mask proposal selection requires vision input');
|
|
295
|
+
prepared = await locateFromProposals(wholeInput, proposalDependencies, requestSignal);
|
|
296
|
+
return prepared.location;
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
release();
|
|
303
|
+
}
|
|
92
304
|
},
|
|
93
305
|
async dispose() {
|
|
94
306
|
disposed = true;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ExtractionResult } from '../entities/segmentation.js';
|
|
2
|
+
/** Reserve every destination exclusively before writing; never overwrite an existing artifact. */
|
|
3
|
+
export declare function writeExtractionFiles(prefix: string, result: Extract<ExtractionResult, {
|
|
4
|
+
status: 'extracted';
|
|
5
|
+
}>): Promise<{
|
|
6
|
+
png: string;
|
|
7
|
+
mask: string;
|
|
8
|
+
descriptor: string;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { open, unlink } from 'node:fs/promises';
|
|
2
|
+
/** Reserve every destination exclusively before writing; never overwrite an existing artifact. */
|
|
3
|
+
export async function writeExtractionFiles(prefix, result) {
|
|
4
|
+
const files = { png: `${prefix}.png`, mask: `${prefix}.mask.u8`, descriptor: `${prefix}.json` };
|
|
5
|
+
const { png, mask, ...description } = result;
|
|
6
|
+
const descriptor = JSON.stringify({
|
|
7
|
+
...description,
|
|
8
|
+
mask: { width: mask.width, height: mask.height, format: mask.format },
|
|
9
|
+
files,
|
|
10
|
+
}, null, 2);
|
|
11
|
+
const owned = [];
|
|
12
|
+
try {
|
|
13
|
+
for (const path of Object.values(files))
|
|
14
|
+
owned.push({ path, handle: await open(path, 'wx') });
|
|
15
|
+
for (const [index, bytes] of [png, mask.data, descriptor].entries())
|
|
16
|
+
await owned[index].handle.writeFile(bytes);
|
|
17
|
+
await Promise.all(owned.map(({ handle }) => handle.close()));
|
|
18
|
+
return files;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const closed = await Promise.allSettled(owned.map(({ handle }) => handle.close()));
|
|
22
|
+
const removed = await Promise.allSettled(owned.map(({ path }) => unlink(path)));
|
|
23
|
+
const cleanupErrors = [...closed, ...removed].flatMap((outcome) => outcome.status === 'rejected' ? [outcome.reason] : []);
|
|
24
|
+
if (cleanupErrors.length)
|
|
25
|
+
throw new AggregateError([error, ...cleanupErrors], 'Extraction output and cleanup failed');
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|