editmamei 0.23.0 → 1.0.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/NOTICES.md +6 -30
- package/README.md +47 -13
- package/dist/api/extendscript/_helpers.js +1 -101
- package/dist/api/photoshop-api.js +23 -16
- package/dist/api/snippet-client.js +38 -2
- package/dist/bin/editmamei-core-darwin-arm64 +0 -0
- package/dist/bin/editmamei-core-darwin-x64 +0 -0
- package/dist/bin/editmamei-core-win-x64.exe +0 -0
- package/dist/core/server.js +60 -184
- package/dist/core/session.js +9 -10
- package/dist/core/tool-registry.js +4 -4
- package/dist/delivery/client.js +50 -1
- package/dist/delivery/provision.js +18 -1
- package/dist/delivery/signing.js +38 -1
- package/dist/delivery/store.js +157 -16
- package/dist/detection/detect-active-doc.js +42 -11
- package/dist/detection/detection-client.js +3 -3
- package/dist/detection/face-detector.js +2 -2
- package/dist/detection/geometry.js +14 -0
- package/dist/detection/landmark-detection-client.js +3 -3
- package/dist/detection/mesh-face.js +2 -3
- package/dist/detection/object-detector.js +2 -2
- package/dist/detection/runtime.js +14 -3
- package/dist/detection/sam-segmenter.js +69 -26
- package/dist/kernel/kernel.js +5 -9
- package/dist/kernel/module-lifecycle.js +187 -0
- package/dist/license/entitlement.js +13 -3
- package/dist/license/store.js +8 -1
- package/dist/modules/ce/index.js +1 -1
- package/dist/perception/facets.js +19 -1
- package/dist/perception/grounding-anchors.js +2 -12
- package/dist/perception/grounding-locate.js +1 -9
- package/dist/perception/grounding-review-crop.js +4 -29
- package/dist/perception/overlay.js +62 -0
- package/dist/perception/pixel-identity.js +41 -0
- package/dist/perception/region-precompute.js +96 -20
- package/dist/perception/scene-model.js +93 -50
- package/dist/perception/select-recipes.js +109 -68
- package/dist/perception/sky-ground-flood.js +0 -5
- package/dist/perception/sky-mask-transfer.js +73 -14
- package/dist/platform/connection.js +60 -43
- package/dist/platform/host-platform.js +16 -0
- package/dist/platform/macos-detector.js +41 -70
- package/dist/platform/macos-runner.js +94 -0
- package/dist/platform/script-queue.js +68 -0
- package/dist/platform/script-result.js +39 -0
- package/dist/platform/windows-detector.js +49 -66
- package/dist/platform/windows-runner.js +84 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/tools/adjustment-tools.js +80 -132
- package/dist/tools/brush-tools.js +4 -15
- package/dist/tools/channel-compose-tools.js +4 -23
- package/dist/tools/detection-tools.js +59 -47
- package/dist/tools/document-tools.js +92 -127
- package/dist/tools/filter-tools.js +490 -731
- package/dist/tools/group-tools.js +75 -156
- package/dist/tools/guide-tools.js +52 -83
- package/dist/tools/history-tools.js +29 -63
- package/dist/tools/image-placement-tools.js +18 -37
- package/dist/tools/image-tools.js +23 -51
- package/dist/tools/layer-ordering-tools.js +2 -9
- package/dist/tools/layer-properties-tools.js +207 -395
- package/dist/tools/layer-tools.js +224 -190
- package/dist/tools/layer-transform-tools.js +41 -106
- package/dist/tools/metadata-tools.js +2 -9
- package/dist/tools/path-tools.js +4 -15
- package/dist/tools/portrait-tools.js +2 -5
- package/dist/tools/preview-tools.js +59 -72
- package/dist/tools/retouch-tools.js +33 -71
- package/dist/tools/scene-tools.js +123 -75
- package/dist/tools/selection-tools.js +386 -566
- package/dist/tools/shape-tools.js +2 -9
- package/dist/tools/text-tools.js +24 -57
- package/dist/tools/transform-canvas-tools.js +21 -54
- package/dist/tools/vector-mask-tools.js +2 -9
- package/dist/update/check.js +1 -1
- package/dist/utils/jpeg-quality.js +1 -0
- package/dist/utils/logger.js +55 -45
- package/dist/utils/operation-timeouts.js +1 -0
- package/dist/utils/run-script.js +7 -2
- package/dist/utils/session-log.js +134 -12
- package/dist/utils/template-storage.js +5 -6
- package/dist/utils/tool-helpers.js +43 -0
- package/dist/version.js +1 -1
- package/package.json +10 -3
- package/LICENSE +0 -81
- package/dist/platform/detector.js +0 -32
- package/dist/platform/macos-executor.js +0 -151
- package/dist/platform/windows-executor.js +0 -160
- /package/dist/platform/{script-executor.js → ports.js} +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { runScript } from '../utils/run-script.js';
|
|
2
|
+
import { SCENE_CHANNEL_TIMEOUT_MS } from '../utils/operation-timeouts.js';
|
|
2
3
|
import { jsLit } from '../utils/jsx.js';
|
|
3
|
-
import { restoreCompositeChannel } from '../api/extendscript/_helpers.js';
|
|
4
|
+
import { restoreCompositeChannel, getSelectionInfo } from '../api/extendscript/_helpers.js';
|
|
5
|
+
import { Logger } from '../utils/logger.js';
|
|
4
6
|
import { resolveSelection, } from './select-recipes.js';
|
|
7
|
+
const logger = new Logger('region-precompute');
|
|
5
8
|
let lastPrecomputedKey = null;
|
|
6
9
|
let cachedMenu = [];
|
|
7
10
|
export function __resetPrecompute() {
|
|
@@ -10,7 +13,7 @@ export function __resetPrecompute() {
|
|
|
10
13
|
}
|
|
11
14
|
export const CHANNEL_PREFIX = 'scene:';
|
|
12
15
|
export async function saveSelectionAsSceneChannel(connection, target) {
|
|
13
|
-
await runScript(connection, saveSelectionToNamedChannelScript(`${CHANNEL_PREFIX}${target}`));
|
|
16
|
+
await runScript(connection, saveSelectionToNamedChannelScript(`${CHANNEL_PREFIX}${target}`), SCENE_CHANNEL_TIMEOUT_MS);
|
|
14
17
|
}
|
|
15
18
|
const PRECOMPUTE_TARGETS = [
|
|
16
19
|
'sky',
|
|
@@ -23,7 +26,7 @@ const PRECOMPUTE_TARGETS = [
|
|
|
23
26
|
];
|
|
24
27
|
function deleteSceneChannelsScript() {
|
|
25
28
|
return `
|
|
26
|
-
if (app.documents.length === 0) { throw new Error('No
|
|
29
|
+
if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
|
|
27
30
|
var doc = app.activeDocument;
|
|
28
31
|
var removed = 0;
|
|
29
32
|
for (var i = doc.channels.length - 1; i >= 0; i--) {
|
|
@@ -36,6 +39,12 @@ function deleteSceneChannelsScript() {
|
|
|
36
39
|
return { removed: removed };
|
|
37
40
|
`;
|
|
38
41
|
}
|
|
42
|
+
export async function purgeSceneChannels(connection) {
|
|
43
|
+
const r = (await runScript(connection, deleteSceneChannelsScript(), SCENE_CHANNEL_TIMEOUT_MS));
|
|
44
|
+
lastPrecomputedKey = null;
|
|
45
|
+
cachedMenu = [];
|
|
46
|
+
return r.removed ?? 0;
|
|
47
|
+
}
|
|
39
48
|
function saveSelectionToNamedChannelScript(name) {
|
|
40
49
|
return `
|
|
41
50
|
var doc = app.activeDocument;
|
|
@@ -50,9 +59,50 @@ function saveSelectionToNamedChannelScript(name) {
|
|
|
50
59
|
return { saved: true, name: ${jsLit(name)} };
|
|
51
60
|
`;
|
|
52
61
|
}
|
|
62
|
+
function channelsExistScript(names) {
|
|
63
|
+
const namesLit = `[${names.map((n) => jsLit(n)).join(', ')}]`;
|
|
64
|
+
return `
|
|
65
|
+
// __mcp_scene_chk__ cheap existence check for previously-saved scene:* channels
|
|
66
|
+
if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
|
|
67
|
+
var doc = app.activeDocument;
|
|
68
|
+
var have = {};
|
|
69
|
+
for (var i = 0; i < doc.channels.length; i++) {
|
|
70
|
+
have[String(doc.channels[i].name)] = true;
|
|
71
|
+
}
|
|
72
|
+
var names = ${namesLit};
|
|
73
|
+
var allPresent = true;
|
|
74
|
+
for (var j = 0; j < names.length; j++) {
|
|
75
|
+
if (!have[names[j]]) { allPresent = false; break; }
|
|
76
|
+
}
|
|
77
|
+
return { all_present: allPresent };
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
async function channelsExist(connection, names) {
|
|
81
|
+
if (names.length === 0)
|
|
82
|
+
return true;
|
|
83
|
+
try {
|
|
84
|
+
const r = (await runScript(connection, channelsExistScript(names)));
|
|
85
|
+
return r.all_present === true;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
89
|
+
logger.debug(`region-precompute: channelsExist check failed (${msg}) — treating as stale, rebuilding`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
53
93
|
function loadNamedChannelScript(name) {
|
|
54
94
|
return `
|
|
55
|
-
|
|
95
|
+
// getSelectionInfo's own source begins with the restoreCompositeChannel
|
|
96
|
+
// definition (see _helpers.ts), so interpolating it here brings BOTH into
|
|
97
|
+
// scope. Interpolating restoreCompositeChannel separately as well emitted a
|
|
98
|
+
// second, byte-identical copy of that function body into every load script —
|
|
99
|
+
// legal (a later function declaration just wins) but pure waste in a string
|
|
100
|
+
// that crosses the COM/AppleScript boundary on every select_by_reference.
|
|
101
|
+
// Hoisted to the TOP so the restoreCompositeChannel(doc) call below sits
|
|
102
|
+
// after its definition textually, rather than relying on hoisting across an
|
|
103
|
+
// interpolation boundary.
|
|
104
|
+
${getSelectionInfo}
|
|
105
|
+
if (app.documents.length === 0) { throw new Error('No document is open in Photoshop'); }
|
|
56
106
|
var doc = app.activeDocument;
|
|
57
107
|
var ch = null;
|
|
58
108
|
for (var i = 0; i < doc.channels.length; i++) {
|
|
@@ -60,29 +110,52 @@ function loadNamedChannelScript(name) {
|
|
|
60
110
|
}
|
|
61
111
|
if (!ch) { return { loaded: false }; }
|
|
62
112
|
doc.selection.load(ch, SelectionType.REPLACE);
|
|
63
|
-
${restoreCompositeChannel}
|
|
64
113
|
restoreCompositeChannel(doc);
|
|
65
|
-
|
|
114
|
+
// Measure in the SAME round trip. The fast path used to return
|
|
115
|
+
// selection_info:null, which forced any caller that wanted to verify the mask
|
|
116
|
+
// into a second PS call — the one thing this path exists to avoid.
|
|
117
|
+
return {
|
|
118
|
+
loaded: true,
|
|
119
|
+
width: doc.width.as('px'),
|
|
120
|
+
height: doc.height.as('px'),
|
|
121
|
+
selection_info: getSelectionInfo()
|
|
122
|
+
};
|
|
66
123
|
`;
|
|
67
124
|
}
|
|
125
|
+
function countingConnection(connection, tally) {
|
|
126
|
+
return new Proxy(connection, {
|
|
127
|
+
get(target, prop, receiver) {
|
|
128
|
+
if (prop === 'executeScript') {
|
|
129
|
+
return (script, timeoutMs) => {
|
|
130
|
+
tally.scripts++;
|
|
131
|
+
return target.executeScript(script, timeoutMs);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return Reflect.get(target, prop, receiver);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
68
138
|
export async function precomputeRegions(connection, snippet, model, composition, opts = {}) {
|
|
69
139
|
if (!opts.force &&
|
|
70
140
|
lastPrecomputedKey !== null &&
|
|
71
|
-
lastPrecomputedKey === model.provenance.cache_key
|
|
141
|
+
lastPrecomputedKey === model.provenance.cache_key &&
|
|
142
|
+
(await channelsExist(connection, cachedMenu.filter((m) => !m.on_demand).map((m) => m.key)))) {
|
|
72
143
|
return cachedMenu;
|
|
73
144
|
}
|
|
74
|
-
|
|
145
|
+
const tally = { scripts: 0 };
|
|
146
|
+
const countedConnection = countingConnection(connection, tally);
|
|
147
|
+
await runScript(countedConnection, deleteSceneChannelsScript(), SCENE_CHANNEL_TIMEOUT_MS);
|
|
75
148
|
const menu = [];
|
|
76
149
|
for (const target of PRECOMPUTE_TARGETS) {
|
|
77
150
|
try {
|
|
78
|
-
const res = await resolveSelection(
|
|
151
|
+
const res = await resolveSelection(countedConnection, snippet, model, target, {
|
|
79
152
|
composition,
|
|
80
153
|
proRefine: opts.proRefine,
|
|
81
154
|
skyCtx: opts.skyCtx,
|
|
82
155
|
});
|
|
83
156
|
if (res.passed) {
|
|
84
157
|
const key = `${CHANNEL_PREFIX}${target}`;
|
|
85
|
-
await runScript(
|
|
158
|
+
await runScript(countedConnection, saveSelectionToNamedChannelScript(key), SCENE_CHANNEL_TIMEOUT_MS);
|
|
86
159
|
menu.push({
|
|
87
160
|
key,
|
|
88
161
|
target,
|
|
@@ -97,26 +170,29 @@ export async function precomputeRegions(connection, snippet, model, composition,
|
|
|
97
170
|
continue;
|
|
98
171
|
}
|
|
99
172
|
}
|
|
100
|
-
if (opts.
|
|
101
|
-
|
|
102
|
-
const faceItems = await opts.precomputeFace();
|
|
103
|
-
menu.push(...faceItems);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
}
|
|
173
|
+
if (opts.faceMenu?.length) {
|
|
174
|
+
menu.push(...opts.faceMenu);
|
|
107
175
|
}
|
|
108
176
|
try {
|
|
109
|
-
await runScript(
|
|
177
|
+
await runScript(countedConnection, await snippet.build('deselect'));
|
|
110
178
|
}
|
|
111
179
|
catch {
|
|
112
180
|
}
|
|
181
|
+
logger.debug(`precompute: ${PRECOMPUTE_TARGETS.length} targets, ${tally.scripts} scripts` +
|
|
182
|
+
` (7 CE targets ≈ 21 scripts before the 2026-07-29 derive+measure merge → 15 after, ` +
|
|
183
|
+
` then 14 once skin short-circuits with no person/face (2026-08-01), ` +
|
|
184
|
+
`on the all-fail path; production pass-paths differ)`);
|
|
113
185
|
lastPrecomputedKey = model.provenance.cache_key;
|
|
114
186
|
cachedMenu = menu;
|
|
115
187
|
return menu;
|
|
116
188
|
}
|
|
117
189
|
export async function loadPrecomputedRegion(connection, target) {
|
|
118
|
-
const r = (await runScript(connection, loadNamedChannelScript(`${CHANNEL_PREFIX}${target}`)));
|
|
190
|
+
const r = (await runScript(connection, loadNamedChannelScript(`${CHANNEL_PREFIX}${target}`), SCENE_CHANNEL_TIMEOUT_MS));
|
|
119
191
|
if (r.loaded !== true)
|
|
120
192
|
return null;
|
|
121
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
width: r.width ?? 0,
|
|
195
|
+
height: r.height ?? 0,
|
|
196
|
+
selection_info: r.selection_info ?? null,
|
|
197
|
+
};
|
|
122
198
|
}
|
|
@@ -1,36 +1,24 @@
|
|
|
1
|
-
import { decode } from 'jpeg-js';
|
|
2
1
|
import { runScript } from '../utils/run-script.js';
|
|
3
2
|
import { getContextInfo } from '../api/extendscript/_helpers.js';
|
|
4
3
|
import { detectActiveDoc } from '../detection/detect-active-doc.js';
|
|
4
|
+
import { computePixelIdentity, docKeyFrom, identityKeyString, samePixelIdentity, } from './pixel-identity.js';
|
|
5
|
+
import { Logger } from '../utils/logger.js';
|
|
5
6
|
import { computeComposition, computeTonalZones, estimateHorizon, pickThresholdLevel, boxArea, toLumaHistogram, } from './facets.js';
|
|
7
|
+
const logger = new Logger('scene-model');
|
|
6
8
|
let cache = null;
|
|
9
|
+
const NO_PIXEL_IDENTITY_COUNTER_WRAP = 1_000_000_000;
|
|
10
|
+
let noPixelIdentityCounter = 0;
|
|
11
|
+
function nextNoPixelIdentityId() {
|
|
12
|
+
noPixelIdentityCounter = (noPixelIdentityCounter + 1) % NO_PIXEL_IDENTITY_COUNTER_WRAP;
|
|
13
|
+
return noPixelIdentityCounter;
|
|
14
|
+
}
|
|
7
15
|
export function __clearSceneCache() {
|
|
8
16
|
cache = null;
|
|
9
17
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (app.documents.length === 0) { throw new Error('No active document'); }
|
|
13
|
-
var doc = app.activeDocument;
|
|
14
|
-
var id = -1;
|
|
15
|
-
try { id = doc.id; } catch (eId) {}
|
|
16
|
-
var hs = -1;
|
|
17
|
-
try { hs = doc.historyStates.length; } catch (eHs) {}
|
|
18
|
-
return { doc_id: id, history_states: hs };
|
|
19
|
-
`;
|
|
20
|
-
const r = (await runScript(connection, script));
|
|
21
|
-
return `${r.doc_id ?? -1}:${r.history_states ?? -1}`;
|
|
22
|
-
}
|
|
23
|
-
export function rowBrightnessProfile(exportBytes, strips = 64) {
|
|
24
|
-
if (!exportBytes || exportBytes.length === 0)
|
|
25
|
-
return undefined;
|
|
26
|
-
let img;
|
|
27
|
-
try {
|
|
28
|
-
img = decode(exportBytes, { useTArray: true, formatAsRGBA: true });
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
18
|
+
export function rowBrightnessProfile(decoded, strips = 64) {
|
|
19
|
+
if (!decoded)
|
|
31
20
|
return undefined;
|
|
32
|
-
}
|
|
33
|
-
const { width: w, height: h, data } = img;
|
|
21
|
+
const { width: w, height: h, data } = decoded;
|
|
34
22
|
if (w <= 0 || h <= 0)
|
|
35
23
|
return undefined;
|
|
36
24
|
const R = Math.max(1, Math.min(strips, h));
|
|
@@ -50,24 +38,55 @@ export function rowBrightnessProfile(exportBytes, strips = 64) {
|
|
|
50
38
|
}
|
|
51
39
|
export async function buildSceneModel(connection, snippet, client, opts = {}) {
|
|
52
40
|
const useCache = opts.useCache ?? true;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const cached = cache.result;
|
|
56
|
-
return {
|
|
57
|
-
...cached,
|
|
58
|
-
model: {
|
|
59
|
-
...cached.model,
|
|
60
|
-
provenance: { ...cached.model.provenance, cached: true },
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
41
|
+
let hit = null;
|
|
42
|
+
let freshIdentity = null;
|
|
64
43
|
const det = await detectActiveDoc(connection, client, {
|
|
65
44
|
faces: true,
|
|
66
45
|
objects: true,
|
|
67
46
|
maxDimension: opts.maxDimension,
|
|
68
47
|
objectThreshold: opts.objectThreshold,
|
|
69
48
|
faceThreshold: opts.faceThreshold,
|
|
70
|
-
|
|
49
|
+
shouldDetect: ({ decoded, context, docWidth, docHeight }) => {
|
|
50
|
+
if (!decoded)
|
|
51
|
+
return true;
|
|
52
|
+
const identity = computePixelIdentity(decoded, docKeyFrom(context, docWidth, docHeight));
|
|
53
|
+
freshIdentity = identity;
|
|
54
|
+
if (useCache && cache && samePixelIdentity(cache.identity, identity)) {
|
|
55
|
+
hit = cache;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
}, opts.detectDeps);
|
|
61
|
+
if (hit) {
|
|
62
|
+
const cached = hit;
|
|
63
|
+
logger.debug(`scene-model: pixel-identity cache HIT (${cached.cacheKey}) — reusing detection + ` +
|
|
64
|
+
`facets, skipping ONNX + histogram + facet rebuild`);
|
|
65
|
+
const model = {
|
|
66
|
+
doc: { width: det.docWidth, height: det.docHeight },
|
|
67
|
+
subjects: cached.subjects,
|
|
68
|
+
faces: cached.faces,
|
|
69
|
+
regions: cached.regions,
|
|
70
|
+
horizon: cached.horizon,
|
|
71
|
+
tonal_zones: cached.tonalZones,
|
|
72
|
+
composition: cached.composition,
|
|
73
|
+
provenance: {
|
|
74
|
+
backends: cached.backends,
|
|
75
|
+
edition: 'ce',
|
|
76
|
+
cache_key: cached.cacheKey,
|
|
77
|
+
cached: true,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
model,
|
|
82
|
+
exportBytes: det.exportBytes,
|
|
83
|
+
decoded: det.decoded,
|
|
84
|
+
context: det.context,
|
|
85
|
+
rawFaces: cached.rawFaces,
|
|
86
|
+
rawObjects: cached.rawObjects,
|
|
87
|
+
exportImage: cached.exportImage,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
71
90
|
const docW = det.docWidth;
|
|
72
91
|
const docH = det.docHeight;
|
|
73
92
|
const objects = det.result.objects ?? [];
|
|
@@ -105,8 +124,11 @@ export async function buildSceneModel(connection, snippet, client, opts = {}) {
|
|
|
105
124
|
const hist = toLumaHistogram(histRaw);
|
|
106
125
|
const pick = pickThresholdLevel(hist);
|
|
107
126
|
const tonalZones = computeTonalZones(hist);
|
|
108
|
-
const
|
|
109
|
-
const
|
|
127
|
+
const allBoxes = objects.map((o) => o.bbox);
|
|
128
|
+
const mainBox = mainIdx >= 0 ? objects[mainIdx].bbox : null;
|
|
129
|
+
const primaryFaceBox = faces.find((f) => f.is_primary)?.bbox ?? faces[0]?.bbox ?? null;
|
|
130
|
+
const rowMeans = rowBrightnessProfile(det.decoded);
|
|
131
|
+
const horizon = estimateHorizon(rowMeans, docH, pick, mainBox, primaryFaceBox);
|
|
110
132
|
const skyCoverage = pick.bright_fraction;
|
|
111
133
|
const regions = [
|
|
112
134
|
{
|
|
@@ -120,9 +142,18 @@ export async function buildSceneModel(connection, snippet, client, opts = {}) {
|
|
|
120
142
|
recipe: { kind: 'posterize_region', levels: 3, sample: 'below_horizon' },
|
|
121
143
|
},
|
|
122
144
|
];
|
|
123
|
-
const allBoxes = objects.map((o) => o.bbox);
|
|
124
|
-
const mainBox = mainIdx >= 0 ? objects[mainIdx].bbox : null;
|
|
125
145
|
const composition = computeComposition(mainBox, allBoxes, docW, docH, horizon.placement);
|
|
146
|
+
const backends = {
|
|
147
|
+
faces: det.result.backends.faces,
|
|
148
|
+
objects: det.result.backends.objects,
|
|
149
|
+
regions: 'heuristic',
|
|
150
|
+
};
|
|
151
|
+
const rawFaces = (det.raw.faces ?? []).map((f) => f.bbox);
|
|
152
|
+
const rawObjects = (det.raw.objects ?? []).map((o) => ({ label: o.label, bbox: o.bbox }));
|
|
153
|
+
const exportImage = det.raw.image;
|
|
154
|
+
const cacheKey = freshIdentity
|
|
155
|
+
? identityKeyString(freshIdentity)
|
|
156
|
+
: `nopixel-${nextNoPixelIdentityId()}`;
|
|
126
157
|
const model = {
|
|
127
158
|
doc: { width: docW, height: docH },
|
|
128
159
|
subjects,
|
|
@@ -132,25 +163,37 @@ export async function buildSceneModel(connection, snippet, client, opts = {}) {
|
|
|
132
163
|
tonal_zones: tonalZones,
|
|
133
164
|
composition,
|
|
134
165
|
provenance: {
|
|
135
|
-
backends
|
|
136
|
-
faces: det.result.backends.faces,
|
|
137
|
-
objects: det.result.backends.objects,
|
|
138
|
-
regions: 'heuristic',
|
|
139
|
-
},
|
|
166
|
+
backends,
|
|
140
167
|
edition: 'ce',
|
|
141
|
-
cache_key:
|
|
168
|
+
cache_key: cacheKey,
|
|
142
169
|
cached: false,
|
|
143
170
|
},
|
|
144
171
|
};
|
|
145
172
|
const result = {
|
|
146
173
|
model,
|
|
147
174
|
exportBytes: det.exportBytes,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
175
|
+
decoded: det.decoded,
|
|
176
|
+
rawFaces,
|
|
177
|
+
rawObjects,
|
|
178
|
+
exportImage,
|
|
151
179
|
context: det.context,
|
|
152
180
|
};
|
|
153
|
-
|
|
181
|
+
if (freshIdentity) {
|
|
182
|
+
cache = {
|
|
183
|
+
identity: freshIdentity,
|
|
184
|
+
cacheKey,
|
|
185
|
+
subjects,
|
|
186
|
+
faces,
|
|
187
|
+
regions,
|
|
188
|
+
horizon,
|
|
189
|
+
tonalZones,
|
|
190
|
+
composition,
|
|
191
|
+
backends,
|
|
192
|
+
rawFaces,
|
|
193
|
+
rawObjects,
|
|
194
|
+
exportImage,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
154
197
|
return result;
|
|
155
198
|
}
|
|
156
199
|
export { getContextInfo };
|