zorya-classifier 0.2.1 → 0.2.2
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/README.md +3 -3
- package/dist/app/classifier-config.d.ts +30 -0
- package/dist/app/classifier-config.js +46 -0
- package/dist/app/classifier-contracts.d.ts +32 -0
- package/dist/app/classifier-contracts.js +1 -0
- package/dist/app/classifier-representation.d.ts +5 -0
- package/dist/app/classifier-representation.js +33 -0
- package/dist/app/classifier-runtime.d.ts +7 -0
- package/dist/app/classifier-runtime.js +71 -0
- package/dist/app/classifier-spatial.d.ts +31 -0
- package/dist/app/classifier-spatial.js +78 -0
- package/dist/app/cli-arguments.d.ts +2 -2
- package/dist/app/create-classifier.d.ts +4 -34
- package/dist/app/create-classifier.js +37 -237
- package/dist/entities/classification.d.ts +98 -0
- package/dist/entities/classification.js +28 -0
- package/dist/entities/contracts.d.ts +6 -475
- package/dist/entities/contracts.js +6 -97
- package/dist/entities/decision.d.ts +211 -0
- package/dist/entities/decision.js +33 -0
- package/dist/entities/geometry.d.ts +37 -0
- package/dist/entities/geometry.js +26 -0
- package/dist/entities/image.d.ts +39 -0
- package/dist/entities/image.js +15 -0
- package/dist/entities/localization.d.ts +125 -0
- package/dist/entities/localization.js +13 -0
- package/dist/entities/segmentation-metadata.d.ts +70 -0
- package/dist/entities/segmentation-metadata.js +21 -1
- package/dist/entities/validation.d.ts +2 -0
- package/dist/entities/validation.js +9 -0
- package/dist/features/choice-beam-layout.js +2 -7
- package/dist/features/proposals/presentation.d.ts +21 -0
- package/dist/features/proposals/presentation.js +122 -0
- package/dist/features/proposals/validation.d.ts +138 -0
- package/dist/features/proposals/validation.js +125 -0
- package/dist/features/segmentation/box-check.js +10 -23
- package/dist/features/segmentation/mask.js +2 -7
- package/dist/features/segmentation/region-result.d.ts +18 -0
- package/dist/features/segmentation/region-result.js +171 -0
- package/dist/features/segmentation/source-mask.d.ts +6 -0
- package/dist/features/segmentation/source-mask.js +32 -0
- package/dist/features/segmentation/validation.d.ts +9 -0
- package/dist/features/segmentation/validation.js +94 -0
- package/dist/infrastructure/decision-transport/errors.d.ts +8 -0
- package/dist/infrastructure/decision-transport/errors.js +34 -0
- package/dist/infrastructure/decision-transport/images.d.ts +1 -0
- package/dist/infrastructure/decision-transport/images.js +52 -0
- package/dist/infrastructure/{openrouter-common.d.ts → decision-transport/request.d.ts} +6 -15
- package/dist/infrastructure/decision-transport/request.js +162 -0
- package/dist/infrastructure/decision-transport/response.d.ts +1 -0
- package/dist/infrastructure/decision-transport/response.js +72 -0
- package/dist/infrastructure/decision-transport/route-identity.d.ts +11 -0
- package/dist/infrastructure/decision-transport/route-identity.js +23 -0
- package/dist/infrastructure/openrouter-vision.js +1 -1
- package/dist/infrastructure/openrouter.js +7 -19
- package/dist/infrastructure/raster.js +3 -8
- package/dist/infrastructure/segmentation/grabcut/index.js +2 -4
- package/dist/infrastructure/segmentation/grabcut/masks.d.ts +49 -0
- package/dist/infrastructure/segmentation/grabcut/masks.js +89 -0
- package/dist/infrastructure/segmentation/grabcut/model-asset.d.ts +4 -0
- package/dist/infrastructure/segmentation/grabcut/model-asset.js +4 -0
- package/dist/infrastructure/segmentation/grabcut/runtime.d.ts +70 -0
- package/dist/infrastructure/segmentation/grabcut/runtime.js +96 -0
- package/dist/infrastructure/segmentation/grabcut/worker-inference.d.ts +4 -43
- package/dist/infrastructure/segmentation/grabcut/worker-inference.js +4 -187
- package/dist/infrastructure/segmentation/mobile-sam/index.d.ts +3 -66
- package/dist/infrastructure/segmentation/mobile-sam/index.js +2 -478
- package/dist/infrastructure/segmentation/mobile-sam/input.d.ts +46 -0
- package/dist/infrastructure/segmentation/mobile-sam/input.js +83 -0
- package/dist/infrastructure/segmentation/mobile-sam/model-assets.d.ts +8 -0
- package/dist/infrastructure/segmentation/mobile-sam/model-assets.js +8 -0
- package/dist/infrastructure/segmentation/mobile-sam/object-proposals.d.ts +7 -0
- package/dist/infrastructure/segmentation/mobile-sam/object-proposals.js +124 -0
- package/dist/infrastructure/segmentation/mobile-sam/options.d.ts +43 -0
- package/dist/infrastructure/segmentation/mobile-sam/options.js +19 -0
- package/dist/infrastructure/segmentation/mobile-sam/proposal-masks.d.ts +26 -0
- package/dist/infrastructure/segmentation/mobile-sam/proposal-masks.js +71 -0
- package/dist/infrastructure/segmentation/mobile-sam/segmentation-masks.d.ts +112 -0
- package/dist/infrastructure/segmentation/mobile-sam/segmentation-masks.js +230 -0
- package/dist/infrastructure/segmentation/mobile-sam/segmenter.d.ts +22 -0
- package/dist/infrastructure/segmentation/mobile-sam/segmenter.js +268 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-expand.d.ts +7 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-expand.js +188 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-inference.d.ts +6 -149
- package/dist/infrastructure/segmentation/mobile-sam/worker-inference.js +6 -845
- package/dist/infrastructure/segmentation/mobile-sam/worker-models.d.ts +14 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-models.js +72 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-proposals.d.ts +7 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-proposals.js +159 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-segment.d.ts +4 -0
- package/dist/infrastructure/segmentation/mobile-sam/worker-segment.js +126 -0
- package/dist/infrastructure/segmentation/native-protocol.d.ts +59 -34
- package/dist/infrastructure/segmentation/native-protocol.js +26 -16
- package/dist/infrastructure/segmentation/proposal-presentation-input.d.ts +20 -0
- package/dist/infrastructure/segmentation/proposal-presentation-input.js +107 -0
- package/dist/infrastructure/segmentation/proposal-presentation-pixels.d.ts +8 -0
- package/dist/infrastructure/segmentation/proposal-presentation-pixels.js +93 -0
- package/dist/infrastructure/segmentation/proposal-presentation.js +10 -201
- package/dist/infrastructure/vision-chat/options.d.ts +62 -0
- package/dist/infrastructure/vision-chat/options.js +46 -0
- package/dist/infrastructure/vision-chat/request.d.ts +23 -0
- package/dist/infrastructure/vision-chat/request.js +109 -0
- package/dist/infrastructure/vision-chat/response.d.ts +3 -0
- package/dist/infrastructure/vision-chat/response.js +105 -0
- package/dist/infrastructure/vision-chat.d.ts +4 -61
- package/dist/infrastructure/vision-chat.js +8 -271
- package/dist/openai-compatible.js +1 -1
- package/dist/processes/extract-from-region.js +11 -216
- package/dist/processes/extract.d.ts +2 -8
- package/dist/processes/extract.js +12 -128
- package/dist/processes/proposal-location.js +5 -251
- package/docs/README.ru.md +15 -12
- package/docs/agents.ru.md +95 -0
- package/docs/architecture.md +31 -5
- package/docs/architecture.ru.md +146 -0
- package/docs/benchmarks.md +3 -1
- package/docs/development.ru.md +141 -0
- package/docs/provenance.json +5 -1
- package/docs/publishing.md +5 -3
- package/docs/refactoring-plan.ru.md +183 -0
- package/package.json +4 -2
- package/dist/infrastructure/boundary-view.d.ts +0 -2
- package/dist/infrastructure/boundary-view.js +0 -263
- package/dist/infrastructure/openrouter-common.js +0 -317
- package/dist/infrastructure/text-representation.d.ts +0 -23
- package/dist/infrastructure/text-representation.js +0 -226
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Provider-independent text/image classification for Node.js and TypeScript, with
|
|
|
6
6
|
|
|
7
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).
|
|
8
8
|
|
|
9
|
-
[Русское руководство](docs/README.ru.md) · [API](docs/api.md) · [Architecture](docs/architecture.md) · [Benchmarks](docs/benchmarks.md)
|
|
9
|
+
[Русское руководство](docs/README.ru.md) · [API](docs/api.md) · [Architecture](docs/architecture.md) · [Архитектура (RU)](docs/architecture.ru.md) · [Разработка (RU)](docs/development.ru.md) · [Benchmarks](docs/benchmarks.md)
|
|
10
10
|
|
|
11
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
12
|
|
|
@@ -22,7 +22,7 @@ Node.js 22.12+ is required. Source: [zoomplus/zorya-classifier](https://github.c
|
|
|
22
22
|
npm install zorya-classifier
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
The release archive also installs with `npm install ./zorya-classifier-0.
|
|
25
|
+
The release archive also installs with `npm install ./zorya-classifier-0.2.0.tgz` (filename follows `package.json` version; not a claim of a newly published release). 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
26
|
|
|
27
27
|
## Capability and status
|
|
28
28
|
|
|
@@ -135,7 +135,7 @@ The locator uses up to three sequential calls: coarse 4×4 regions, fine grids i
|
|
|
135
135
|
|
|
136
136
|
## Extract a mask-derived object (experimental)
|
|
137
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
|
|
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 only. After segmentation, the caller's decision port checks the full source with the mask-derived box and original seed anchor: `complete` yields the result (optional cutout); `needs-expansion` allows at most one enclosing-mask expansion and one final check (a final `complete` can still produce the result); unresolved checks, missing expansion support, no valid enclosing mask, or a failed final check yield `unknown` with no second expansion. Crops, resize and padding preserve source/mask alignment; box-check rendering uses injected `RasterPort.imageState` on the full canvas. 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
139
|
|
|
140
140
|
## Development and limits
|
|
141
141
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ObjectProposalPort } from '../entities/object-proposals.js';
|
|
3
|
+
import type { SegmentationPort } from '../entities/segmentation.js';
|
|
4
|
+
import type { ClassifierOptions } from './classifier-contracts.js';
|
|
5
|
+
declare const configSchema: z.ZodObject<{
|
|
6
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
concurrency: z.ZodDefault<z.ZodNumber>;
|
|
8
|
+
maxQueue: z.ZodDefault<z.ZodNumber>;
|
|
9
|
+
maxInputBytes: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
concurrency: number;
|
|
13
|
+
maxQueue: number;
|
|
14
|
+
maxInputBytes: number;
|
|
15
|
+
}, {
|
|
16
|
+
timeoutMs?: number | undefined;
|
|
17
|
+
concurrency?: number | undefined;
|
|
18
|
+
maxQueue?: number | undefined;
|
|
19
|
+
maxInputBytes?: number | undefined;
|
|
20
|
+
}>;
|
|
21
|
+
export type ClassifierRuntimeConfig = z.infer<typeof configSchema>;
|
|
22
|
+
type ValidatedClassifierPorts = {
|
|
23
|
+
segmenter: SegmentationPort | undefined;
|
|
24
|
+
objectProposals: ObjectProposalPort | undefined;
|
|
25
|
+
};
|
|
26
|
+
export declare function validateClassifierOptions(options: ClassifierOptions): {
|
|
27
|
+
config: ClassifierRuntimeConfig;
|
|
28
|
+
ports: ValidatedClassifierPorts;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { parse } from '../entities/contracts.js';
|
|
3
|
+
import { ClassifierError } from '../entities/errors.js';
|
|
4
|
+
const configSchema = z.object({
|
|
5
|
+
timeoutMs: z.number().int().min(1).max(120000).default(45000),
|
|
6
|
+
concurrency: z.number().int().min(1).max(4).default(2),
|
|
7
|
+
maxQueue: z.number().int().min(0).max(32).default(16),
|
|
8
|
+
maxInputBytes: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(256 * 1024 * 1024)
|
|
13
|
+
.default(64 * 1024 * 1024),
|
|
14
|
+
});
|
|
15
|
+
export function validateClassifierOptions(options) {
|
|
16
|
+
if (!options || typeof options.decision?.decide !== 'function')
|
|
17
|
+
throw new ClassifierError('INVALID_INPUT', 'A DecisionPort is required');
|
|
18
|
+
const segmenter = options.segmentation;
|
|
19
|
+
const objectProposals = options.objectProposals;
|
|
20
|
+
if (segmenter !== undefined &&
|
|
21
|
+
(!segmenter ||
|
|
22
|
+
typeof segmenter.segment !== 'function' ||
|
|
23
|
+
(segmenter.expand !== undefined && typeof segmenter.expand !== 'function') ||
|
|
24
|
+
typeof segmenter.settle !== 'function' ||
|
|
25
|
+
typeof segmenter.dispose !== 'function'))
|
|
26
|
+
throw new ClassifierError('INVALID_INPUT', 'segmentation must implement SegmentationPort');
|
|
27
|
+
if (options.segmentationBoxCheck !== undefined &&
|
|
28
|
+
(!options.segmentationBoxCheck || typeof options.segmentationBoxCheck.check !== 'function'))
|
|
29
|
+
throw new ClassifierError('INVALID_INPUT', 'segmentationBoxCheck must implement its check port');
|
|
30
|
+
if (objectProposals !== undefined &&
|
|
31
|
+
(!objectProposals ||
|
|
32
|
+
typeof objectProposals.propose !== 'function' ||
|
|
33
|
+
typeof objectProposals.settle !== 'function' ||
|
|
34
|
+
typeof objectProposals.dispose !== 'function'))
|
|
35
|
+
throw new ClassifierError('INVALID_INPUT', 'objectProposals must implement ObjectProposalPort');
|
|
36
|
+
const config = parse(configSchema, {
|
|
37
|
+
timeoutMs: options.timeoutMs,
|
|
38
|
+
concurrency: options.concurrency,
|
|
39
|
+
maxQueue: options.maxQueue,
|
|
40
|
+
maxInputBytes: options.maxInputBytes,
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
config,
|
|
44
|
+
ports: { segmenter, objectProposals },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ClassificationResult, ClassifyInput, DecisionPort, LocalizationResult, LocateInput, RefinementPort } from '../entities/contracts.js';
|
|
2
|
+
import type { ExtractInput, ExtractionResult, SegmentationPort } from '../entities/segmentation.js';
|
|
3
|
+
import type { ObjectProposalPort } from '../entities/object-proposals.js';
|
|
4
|
+
import type { SegmentationBoxCheckPort } from '../entities/segmentation-verification.js';
|
|
5
|
+
import type { AnyLocateInput, WholeObjectLocateInput, WholeObjectLocalizationResult } from '../entities/whole-object.js';
|
|
6
|
+
export interface ClassifierOptions {
|
|
7
|
+
decision: DecisionPort;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
concurrency?: number;
|
|
10
|
+
maxQueue?: number;
|
|
11
|
+
maxInputBytes?: number;
|
|
12
|
+
/** @experimental Optional legacy region refinement; unqualified for general use. */
|
|
13
|
+
refinement?: RefinementPort;
|
|
14
|
+
/** Caller-owned; classifier disposal never disposes a shared segmenter. @experimental Unqualified segmentation adapter. */
|
|
15
|
+
segmentation?: SegmentationPort;
|
|
16
|
+
/** Optional caller-owned containment checker; by default reuses the injected decision port. @experimental Unqualified box-check override. */
|
|
17
|
+
segmentationBoxCheck?: SegmentationBoxCheckPort;
|
|
18
|
+
/** Explicit, caller-owned pixel-mask proposal strategy for whole-object operations. @experimental Unqualified proposal path. */
|
|
19
|
+
objectProposals?: ObjectProposalPort;
|
|
20
|
+
}
|
|
21
|
+
export interface Classifier {
|
|
22
|
+
classify(input: ClassifyInput): Promise<ClassificationResult>;
|
|
23
|
+
/** @experimental Unqualified spatial localization; see {@link LocateInput} and {@link WholeObjectLocateInput}. */
|
|
24
|
+
locate(input: WholeObjectLocateInput): Promise<WholeObjectLocalizationResult>;
|
|
25
|
+
/** @experimental Unqualified coarse region search; returns a hypothesized grid cell. */
|
|
26
|
+
locate(input: LocateInput): Promise<LocalizationResult>;
|
|
27
|
+
/** @experimental Unqualified spatial localization. */
|
|
28
|
+
locate(input: AnyLocateInput): Promise<LocalizationResult | WholeObjectLocalizationResult>;
|
|
29
|
+
/** @experimental Region-seeded extraction; unqualified for general mask or object accuracy. */
|
|
30
|
+
extract(input: ExtractInput): Promise<ExtractionResult>;
|
|
31
|
+
dispose(): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Representation } from '../entities/contracts.js';
|
|
2
|
+
import type { ImageRepresentationPort } from '../entities/representation.js';
|
|
3
|
+
export declare function createClassifierRepresentation(experimental: ImageRepresentationPort | undefined): {
|
|
4
|
+
representationFor(value: Representation | undefined): ImageRepresentationPort | undefined;
|
|
5
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ClassifierError } from '../entities/errors.js';
|
|
2
|
+
import { preparedTextSchema, representationLimits } from '../entities/representation.js';
|
|
3
|
+
import { parse } from '../entities/contracts.js';
|
|
4
|
+
export function createClassifierRepresentation(experimental) {
|
|
5
|
+
const textRepresentation = experimental
|
|
6
|
+
? {
|
|
7
|
+
async prepare(input) {
|
|
8
|
+
const result = parse(preparedTextSchema, await experimental.prepare(input), 'INVALID_RESPONSE');
|
|
9
|
+
const bytes = result.state.reduce((sum, part) => sum + Buffer.byteLength(part, 'utf8'), 0);
|
|
10
|
+
if (bytes > representationLimits.totalBytes || bytes !== result.metadata.bytes)
|
|
11
|
+
throw new ClassifierError('INVALID_RESPONSE', 'Representation byte accounting is inconsistent');
|
|
12
|
+
return {
|
|
13
|
+
state: result.state,
|
|
14
|
+
metadata: {
|
|
15
|
+
...result.metadata,
|
|
16
|
+
views: result.metadata.views.map(({ content, ...view }) => ({
|
|
17
|
+
...view,
|
|
18
|
+
...(content === undefined ? {} : { content }),
|
|
19
|
+
})),
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
: undefined;
|
|
25
|
+
function representationFor(value) {
|
|
26
|
+
if (value?.type !== 'experimental-text')
|
|
27
|
+
return undefined;
|
|
28
|
+
if (!textRepresentation)
|
|
29
|
+
throw new ClassifierError('UNSUPPORTED', 'No text-image profile has passed release qualification yet');
|
|
30
|
+
return textRepresentation;
|
|
31
|
+
}
|
|
32
|
+
return { representationFor };
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClassifierRuntimeConfig } from './classifier-config.js';
|
|
2
|
+
export declare function parseOperationSignal(value: unknown): AbortSignal | undefined;
|
|
3
|
+
export declare function createClassifierRuntime(config: ClassifierRuntimeConfig): {
|
|
4
|
+
dispose: () => Promise<void>;
|
|
5
|
+
assertAcceptingOperations: () => void;
|
|
6
|
+
withOwnedInput: <TSnapshot, TResult>(byteCount: number, parentSignal: AbortSignal | undefined, snapshotFactory: () => TSnapshot, task: (snapshot: TSnapshot, signal: AbortSignal) => Promise<TResult>) => Promise<TResult>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ClassifierError } from '../entities/errors.js';
|
|
2
|
+
import { OperationGate } from '../shared/gate.js';
|
|
3
|
+
import { OperationScope } from '../shared/scope.js';
|
|
4
|
+
import { InputBudget } from '../shared/input-budget.js';
|
|
5
|
+
export function parseOperationSignal(value) {
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!(value instanceof AbortSignal))
|
|
9
|
+
throw new ClassifierError('INVALID_INPUT', 'signal must be an AbortSignal');
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export function createClassifierRuntime(config) {
|
|
13
|
+
const gate = new OperationGate(config.concurrency, config.maxQueue);
|
|
14
|
+
const inputBudget = new InputBudget(config.maxInputBytes);
|
|
15
|
+
const owned = new Map();
|
|
16
|
+
let disposed = false;
|
|
17
|
+
async function run(signal, task) {
|
|
18
|
+
if (disposed)
|
|
19
|
+
throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
|
|
20
|
+
const scope = new OperationScope(config.timeoutMs, signal);
|
|
21
|
+
const job = (async () => {
|
|
22
|
+
let acquired = false;
|
|
23
|
+
try {
|
|
24
|
+
await gate.acquire(scope.signal);
|
|
25
|
+
acquired = true;
|
|
26
|
+
scope.guard();
|
|
27
|
+
const result = await task(scope.signal);
|
|
28
|
+
scope.guard();
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
scope.guard();
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
if (acquired)
|
|
37
|
+
gate.release();
|
|
38
|
+
scope.dispose();
|
|
39
|
+
owned.delete(scope);
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
owned.set(scope, job);
|
|
43
|
+
return job;
|
|
44
|
+
}
|
|
45
|
+
async function dispose() {
|
|
46
|
+
disposed = true;
|
|
47
|
+
for (const scope of owned.keys())
|
|
48
|
+
scope.abort();
|
|
49
|
+
await Promise.allSettled(owned.values());
|
|
50
|
+
}
|
|
51
|
+
function assertAcceptingOperations() {
|
|
52
|
+
if (disposed)
|
|
53
|
+
throw new ClassifierError('DISPOSED', 'Classifier has been disposed');
|
|
54
|
+
gate.assertAvailable();
|
|
55
|
+
}
|
|
56
|
+
async function withOwnedInput(byteCount, parentSignal, snapshotFactory, task) {
|
|
57
|
+
const release = inputBudget.reserve(byteCount);
|
|
58
|
+
try {
|
|
59
|
+
const snapshot = snapshotFactory();
|
|
60
|
+
return await run(parentSignal, (signal) => task(snapshot, signal));
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
release();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
dispose,
|
|
68
|
+
assertAcceptingOperations,
|
|
69
|
+
withOwnedInput,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DecisionPort, RasterPort } from '../entities/contracts.js';
|
|
2
|
+
import type { CutoutRasterPort } from '../entities/cutout-raster.js';
|
|
3
|
+
import type { BoundaryViewPort } from '../entities/boundary-view.js';
|
|
4
|
+
import type { ObjectProposalPort, ObjectProposalPresentationPort } from '../entities/object-proposals.js';
|
|
5
|
+
import type { ImageRepresentationPort } from '../entities/representation.js';
|
|
6
|
+
import type { ExtractInput, ExtractionResult, SegmentationPort } from '../entities/segmentation.js';
|
|
7
|
+
import type { SegmentationBoxCheckPort } from '../entities/segmentation-verification.js';
|
|
8
|
+
import type { WholeObjectLocateInput, WholeObjectLocalizationResult } from '../entities/whole-object.js';
|
|
9
|
+
import type { WholeObjectIntervalPolicy } from '../processes/whole-object.js';
|
|
10
|
+
type ClassifierSpatialDeps = {
|
|
11
|
+
getDecision: () => DecisionPort;
|
|
12
|
+
raster: RasterPort;
|
|
13
|
+
cutoutRaster: CutoutRasterPort;
|
|
14
|
+
segmenter: SegmentationPort | undefined;
|
|
15
|
+
boxCheck: SegmentationBoxCheckPort | undefined;
|
|
16
|
+
proposalDependencies: {
|
|
17
|
+
decision: DecisionPort;
|
|
18
|
+
raster: RasterPort;
|
|
19
|
+
image: CutoutRasterPort;
|
|
20
|
+
proposals: ObjectProposalPort;
|
|
21
|
+
presentation: ObjectProposalPresentationPort;
|
|
22
|
+
} | undefined;
|
|
23
|
+
boundaryView: BoundaryViewPort | undefined;
|
|
24
|
+
intervalPolicy: WholeObjectIntervalPolicy | undefined;
|
|
25
|
+
};
|
|
26
|
+
export declare function createClassifierSpatial(deps: ClassifierSpatialDeps): {
|
|
27
|
+
locateWhole: (input: Omit<WholeObjectLocateInput, "signal">, signal: AbortSignal, representation: ImageRepresentationPort | undefined) => Promise<WholeObjectLocalizationResult>;
|
|
28
|
+
extractRegion: (input: Omit<ExtractInput, "signal">, signal: AbortSignal, representation: ImageRepresentationPort | undefined) => Promise<ExtractionResult>;
|
|
29
|
+
extractWithProposals: (snapshot: Omit<ExtractInput, "signal">, signal: AbortSignal, representation: ImageRepresentationPort | undefined) => Promise<ExtractionResult>;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ClassifierError } from '../entities/errors.js';
|
|
2
|
+
import { locateWholeObjectInput } from '../processes/whole-object.js';
|
|
3
|
+
import { locateInput } from '../processes/locate.js';
|
|
4
|
+
import { extractFromRegion } from '../processes/extract-from-region.js';
|
|
5
|
+
import { extractObject } from '../processes/extract.js';
|
|
6
|
+
import { locateFromProposals } from '../processes/proposal-location.js';
|
|
7
|
+
export function createClassifierSpatial(deps) {
|
|
8
|
+
const { getDecision, raster, cutoutRaster, segmenter, boxCheck, proposalDependencies, boundaryView, intervalPolicy, } = deps;
|
|
9
|
+
function extractRegion(input, signal, representation) {
|
|
10
|
+
if (!segmenter || !boxCheck)
|
|
11
|
+
throw new ClassifierError('UNSUPPORTED', 'Region extraction requires a SegmentationPort');
|
|
12
|
+
if (representation)
|
|
13
|
+
throw new ClassifierError('UNSUPPORTED', 'Checked segmentation requires vision input');
|
|
14
|
+
return extractFromRegion({ ...input, signal }, {
|
|
15
|
+
raster,
|
|
16
|
+
image: cutoutRaster,
|
|
17
|
+
segmenter,
|
|
18
|
+
boxCheck,
|
|
19
|
+
locateRegion: (source, { signal: operationSignal, ...request }) => locateInput({ ...request, image: source.bytes }, getDecision(), raster, operationSignal, undefined, representation),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function locateWhole(input, signal, representation) {
|
|
23
|
+
if (segmenter) {
|
|
24
|
+
const result = await extractRegion(input, signal, representation);
|
|
25
|
+
if (result.status === 'extracted')
|
|
26
|
+
return result.location;
|
|
27
|
+
if (result.status === 'unknown' && result.reason === 'segmentation-uncertain')
|
|
28
|
+
return {
|
|
29
|
+
status: 'unknown',
|
|
30
|
+
reason: 'segmentation-uncertain',
|
|
31
|
+
metadata: 'metadata' in result ? result.metadata : result.location.metadata,
|
|
32
|
+
...(result.verification ? { verification: result.verification } : {}),
|
|
33
|
+
};
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
if (proposalDependencies) {
|
|
37
|
+
if (representation)
|
|
38
|
+
throw new ClassifierError('UNSUPPORTED', 'Mask proposal selection requires vision input');
|
|
39
|
+
return (await locateFromProposals(input, proposalDependencies, signal)).location;
|
|
40
|
+
}
|
|
41
|
+
return locateWholeObjectInput(input, getDecision(), raster, signal, representation, boundaryView, intervalPolicy);
|
|
42
|
+
}
|
|
43
|
+
function extractWithProposals(snapshot, signal, representation) {
|
|
44
|
+
let prepared;
|
|
45
|
+
const selectedMask = {
|
|
46
|
+
async segment() {
|
|
47
|
+
if (!prepared?.mask || !prepared.segmentation)
|
|
48
|
+
throw new ClassifierError('INVALID_RESPONSE', 'Proposal selection omitted its mask');
|
|
49
|
+
return {
|
|
50
|
+
status: 'segmented',
|
|
51
|
+
mask: prepared.mask,
|
|
52
|
+
metadata: prepared.segmentation,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
async settle() { },
|
|
56
|
+
async dispose() { },
|
|
57
|
+
};
|
|
58
|
+
return extractObject({ ...snapshot, mode: 'whole-object', signal }, {
|
|
59
|
+
raster,
|
|
60
|
+
image: cutoutRaster,
|
|
61
|
+
segmenter: selectedMask,
|
|
62
|
+
locateWhole: async (source, { signal: requestSignal, ...request }) => {
|
|
63
|
+
const wholeInput = {
|
|
64
|
+
...request,
|
|
65
|
+
mode: 'whole-object',
|
|
66
|
+
image: source.bytes,
|
|
67
|
+
};
|
|
68
|
+
if (!proposalDependencies)
|
|
69
|
+
throw new ClassifierError('UNSUPPORTED', 'ObjectProposalPort is required');
|
|
70
|
+
if (representation)
|
|
71
|
+
throw new ClassifierError('UNSUPPORTED', 'Mask proposal selection requires vision input');
|
|
72
|
+
prepared = await locateFromProposals(wholeInput, proposalDependencies, requestSignal);
|
|
73
|
+
return prepared.location;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return { locateWhole, extractRegion, extractWithProposals };
|
|
78
|
+
}
|
|
@@ -14,8 +14,8 @@ declare const schema: z.ZodObject<{
|
|
|
14
14
|
value: string;
|
|
15
15
|
target: string;
|
|
16
16
|
command: "classify-text" | "classify-image" | "locate" | "extract";
|
|
17
|
-
mode?: "region" | "whole-object" | undefined;
|
|
18
17
|
provider?: "cpu" | "dml" | undefined;
|
|
18
|
+
mode?: "region" | "whole-object" | undefined;
|
|
19
19
|
output?: string | undefined;
|
|
20
20
|
backend?: "mobile-sam" | "grabcut" | undefined;
|
|
21
21
|
encoder?: string | undefined;
|
|
@@ -25,8 +25,8 @@ declare const schema: z.ZodObject<{
|
|
|
25
25
|
value: string;
|
|
26
26
|
target: string;
|
|
27
27
|
command: "classify-text" | "classify-image" | "locate" | "extract";
|
|
28
|
-
mode?: "region" | "whole-object" | undefined;
|
|
29
28
|
provider?: "cpu" | "dml" | undefined;
|
|
29
|
+
mode?: "region" | "whole-object" | undefined;
|
|
30
30
|
output?: string | undefined;
|
|
31
31
|
backend?: "mobile-sam" | "grabcut" | undefined;
|
|
32
32
|
encoder?: string | undefined;
|
|
@@ -1,38 +1,8 @@
|
|
|
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
1
|
import type { WholeObjectIntervalPolicy } from '../processes/whole-object.js';
|
|
4
2
|
import type { ImageRepresentationPort } from '../entities/representation.js';
|
|
5
|
-
import type { ExtractInput, ExtractionResult, SegmentationPort } from '../entities/segmentation.js';
|
|
6
3
|
import type { BoundaryViewPort } from '../entities/boundary-view.js';
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
export interface ClassifierOptions {
|
|
10
|
-
decision: DecisionPort;
|
|
11
|
-
timeoutMs?: number;
|
|
12
|
-
concurrency?: number;
|
|
13
|
-
maxQueue?: number;
|
|
14
|
-
maxInputBytes?: number;
|
|
15
|
-
/** @experimental Optional legacy region refinement; unqualified for general use. */
|
|
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;
|
|
23
|
-
}
|
|
24
|
-
export interface Classifier {
|
|
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. */
|
|
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>;
|
|
34
|
-
dispose(): Promise<void>;
|
|
35
|
-
}
|
|
4
|
+
import type { Classifier, ClassifierOptions } from './classifier-contracts.js';
|
|
5
|
+
export type { Classifier, ClassifierOptions } from './classifier-contracts.js';
|
|
36
6
|
export declare function createClassifier(options: ClassifierOptions): Classifier;
|
|
37
|
-
/** Internal
|
|
38
|
-
export declare function
|
|
7
|
+
/** Internal composition seam for benchmark helpers; not re-exported from public entry points. */
|
|
8
|
+
export declare function composeClassifier(options: ClassifierOptions, experimental?: ImageRepresentationPort, boundaryView?: BoundaryViewPort, intervalPolicy?: WholeObjectIntervalPolicy): Classifier;
|