zorya-classifier 0.1.0 → 0.2.0
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 +18 -6
- package/dist/app/cli-arguments.d.ts +40 -0
- package/dist/app/cli-arguments.js +67 -0
- package/dist/app/create-classifier.d.ts +19 -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 +77 -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 +53 -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 +30 -0
- package/dist/entities/segmentation-verification.js +1 -0
- package/dist/entities/segmentation.d.ts +159 -0
- package/dist/entities/segmentation.js +5 -0
- package/dist/entities/whole-object.d.ts +128 -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 +13 -0
- package/dist/infrastructure/segmentation/grabcut/index.js +118 -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 +58 -0
- package/dist/infrastructure/segmentation/mobile-sam/index.js +472 -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 +18 -5
- package/docs/acceptance.md +116 -3
- package/docs/api.md +8 -4
- package/docs/architecture.md +20 -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/README.md +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-261dc23.md +15 -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 +6 -0
- package/docs/plan.md +2 -0
- package/docs/publishing.md +10 -1
- package/docs/research/text-transport-audit.md +66 -0
- package/docs/v02-usage.md +162 -0
- package/docs/vision-results.md +29 -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 and coarse-to-fine object localization for Node.js and TypeScript. 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.
|
|
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,11 @@ 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.
|
|
18
26
|
|
|
19
|
-
##
|
|
27
|
+
## Example: Luna Decisions
|
|
20
28
|
|
|
21
|
-
This example uses the typed **Decisions API** with genuine Choice probabilities. Model and provider are explicit; there are no automatic retries or fallbacks. Jev is optional and is not used by this example.
|
|
29
|
+
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
30
|
|
|
23
31
|
```ts
|
|
24
32
|
import { createClassifier } from 'zorya-classifier'
|
|
@@ -109,6 +117,10 @@ if (result.status === 'located') console.log(result.region)
|
|
|
109
117
|
|
|
110
118
|
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
119
|
|
|
120
|
+
## Extract a mask-derived object
|
|
121
|
+
|
|
122
|
+
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`](examples/extract-object.ts). The classifier and segmenter are separately caller-owned and both must be disposed.
|
|
123
|
+
|
|
112
124
|
## Development and limits
|
|
113
125
|
|
|
114
126
|
```sh
|
|
@@ -123,6 +135,6 @@ CLI uses explicit `ZORYA_MODEL` and defaults to `ZORYA_MODE=decisions`. OpenRout
|
|
|
123
135
|
|
|
124
136
|
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
137
|
|
|
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.
|
|
138
|
+
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
139
|
|
|
128
140
|
See [CONTRIBUTING](CONTRIBUTING.md), [SECURITY](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,33 @@
|
|
|
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;
|
|
7
15
|
refinement?: RefinementPort;
|
|
16
|
+
/** Caller-owned; classifier disposal never disposes a shared segmenter. */
|
|
17
|
+
segmentation?: SegmentationPort;
|
|
18
|
+
/** Optional caller-owned containment checker; by default reuses the injected decision port. */
|
|
19
|
+
segmentationBoxCheck?: SegmentationBoxCheckPort;
|
|
20
|
+
/** Explicit, caller-owned pixel-mask proposal strategy for whole-object operations. */
|
|
21
|
+
objectProposals?: ObjectProposalPort;
|
|
8
22
|
}
|
|
9
23
|
export interface Classifier {
|
|
10
24
|
classify(input: ClassifyInput): Promise<ClassificationResult>;
|
|
25
|
+
locate(input: WholeObjectLocateInput): Promise<WholeObjectLocalizationResult>;
|
|
11
26
|
locate(input: LocateInput): Promise<LocalizationResult>;
|
|
27
|
+
locate(input: AnyLocateInput): Promise<LocalizationResult | WholeObjectLocalizationResult>;
|
|
28
|
+
extract(input: ExtractInput): Promise<ExtractionResult>;
|
|
12
29
|
dispose(): Promise<void>;
|
|
13
30
|
}
|
|
14
31
|
export declare function createClassifier(options: ClassifierOptions): Classifier;
|
|
32
|
+
/** Internal benchmark composition. No research codec is exported by the npm entry points. */
|
|
33
|
+
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
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -2,29 +2,73 @@
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { createClassifier } from './index.js';
|
|
4
4
|
import { createCliDecision } from './app/cli-route.js';
|
|
5
|
+
import { parseCliArguments } from './app/cli-arguments.js';
|
|
6
|
+
import { writeExtractionFiles } from './app/extraction-files.js';
|
|
7
|
+
import { createMobileSamSegmenter, createGrabCutSegmenter } from './segmentation.js';
|
|
5
8
|
async function main() {
|
|
6
|
-
const
|
|
7
|
-
if (command === '
|
|
8
|
-
console.log('zorya-classifier classify-text <text> <classes.json>\nzorya-classifier classify-image <image> <classes.json>\nzorya-classifier locate <image> <target>\nSet ZORYA_MODEL. Default ZORYA_MODE=decisions; use chat for ordinary vision models/custom chat endpoints. OpenRouter: OPENROUTER_API_KEY + ZORYA_PROVIDER. Direct: ZORYA_ENDPOINT + optional ZORYA_API_KEY. Optional ZORYA_REASONING_EFFORT and ZORYA_RESPONSE_FORMAT. One classify request; up to three
|
|
9
|
+
const args = parseCliArguments(process.argv.slice(2));
|
|
10
|
+
if (args.command === 'help') {
|
|
11
|
+
console.log('zorya-classifier classify-text <text> <classes.json>\nzorya-classifier classify-image <image> <classes.json>\nzorya-classifier locate <image> <target> [--mode region|whole-object]\nzorya-classifier extract <image> <target> --output <prefix> --backend mobile-sam --encoder <path> --decoder <path> [--provider cpu|dml]\nzorya-classifier extract <image> <target> --output <prefix> --backend grabcut --opencv <path>\nSet ZORYA_MODEL. Default ZORYA_MODE=decisions; use chat for ordinary vision models/custom chat endpoints. OpenRouter: OPENROUTER_API_KEY + ZORYA_PROVIDER. Direct: ZORYA_ENDPOINT + optional ZORYA_API_KEY. Optional ZORYA_REASONING_EFFORT and ZORYA_RESPONSE_FORMAT. One classify request; up to three region or six whole-object requests. Extraction additionally runs the explicit local backend. Output files are never overwritten; native assets are never downloaded automatically.');
|
|
9
12
|
return;
|
|
10
13
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
+
const { command, value, target } = args;
|
|
15
|
+
const decision = createCliDecision(process.env);
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const abort = () => controller.abort();
|
|
18
|
+
process.once('SIGINT', abort);
|
|
19
|
+
process.once('SIGTERM', abort);
|
|
20
|
+
let segmenter;
|
|
21
|
+
let client;
|
|
14
22
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
// Validate local data before creating a native worker or dispatching any model request.
|
|
24
|
+
const image = command === 'classify-text' ? undefined : await readFile(value);
|
|
25
|
+
let classes;
|
|
26
|
+
if (command === 'classify-text' || command === 'classify-image') {
|
|
18
27
|
const { z } = await import('zod');
|
|
19
|
-
const { classSchema } = await import('./entities/contracts.js');
|
|
28
|
+
const { classSchema, limits } = await import('./entities/contracts.js');
|
|
20
29
|
const unknownClasses = JSON.parse(await readFile(target, 'utf8'));
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
classes = z.array(classSchema).min(2).max(limits.maxClasses).parse(unknownClasses);
|
|
31
|
+
}
|
|
32
|
+
controller.signal.throwIfAborted();
|
|
33
|
+
if (command === 'extract')
|
|
34
|
+
segmenter =
|
|
35
|
+
args.backend === 'mobile-sam'
|
|
36
|
+
? await createMobileSamSegmenter({
|
|
37
|
+
encoderPath: args.encoder,
|
|
38
|
+
decoderPath: args.decoder,
|
|
39
|
+
executionProvider: args.provider ?? 'cpu',
|
|
40
|
+
})
|
|
41
|
+
: await createGrabCutSegmenter({ assetPath: args.opencv });
|
|
42
|
+
client = createClassifier({ decision, ...(segmenter ? { segmentation: segmenter } : {}) });
|
|
43
|
+
controller.signal.throwIfAborted();
|
|
44
|
+
if (command === 'locate') {
|
|
45
|
+
console.log(JSON.stringify(await client.locate({
|
|
46
|
+
image: image,
|
|
47
|
+
target,
|
|
48
|
+
...(args.mode ? { mode: args.mode } : {}),
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
}), null, 2));
|
|
51
|
+
}
|
|
52
|
+
else if (command === 'extract') {
|
|
53
|
+
const result = await client.extract({ image: image, target, signal: controller.signal });
|
|
54
|
+
console.log(JSON.stringify(result.status === 'extracted'
|
|
55
|
+
? { status: result.status, files: await writeExtractionFiles(args.output, result) }
|
|
56
|
+
: result, null, 2));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const input = command === 'classify-text' ? value : { image: image };
|
|
60
|
+
console.log(JSON.stringify(await client.classify({ input, classes: classes, signal: controller.signal }), null, 2));
|
|
24
61
|
}
|
|
25
62
|
}
|
|
26
63
|
finally {
|
|
27
|
-
|
|
64
|
+
process.removeListener('SIGINT', abort);
|
|
65
|
+
process.removeListener('SIGTERM', abort);
|
|
66
|
+
try {
|
|
67
|
+
await client?.dispose();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await segmenter?.dispose();
|
|
71
|
+
}
|
|
28
72
|
}
|
|
29
73
|
}
|
|
30
74
|
main().catch((error) => {
|