@jax-data-science/sci-image-visualizer 0.2.16 → 0.2.17
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/esm2022/index.mjs +6 -1
- package/esm2022/lib/contracts/semantic-segmenter.contract.mjs +11 -0
- package/esm2022/lib/toolbar/segmentation/retinal-layer-tool.service.mjs +197 -0
- package/esm2022/lib/toolbar/segmentation/retinal-model-registry.mjs +119 -0
- package/esm2022/lib/toolbar/segmentation/retinal-segmenter.service.mjs +157 -0
- package/esm2022/lib/toolbar/toolbar.component.mjs +31 -3
- package/esm2022/lib/visualizer.component.mjs +86 -22
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +579 -18
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/index.d.ts +4 -0
- package/lib/contracts/semantic-segmenter.contract.d.ts +105 -0
- package/lib/toolbar/segmentation/retinal-layer-tool.service.d.ts +49 -0
- package/lib/toolbar/segmentation/retinal-model-registry.d.ts +71 -0
- package/lib/toolbar/segmentation/retinal-segmenter.service.d.ts +49 -0
- package/lib/toolbar/toolbar.component.d.ts +15 -1
- package/lib/visualizer.component.d.ts +38 -2
- package/package.json +2 -1
package/esm2022/index.mjs
CHANGED
|
@@ -26,6 +26,9 @@ export { CellposeSegmenterService } from './lib/toolbar/segmentation/cellpose-se
|
|
|
26
26
|
export * from './lib/contracts/instance-segmenter.contract';
|
|
27
27
|
export { YoloSegmenterService } from './lib/toolbar/segmentation/yolo-segmenter.service';
|
|
28
28
|
export { YoloDetectToolService, YOLO_REGION_SOURCE } from './lib/toolbar/segmentation/yolo-detect-tool.service';
|
|
29
|
+
export * from './lib/contracts/semantic-segmenter.contract';
|
|
30
|
+
export { RetinalSegmenterService } from './lib/toolbar/segmentation/retinal-segmenter.service';
|
|
31
|
+
export { RetinalLayerToolService, RETINAL_REGION_SOURCE, } from './lib/toolbar/segmentation/retinal-layer-tool.service';
|
|
29
32
|
// ── Generic client-side image utilities (shared with the host's pipeline) ──
|
|
30
33
|
export { ProcessingImage } from './lib/processing/processing-image';
|
|
31
34
|
export { cropImage } from './lib/processing/image-crop';
|
|
@@ -34,6 +37,8 @@ export { ImageConverterService } from './lib/processing/image-converter.service'
|
|
|
34
37
|
export { SAM_MODELS, DEFAULT_SAM_MODEL_ID, getSamModel, isSamModelReady, setSamModelUrls, setDefaultSamModel, getDefaultSamModelId, } from './lib/toolbar/segmentation/sam-model-registry';
|
|
35
38
|
// ── YOLO instance-segmentation model registry (same repointing story) ─────
|
|
36
39
|
export { YOLO_MODELS, DEFAULT_YOLO_MODEL_ID, getYoloModel, isYoloModelReady, setYoloModelUrls, setDefaultYoloModel, getDefaultYoloModelId, } from './lib/toolbar/segmentation/yolo-model-registry';
|
|
40
|
+
// ── Retinal-layer semantic-segmentation registry (same repointing story) ──
|
|
41
|
+
export { RETINAL_MODELS, DEFAULT_RETINAL_MODEL_ID, getRetinalModel, isRetinalModelReady, readyRetinalModels, setRetinalModelUrls, setDefaultRetinalModel, getDefaultRetinalModelId, } from './lib/toolbar/segmentation/retinal-model-registry';
|
|
37
42
|
// ── onnxruntime-web WASM location (host overrides once, at app init) ───────
|
|
38
43
|
export { setOrtWasmBase, getOrtWasmBase } from './lib/toolbar/segmentation/ort-runtime-config';
|
|
39
44
|
// ── Neutral data models ──────────────────────────────────────────────────
|
|
@@ -54,4 +59,4 @@ export { VisualizerComponent } from './lib/visualizer.component';
|
|
|
54
59
|
export { RegionEditorComponent } from './lib/region-editor/region-editor.component';
|
|
55
60
|
export { HexColorPickerComponent } from './lib/hex-color-picker/hex-color-picker.component';
|
|
56
61
|
export { ChannelHistogramComponent } from './lib/channel-histogram/channel-histogram.component';
|
|
57
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
62
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBRUgsNEVBQTRFO0FBQzVFLGNBQWMscUNBQXFDLENBQUM7QUFDcEQsY0FBYyw0Q0FBNEMsQ0FBQztBQUMzRCxjQUFjLHdDQUF3QyxDQUFDO0FBQ3ZELGNBQWMsd0NBQXdDLENBQUM7QUFDdkQsY0FBYyxzQ0FBc0MsQ0FBQztBQUNyRCxjQUFjLHdDQUF3QyxDQUFDO0FBQ3ZELGNBQWMsZ0RBQWdELENBQUM7QUFDL0QsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLGdDQUFnQyxDQUFDO0FBQy9DLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLHVDQUF1QyxDQUFDO0FBQ3RELGNBQWMseUNBQXlDLENBQUM7QUFDeEQsY0FBYywrQkFBK0IsQ0FBQztBQUM5QyxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMseUNBQXlDLENBQUM7QUFDeEQsT0FBTyxFQUFFLHdCQUF3QixFQUFFLE1BQU0sdURBQXVELENBQUM7QUFDakcsY0FBYyw2Q0FBNkMsQ0FBQztBQUM1RCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxtREFBbUQsQ0FBQztBQUN6RixPQUFPLEVBQUUscUJBQXFCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxxREFBcUQsQ0FBQztBQUNoSCxjQUFjLDZDQUE2QyxDQUFDO0FBQzVELE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLHNEQUFzRCxDQUFDO0FBQy9GLE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIscUJBQXFCLEdBQ3RCLE1BQU0sdURBQXVELENBQUM7QUFFL0QsOEVBQThFO0FBQzlFLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsU0FBUyxFQUFvQixNQUFNLDZCQUE2QixDQUFDO0FBQzFFLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLDBDQUEwQyxDQUFDO0FBRWpGLDZFQUE2RTtBQUM3RSxPQUFPLEVBQ0wsVUFBVSxFQUFFLG9CQUFvQixFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUMvRSxrQkFBa0IsRUFBRSxvQkFBb0IsR0FDekMsTUFBTSwrQ0FBK0MsQ0FBQztBQUV2RCw2RUFBNkU7QUFDN0UsT0FBTyxFQUNMLFdBQVcsRUFBRSxxQkFBcUIsRUFBRSxZQUFZLEVBQUUsZ0JBQWdCLEVBQUUsZ0JBQWdCLEVBQ3BGLG1CQUFtQixFQUFFLHFCQUFxQixHQUMzQyxNQUFNLGdEQUFnRCxDQUFDO0FBRXhELDZFQUE2RTtBQUM3RSxPQUFPLEVBQ0wsY0FBYyxFQUFFLHdCQUF3QixFQUFFLGVBQWUsRUFBRSxtQkFBbUIsRUFDOUUsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQUUsc0JBQXNCLEVBQUUsd0JBQXdCLEdBRTFGLE1BQU0sbURBQW1ELENBQUM7QUFFM0QsOEVBQThFO0FBQzlFLE9BQU8sRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFFL0YsNEVBQTRFO0FBQzVFLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFcEQsNEVBQTRFO0FBQzVFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQzVFLDZFQUE2RTtBQUM3RSxtRUFBbUU7QUFDbkUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFbkUsNkVBQTZFO0FBQzdFLDBFQUEwRTtBQUMxRSw4RUFBOEU7QUFDOUUsNkRBQTZEO0FBQzdELE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLDZDQUE2QyxDQUFDO0FBQ3BGLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLG1EQUFtRCxDQUFDO0FBQzVGLE9BQU8sRUFBRSx5QkFBeUIsRUFBRSxNQUFNLHFEQUFxRCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBQdWJsaWMgQVBJIG9mIHRoZSBqYXgtaW1hZ2UtdmlzdWFsaXphdGlvbiBsaWJyYXJ5LiBFeHRlcm5hbCBjb25zdW1lcnMgaW1wb3J0XG4gKiBPTkxZIGZyb20gaGVyZSAoYEBqYXgtZGF0YS1zY2llbmNlL3NjaS1pbWFnZS12aXN1YWxpemVyYCk7IGV2ZXJ5dGhpbmcgZWxzZSB1bmRlciBgbGliL2AgaXNcbiAqIGludGVybmFsLiBUaGUgc3VyZmFjZSBpcyBjb250cmFjdHMgKyB0b2tlbnMgKyBuZXV0cmFsIG1vZGVscyArIHRoZSBBbmd1bGFyXG4gKiBtb2R1bGUsIHBsdXMgdGhlIHJvdXRpbmcgc2VydmljZSB0aGUgaG9zdCdzIGNvbXBvc2l0aW9uIHJvb3QgYmluZHMgdGhlIHRva2Vuc1xuICogdG8gKGB1c2VFeGlzdGluZ2ApLlxuICovXG5cbi8vIOKUgOKUgCBDb250cmFjdHMgJiBESSB0b2tlbnMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb250cmFjdHMvdmlzdWFsaXplci5jb250cmFjdCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9jb250cmFjdHMvcmVnaW9uLWVkaXRvci1hcGkuY29udHJhY3QnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL3BvcnRzL2ltYWdlLXN0YXRlLnBvcnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL3BvcnRzL3RpbGUtYWNjZXNzLnBvcnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL3BvcnRzL3JlZ2lvbi1pby5wb3J0JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9wb3J0cy9wcmVmZXJlbmNlcy5wb3J0JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9jaGFubmVsLWhpc3RvZ3JhbS1hcGkuY29udHJhY3QnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL3Zpei1jb25maWcnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL2ltYWdlLmNvbnRyYWN0JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9wbG90LXR5cGUnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL3Rvb2xiYXItY29uZmlnJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9jYXBhYmlsaXRpZXMuY29udHJhY3QnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL3JlZ2lvbi1vdmVybGF5LmNvbnRyYWN0JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9kaXNwbGF5LXR5cGVzJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9zYW0uY29udHJhY3QnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvY29udHJhY3RzL2NlbGwtc2VnbWVudGVyLmNvbnRyYWN0JztcbmV4cG9ydCB7IENlbGxwb3NlU2VnbWVudGVyU2VydmljZSB9IGZyb20gJy4vbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL2NlbGxwb3NlLXNlZ21lbnRlci5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9pbnN0YW5jZS1zZWdtZW50ZXIuY29udHJhY3QnO1xuZXhwb3J0IHsgWW9sb1NlZ21lbnRlclNlcnZpY2UgfSBmcm9tICcuL2xpYi90b29sYmFyL3NlZ21lbnRhdGlvbi95b2xvLXNlZ21lbnRlci5zZXJ2aWNlJztcbmV4cG9ydCB7IFlvbG9EZXRlY3RUb29sU2VydmljZSwgWU9MT19SRUdJT05fU09VUkNFIH0gZnJvbSAnLi9saWIvdG9vbGJhci9zZWdtZW50YXRpb24veW9sby1kZXRlY3QtdG9vbC5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2NvbnRyYWN0cy9zZW1hbnRpYy1zZWdtZW50ZXIuY29udHJhY3QnO1xuZXhwb3J0IHsgUmV0aW5hbFNlZ21lbnRlclNlcnZpY2UgfSBmcm9tICcuL2xpYi90b29sYmFyL3NlZ21lbnRhdGlvbi9yZXRpbmFsLXNlZ21lbnRlci5zZXJ2aWNlJztcbmV4cG9ydCB7XG4gIFJldGluYWxMYXllclRvb2xTZXJ2aWNlLFxuICBSRVRJTkFMX1JFR0lPTl9TT1VSQ0UsXG59IGZyb20gJy4vbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL3JldGluYWwtbGF5ZXItdG9vbC5zZXJ2aWNlJztcblxuLy8g4pSA4pSAIEdlbmVyaWMgY2xpZW50LXNpZGUgaW1hZ2UgdXRpbGl0aWVzIChzaGFyZWQgd2l0aCB0aGUgaG9zdCdzIHBpcGVsaW5lKSDilIDilIBcbmV4cG9ydCB7IFByb2Nlc3NpbmdJbWFnZSB9IGZyb20gJy4vbGliL3Byb2Nlc3NpbmcvcHJvY2Vzc2luZy1pbWFnZSc7XG5leHBvcnQgeyBjcm9wSW1hZ2UsIENyb3BJbWFnZU9wdGlvbnMgfSBmcm9tICcuL2xpYi9wcm9jZXNzaW5nL2ltYWdlLWNyb3AnO1xuZXhwb3J0IHsgSW1hZ2VDb252ZXJ0ZXJTZXJ2aWNlIH0gZnJvbSAnLi9saWIvcHJvY2Vzc2luZy9pbWFnZS1jb252ZXJ0ZXIuc2VydmljZSc7XG5cbi8vIOKUgOKUgCBTQU0gbW9kZWwgcmVnaXN0cnkgKGhvc3QgY29uZmlndXJlcyBob3N0ZWQgT05OWCBVUkxzIG9uY2UgYXZhaWxhYmxlKSDilIDilIBcbmV4cG9ydCB7XG4gIFNBTV9NT0RFTFMsIERFRkFVTFRfU0FNX01PREVMX0lELCBnZXRTYW1Nb2RlbCwgaXNTYW1Nb2RlbFJlYWR5LCBzZXRTYW1Nb2RlbFVybHMsXG4gIHNldERlZmF1bHRTYW1Nb2RlbCwgZ2V0RGVmYXVsdFNhbU1vZGVsSWQsXG59IGZyb20gJy4vbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL3NhbS1tb2RlbC1yZWdpc3RyeSc7XG5cbi8vIOKUgOKUgCBZT0xPIGluc3RhbmNlLXNlZ21lbnRhdGlvbiBtb2RlbCByZWdpc3RyeSAoc2FtZSByZXBvaW50aW5nIHN0b3J5KSDilIDilIDilIDilIDilIBcbmV4cG9ydCB7XG4gIFlPTE9fTU9ERUxTLCBERUZBVUxUX1lPTE9fTU9ERUxfSUQsIGdldFlvbG9Nb2RlbCwgaXNZb2xvTW9kZWxSZWFkeSwgc2V0WW9sb01vZGVsVXJscyxcbiAgc2V0RGVmYXVsdFlvbG9Nb2RlbCwgZ2V0RGVmYXVsdFlvbG9Nb2RlbElkLCBZb2xvTW9kZWxEZWYsXG59IGZyb20gJy4vbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL3lvbG8tbW9kZWwtcmVnaXN0cnknO1xuXG4vLyDilIDilIAgUmV0aW5hbC1sYXllciBzZW1hbnRpYy1zZWdtZW50YXRpb24gcmVnaXN0cnkgKHNhbWUgcmVwb2ludGluZyBzdG9yeSkg4pSA4pSAXG5leHBvcnQge1xuICBSRVRJTkFMX01PREVMUywgREVGQVVMVF9SRVRJTkFMX01PREVMX0lELCBnZXRSZXRpbmFsTW9kZWwsIGlzUmV0aW5hbE1vZGVsUmVhZHksXG4gIHJlYWR5UmV0aW5hbE1vZGVscywgc2V0UmV0aW5hbE1vZGVsVXJscywgc2V0RGVmYXVsdFJldGluYWxNb2RlbCwgZ2V0RGVmYXVsdFJldGluYWxNb2RlbElkLFxuICBSZXRpbmFsTW9kZWxEZWYsXG59IGZyb20gJy4vbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL3JldGluYWwtbW9kZWwtcmVnaXN0cnknO1xuXG4vLyDilIDilIAgb25ueHJ1bnRpbWUtd2ViIFdBU00gbG9jYXRpb24gKGhvc3Qgb3ZlcnJpZGVzIG9uY2UsIGF0IGFwcCBpbml0KSDilIDilIDilIDilIDilIDilIDilIBcbmV4cG9ydCB7IHNldE9ydFdhc21CYXNlLCBnZXRPcnRXYXNtQmFzZSB9IGZyb20gJy4vbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL29ydC1ydW50aW1lLWNvbmZpZyc7XG5cbi8vIOKUgOKUgCBOZXV0cmFsIGRhdGEgbW9kZWxzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuZXhwb3J0ICogZnJvbSAnLi9saWIvbW9kZWxzL3JlZ2lvbic7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9tb2RlbHMvY2xhc3MtcHJlc2V0JztcbmV4cG9ydCB7IFNoYXBlU2VsZWN0aW9uIH0gZnJvbSAnLi9saWIvbW9kZWxzL3NoYXBlJztcblxuLy8g4pSA4pSAIEFuZ3VsYXIgbW9kdWxlICsgY29tcG9zaXRpb24tcm9vdCBzZXJ2aWNlIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuZXhwb3J0IHsgVmlzdWFsaXphdGlvbk1vZHVsZSB9IGZyb20gJy4vbGliL3Zpc3VhbGl6YXRpb24ubW9kdWxlJztcbmV4cG9ydCB7IFJvdXRpbmdWaXN1YWxpemVyU2VydmljZSB9IGZyb20gJy4vbGliL3JvdXRpbmctdmlzdWFsaXplci5zZXJ2aWNlJztcbi8vIFByb3ZpZGVyIGZhY3RvcnkgZm9yIGFuIGlzb2xhdGVkLCBjb21wb25lbnQtc2NvcGVkIHZpZXdlciBpbnN0YW5jZSAoZS5nLiBhXG4vLyBtb2RhbCB0aGF0IG11c3Qgbm90IHNoYXJlIHRoZSBtYWluIHZpZXdlcidzIHJlZ2lvbi9pbWFnZSBzdGF0ZSkuXG5leHBvcnQgeyBwcm92aWRlVmlzdWFsaXphdGlvbiB9IGZyb20gJy4vbGliL3Byb3ZpZGUtdmlzdWFsaXphdGlvbic7XG5cbi8vIOKUgOKUgCBQdWJsaWMgY29tcG9uZW50cyAoZXhwb3J0ZWQgYnkgVmlzdWFsaXphdGlvbk1vZHVsZSkg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4vLyBuZy1wYWNrYWdyIHJlcXVpcmVzIG1vZHVsZS1leHBvcnRlZCBjb21wb25lbnRzIHRvIGJlIHJlYWNoYWJsZSBmcm9tIHRoZVxuLy8gZW50cnkgcG9pbnQgc28gY29uc3VtZXJzIGdldCB0aGVpciB0eXBlczsgdGhlc2UgYXJlIHRoZSBlbWJlZGRhYmxlIGVsZW1lbnRzXG4vLyAoYDx2aXN1YWxpemVyPmAsIGA8cmVnaW9uLWVkaXRvcj5gLCBgPGhleC1jb2xvci1waWNrZXI+YCkuXG5leHBvcnQgeyBWaXN1YWxpemVyQ29tcG9uZW50IH0gZnJvbSAnLi9saWIvdmlzdWFsaXplci5jb21wb25lbnQnO1xuZXhwb3J0IHsgUmVnaW9uRWRpdG9yQ29tcG9uZW50IH0gZnJvbSAnLi9saWIvcmVnaW9uLWVkaXRvci9yZWdpb24tZWRpdG9yLmNvbXBvbmVudCc7XG5leHBvcnQgeyBIZXhDb2xvclBpY2tlckNvbXBvbmVudCB9IGZyb20gJy4vbGliL2hleC1jb2xvci1waWNrZXIvaGV4LWNvbG9yLXBpY2tlci5jb21wb25lbnQnO1xuZXhwb3J0IHsgQ2hhbm5lbEhpc3RvZ3JhbUNvbXBvbmVudCB9IGZyb20gJy4vbGliL2NoYW5uZWwtaGlzdG9ncmFtL2NoYW5uZWwtaGlzdG9ncmFtLmNvbXBvbmVudCc7XG4iXX0=
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { InjectionToken, inject } from '@angular/core';
|
|
2
|
+
import { RetinalSegmenterService } from '../toolbar/segmentation/retinal-segmenter.service';
|
|
3
|
+
/**
|
|
4
|
+
* DI token for the semantic segmenter, defaulting to the in-library
|
|
5
|
+
* jax-ai-js-backed implementation. Hosts override it to supply their own.
|
|
6
|
+
*/
|
|
7
|
+
export const SEMANTIC_SEGMENTER = new InjectionToken('SEMANTIC_SEGMENTER', {
|
|
8
|
+
providedIn: 'root',
|
|
9
|
+
factory: () => inject(RetinalSegmenterService),
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VtYW50aWMtc2VnbWVudGVyLmNvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9jb250cmFjdHMvc2VtYW50aWMtc2VnbWVudGVyLmNvbnRyYWN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXZELE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLG1EQUFtRCxDQUFDO0FBNEc1Rjs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLGNBQWMsQ0FBcUIsb0JBQW9CLEVBQUU7SUFDN0YsVUFBVSxFQUFFLE1BQU07SUFDbEIsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyx1QkFBdUIsQ0FBQztDQUMvQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3Rpb25Ub2tlbiwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbmltcG9ydCB7IFJldGluYWxTZWdtZW50ZXJTZXJ2aWNlIH0gZnJvbSAnLi4vdG9vbGJhci9zZWdtZW50YXRpb24vcmV0aW5hbC1zZWdtZW50ZXIuc2VydmljZSc7XG5cbi8qKlxuICogUG9ydCBmb3IgYSAqKnNlbWFudGljKiogc2VnbWVudGVyIOKAlCBvbmUgdGhhdCBhc3NpZ25zIGV2ZXJ5IHBpeGVsIGEgY2xhc3MgZnJvbVxuICogYSBmaXhlZCBzZXQsIHdpdGggbm8gbm90aW9uIG9mIHNlcGFyYXRlIG9iamVjdHMuXG4gKlxuICogVGhlIHRoaXJkIHNpYmxpbmcgb2Yge0BsaW5rIElDZWxsU2VnbWVudGVyfSBhbmQge0BsaW5rIElJbnN0YW5jZVNlZ21lbnRlcn0sXG4gKiBhbmQgZ2VudWluZWx5IGEgZGlmZmVyZW50IHNoYXBlIGZyb20gYm90aC4gQ2VsbHBvc2Ugc2VwYXJhdGVzIHRvdWNoaW5nXG4gKiBpbnN0YW5jZXMgb2Ygb25lIGNsYXNzOyBZT0xPIHJldHVybnMgb3ZlcmxhcHBpbmcgY2xhc3NpZmllZCBvYmplY3RzOyB0aGlzXG4gKiByZXR1cm5zICpsYXllcnMqIOKAlCByZWdpb25zIHRoYXQgdGlsZSB0aGUgaW1hZ2UsIG5ldmVyIG92ZXJsYXAsIGFuZCB3aGVyZSB0d29cbiAqIGRpc2Nvbm5lY3RlZCBwYXRjaGVzIG9mIHRoZSBzYW1lIGNsYXNzIGFyZSB0aGUgc2FtZSBmaW5kaW5nIHJhdGhlciB0aGFuIHR3b1xuICogb2JqZWN0cy4gVGhlcmUgaXMgbm8gY29uZmlkZW5jZSBwZXIgb2JqZWN0IGFuZCBubyBOTVMsIGJlY2F1c2UgdGhlcmUgYXJlIG5vXG4gKiBvYmplY3RzLlxuICpcbiAqIFJlc3VsdHMgYXJlIGRlbGl2ZXJlZCBhcyBwb2x5Z29uIHJpbmdzIGluICoqaW1hZ2UgcGl4ZWxzKiosIHNvIGEgY2FsbGVyIGNhblxuICogYnVpbGQge0BsaW5rIFJlZ2lvbn1zIGRpcmVjdGx5IOKAlCB0aGUgc2FtZSBjdXJyZW5jeSB0aGUgb3RoZXIgdHdvIHBvcnRzIHVzZS5cbiAqL1xuXG4vKiogQSBjbG9zZWQgcmluZzsgdGhlIGxhc3QgcG9pbnQgaXMgTk9UIGEgcmVwZWF0IG9mIHRoZSBmaXJzdC4gKi9cbmV4cG9ydCB0eXBlIFNlbWFudGljUmluZyA9IEFycmF5PFtudW1iZXIsIG51bWJlcl0+O1xuXG4vKiogT25lIGNvbm5lY3RlZCBjb21wb25lbnQgb2Ygb25lIGNsYXNzLCB3aXRoIGFueSBpbnRlcmlvciB2b2lkcy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VtYW50aWNSZWdpb24ge1xuICBleHRlcmlvcjogU2VtYW50aWNSaW5nO1xuICBob2xlczogU2VtYW50aWNSaW5nW107XG4gIGNsYXNzSWQ6IG51bWJlcjtcbiAgY2xhc3NOYW1lOiBzdHJpbmc7XG4gIC8qKiBBcmVhIGluIHBpeGVscywgaG9sZXMgYWxyZWFkeSBzdWJ0cmFjdGVkLiAqL1xuICBhcmVhOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2VtYW50aWNTZWdtZW50YXRpb24ge1xuICByZWdpb25zOiBTZW1hbnRpY1JlZ2lvbltdO1xuICAvKiogVG90YWwgcGl4ZWxzIGFzc2lnbmVkIHRvIGVhY2ggY2xhc3MsIGluZGV4ZWQgYnkgY2xhc3MgaWQuICovXG4gIGNsYXNzQXJlYXM6IG51bWJlcltdO1xuICAvKiogQ2xhc3MgbmFtZXMgaW5kZXhlZCBieSBjbGFzcyBpZCwgZnJvbSB0aGUgbW9kZWwncyBvd24gbWV0YWRhdGEuICovXG4gIGNsYXNzTmFtZXM6IHN0cmluZ1tdO1xuICB3aWR0aDogbnVtYmVyO1xuICBoZWlnaHQ6IG51bWJlcjtcbiAgLyoqXG4gICAqIEZyYWN0aW9uIG9mIHBpeGVscyB0aGF0IG1ldCBubyBjbGFzcydzIHRocmVzaG9sZC5cbiAgICpcbiAgICogV29ydGggc3VyZmFjaW5nIHJhdGhlciB0aGFuIGRpc2NhcmRpbmc6IHRoZXNlIG1vZGVscyBhcmUgdGhyZXNob2xkZWQgcGVyXG4gICAqIGNsYXNzIHJhdGhlciB0aGFuIGFyZ21heGVkLCBzbyBhIGhpZ2ggdmFsdWUgbWVhbnMgdGhlIG1vZGVsIHdhcyB1bnN1cmVcbiAgICogZXZlcnl3aGVyZSDigJQgd2hpY2ggaXMgdGhlIHNpZ25hdHVyZSBvZiB3cm9uZyBwcmVwcm9jZXNzaW5nICh3cm9uZyBpbnB1dFxuICAgKiBzY2FsZSwgd3JvbmcgZ3JleXNjYWxlKSBsb25nIGJlZm9yZSB0aGUgcG9seWdvbnMgbG9vayBvYnZpb3VzbHkgd3JvbmcuXG4gICAqL1xuICB1bmFzc2lnbmVkRnJhY3Rpb246IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTZW1hbnRpY1NlZ21lbnRPcHRpb25zIHtcbiAgLyoqIFdoaWNoIHJlZ2lzdGVyZWQgbW9kZWwgdG8gcnVuLiBEZWZhdWx0cyB0byB0aGUgcmVnaXN0cnkncyBkZWZhdWx0LiAqL1xuICBtb2RlbElkPzogc3RyaW5nO1xuICAvKipcbiAgICogU2NhbGUgdG8gcnVuIGF0LCBhcyBhIGRpdmlzb3Igb2YgZnVsbCByZXNvbHV0aW9uIOKAlCB0aGUgc2FtZSBtZWFuaW5nIHRoZVxuICAgKiBzZXJ2ZXIgZ2l2ZXMgaXQuXG4gICAqXG4gICAqIE1hdHRlcnMgYXMgbXVjaCBoZXJlIGFzIGZvciBhIGRldGVjdG9yOiB0aGVzZSBjaGVja3BvaW50cyB3ZXJlIHRyYWluZWQgb25cbiAgICogNDB4IG9yIDIweCBwYXRjaGVzLCBhbmQgYSBsYXllciBpbWFnZWQgYXQgdGhlIHdyb25nIHNjYWxlIGRvZXMgbm90IGxvb2tcbiAgICogbGlrZSB0aGUgbGF5ZXIgdGhlIG1vZGVsIGxlYXJuZWQuXG4gICAqL1xuICBkb3duc2FtcGxpbmdGYWN0b3I/OiBudW1iZXI7XG4gIC8qKlxuICAgKiBNaW5pbXVtIHByb2JhYmlsaXR5IGZvciBhIHBpeGVsIHRvIGJlIGFzc2lnbmVkIHRvIGEgY2xhc3MsIGRlZmF1bHQgMC41LlxuICAgKlxuICAgKiBOb3QgYSBwbGFpbiBhcmdtYXguIFRoZSByZWZlcmVuY2Ugd29ya2VyIHRocmVzaG9sZHMgZWFjaCBjbGFzcyBjaGFubmVsXG4gICAqIGluZGVwZW5kZW50bHkgYXQgMC41OyBiZWNhdXNlIHRoZSBtb2RlbCdzIGxhc3QgbGF5ZXIgaXMgYSBzb2Z0bWF4IGF0IG1vc3RcbiAgICogb25lIGNsYXNzIGNhbiBjbGVhciB0aGF0LCBzbyB0aGlzIGlzIGFyZ21heCAqcGx1cyBhIGNvbmZpZGVuY2UgZmxvb3IqIOKAlFxuICAgKiBwaXhlbHMgd2hlcmUgdGhlIHdpbm5lciBpcyB3ZWFrIGFyZSBsZWZ0IHVuYXNzaWduZWQgcmF0aGVyIHRoYW4gZm9yY2VkIGludG9cbiAgICogdGhlIGJlc3Qgb2YgYSBiYWQgc2V0LiBMb3dlcmluZyBpdCB0b3dhcmQgMC4yNSBhcHByb2FjaGVzIGEgdHJ1ZSBhcmdtYXggYW5kXG4gICAqIHZpc2libHkgZmF0dGVucyBldmVyeSBsYXllci5cbiAgICovXG4gIGNsYXNzVGhyZXNob2xkPzogbnVtYmVyO1xuICAvKiogS2VlcCBvbmx5IHRoZXNlIGNsYXNzIGlkczsgb21pdCBmb3IgZXZlcnl0aGluZyBleGNlcHQgYmFja2dyb3VuZC4gKi9cbiAgY2xhc3NGaWx0ZXI/OiBudW1iZXJbXTtcbiAgLyoqIERvdWdsYXMtUGV1Y2tlciB0b2xlcmFuY2UgaW4gcGl4ZWxzIGZvciBvdXRsaW5lIHNpbXBsaWZpY2F0aW9uOyAwIGRpc2FibGVzLiAqL1xuICBzaW1wbGlmeVRvbGVyYW5jZT86IG51bWJlcjtcbiAgLyoqIERyb3AgcmVnaW9ucyB3aG9zZSBhcmVhIGlzIGJlbG93IHRoaXMsIGluIHNxdWFyZSBwaXhlbHMuICovXG4gIG1pbkFyZWE/OiBudW1iZXI7XG4gIC8qKlxuICAgKiBDb29wZXJhdGl2ZSBjYW5jZWxsYXRpb24sIGNoZWNrZWQgYXQgcGF0Y2ggYm91bmRhcmllcyDigJQgc28gaXQgdGFrZXMgZWZmZWN0XG4gICAqIHdpdGhpbiByb3VnaGx5IG9uZSBwYXRjaCByYXRoZXIgdGhhbiBpbnN0YW50bHkuXG4gICAqL1xuICBzaWduYWw/OiBBYm9ydFNpZ25hbDtcbn1cblxuLyoqIFByb2dyZXNzL3N0YXR1cyBjYWxsYmFja3MgKGFsbCBvcHRpb25hbCkuIE1pcnJvcnMge0BsaW5rIEluc3RhbmNlU2VnbWVudFByb2dyZXNzfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VtYW50aWNTZWdtZW50UHJvZ3Jlc3Mge1xuICAvKiogTW9kZWwtZG93bmxvYWQgcHJvZ3Jlc3MsIDAuLjEgKGZpcnN0IHJ1biBvbmx5KS4gKi9cbiAgb25Qcm9ncmVzcz86IChmcmFjdGlvbjogbnVtYmVyKSA9PiB2b2lkO1xuICAvKipcbiAgICogUmF3IGRvd25sb2FkIGJ5dGUgY291bnRzLCBmb3IgaG9zdHMgdGhhdCByZW5kZXIgYSBzaXplIHJhdGhlciB0aGFuIGEgYmFyZVxuICAgKiBwZXJjZW50YWdlLiBgdG90YWxgIGlzIG51bGwgd2hlbiB0aGUgcmVzcG9uc2UgY2FycmllZCBubyBDb250ZW50LUxlbmd0aC5cbiAgICovXG4gIG9uQnl0ZXM/OiAobG9hZGVkOiBudW1iZXIsIHRvdGFsOiBudW1iZXIgfCBudWxsKSA9PiB2b2lkO1xuICAvKiogSHVtYW4tcmVhZGFibGUgcGhhc2UsIGUuZy4gJ1NlZ21lbnRpbmcgcGF0Y2ggMy844oCmJy4gKi9cbiAgb25TdGF0dXM/OiAoc3RhdHVzOiBzdHJpbmcpID0+IHZvaWQ7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSVNlbWFudGljU2VnbWVudGVyIHtcbiAgLyoqIEFzc2lnbiBldmVyeSBwaXhlbCBhIGNsYXNzIGFuZCByZXR1cm4gdGhlIHJlc3VsdGluZyByZWdpb25zLiAqL1xuICBzZWdtZW50U2VtYW50aWMoXG4gICAgaW1hZ2U6IHsgZGF0YTogVWludDhDbGFtcGVkQXJyYXk7IHdpZHRoOiBudW1iZXI7IGhlaWdodDogbnVtYmVyIH0sXG4gICAgb3B0cz86IFNlbWFudGljU2VnbWVudE9wdGlvbnMsXG4gICAgcHJvZ3Jlc3M/OiBTZW1hbnRpY1NlZ21lbnRQcm9ncmVzcyxcbiAgKTogUHJvbWlzZTxTZW1hbnRpY1NlZ21lbnRhdGlvbj47XG59XG5cbi8qKlxuICogREkgdG9rZW4gZm9yIHRoZSBzZW1hbnRpYyBzZWdtZW50ZXIsIGRlZmF1bHRpbmcgdG8gdGhlIGluLWxpYnJhcnlcbiAqIGpheC1haS1qcy1iYWNrZWQgaW1wbGVtZW50YXRpb24uIEhvc3RzIG92ZXJyaWRlIGl0IHRvIHN1cHBseSB0aGVpciBvd24uXG4gKi9cbmV4cG9ydCBjb25zdCBTRU1BTlRJQ19TRUdNRU5URVIgPSBuZXcgSW5qZWN0aW9uVG9rZW48SVNlbWFudGljU2VnbWVudGVyPignU0VNQU5USUNfU0VHTUVOVEVSJywge1xuICBwcm92aWRlZEluOiAncm9vdCcsXG4gIGZhY3Rvcnk6ICgpID0+IGluamVjdChSZXRpbmFsU2VnbWVudGVyU2VydmljZSksXG59KTtcbiJdfQ==
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Inject, Injectable, Optional } from '@angular/core';
|
|
2
|
+
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
|
3
|
+
import { Region, Polygon } from '../../models/region';
|
|
4
|
+
import { TILE_ACCESS_PORT } from '../../contracts/ports/tile-access.port';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
/** Marks regions this tool produced, so a re-run can replace only its own. */
|
|
7
|
+
export const RETINAL_REGION_SOURCE = 'retinal-layer';
|
|
8
|
+
/**
|
|
9
|
+
* Retinal-layer segmentation over the current view.
|
|
10
|
+
*
|
|
11
|
+
* Sibling of {@link YoloDetectToolService}, and structured the same way: run on
|
|
12
|
+
* whatever is on screen, map the results onto full-image coordinates through
|
|
13
|
+
* the viewer's source rect, and commit them as regions. The difference is what
|
|
14
|
+
* comes back — layers that tile the field rather than discrete objects, so
|
|
15
|
+
* every region is one connected component of one layer and there is no
|
|
16
|
+
* per-object confidence to filter on.
|
|
17
|
+
*
|
|
18
|
+
* Exposes the same `status$` / `busy$` / `progress$` surface as the cellpose and
|
|
19
|
+
* YOLO tools so the host's shared segmentation toast drives it unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export class RetinalLayerToolService {
|
|
22
|
+
constructor(
|
|
23
|
+
/** Server-side crop, so segmentation can run at the model's scale rather
|
|
24
|
+
* than the viewer's. Optional: without it the tool falls back to the
|
|
25
|
+
* displayed pixels, which is correct but scale-dependent. */
|
|
26
|
+
tileAccess = null) {
|
|
27
|
+
this.tileAccess = tileAccess;
|
|
28
|
+
this.status$ = new BehaviorSubject('');
|
|
29
|
+
this.busy$ = new BehaviorSubject(false);
|
|
30
|
+
this.progress$ = new BehaviorSubject(-1);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Segment the current view and commit the layers as regions.
|
|
34
|
+
*
|
|
35
|
+
* @returns the number of regions added.
|
|
36
|
+
*/
|
|
37
|
+
async segmentInView(viz, segmenter, opts = {}) {
|
|
38
|
+
const pixels = viz.getDisplayedPixelData?.();
|
|
39
|
+
if (!pixels || pixels.width === 0 || pixels.height === 0) {
|
|
40
|
+
this.status$.next('No image on screen to segment.');
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
this.busy$.next(true);
|
|
44
|
+
this.progress$.next(-1);
|
|
45
|
+
try {
|
|
46
|
+
const rect = viz.getDisplayedSourceRect?.() ?? null;
|
|
47
|
+
const target = Number(opts.downsamplingFactor ?? 0) || 0;
|
|
48
|
+
const viewFactor = rect && pixels.width > 0 ? rect.width / pixels.width : 1;
|
|
49
|
+
// Re-crop at the model's scale when the view is coarser than it wants.
|
|
50
|
+
// The image is tile-backed, so that detail exists server-side even though
|
|
51
|
+
// the displayed pixels have discarded it.
|
|
52
|
+
let source = { data: pixels.data, width: pixels.width, height: pixels.height };
|
|
53
|
+
if (rect && target > 0 && target < viewFactor * 0.98) {
|
|
54
|
+
this.status$.next('Fetching image at model resolution…');
|
|
55
|
+
const hi = await this.fetchCropAtScale(rect, target);
|
|
56
|
+
if (hi)
|
|
57
|
+
source = hi;
|
|
58
|
+
}
|
|
59
|
+
const result = await segmenter.segmentSemantic(source, opts, {
|
|
60
|
+
onProgress: (f) => this.progress$.next(f),
|
|
61
|
+
onStatus: (s) => this.status$.next(s),
|
|
62
|
+
});
|
|
63
|
+
// View pixels -> full-resolution image pixels. Without this every region
|
|
64
|
+
// would sit at view-local coordinates, i.e. the wrong place on the slide
|
|
65
|
+
// for any zoomed or panned view.
|
|
66
|
+
const offsetX = rect ? rect.x : 0;
|
|
67
|
+
const offsetY = rect ? rect.y : 0;
|
|
68
|
+
// Against the pixels actually inferred on, which may be the re-crop.
|
|
69
|
+
const ratioX = rect && source.width > 0 ? rect.width / source.width : 1;
|
|
70
|
+
const ratioY = rect && source.height > 0 ? rect.height / source.height : 1;
|
|
71
|
+
// View pixels -> image pixels, for any ring.
|
|
72
|
+
const mapRing = (ring) => ring.map(([x, y]) => [Math.round(offsetX + x * ratioX), Math.round(offsetY + y * ratioY)]);
|
|
73
|
+
const added = [];
|
|
74
|
+
for (const r of result.regions) {
|
|
75
|
+
if (r.exterior.length < 3)
|
|
76
|
+
continue;
|
|
77
|
+
const exterior = mapRing(r.exterior);
|
|
78
|
+
added.push(this.makeRegion(exterior.map(([x]) => x), exterior.map(([, y]) => y), r.className,
|
|
79
|
+
// Holes are real findings here, not noise: a layer with a void
|
|
80
|
+
// through it is a donut, and dropping the interior rings fills it
|
|
81
|
+
// in silently. Rings that survive simplification to fewer than 3
|
|
82
|
+
// points cannot bound anything, so they are skipped rather than
|
|
83
|
+
// emitted as degenerate geometry.
|
|
84
|
+
r.holes.filter((h) => h.length >= 3).map(mapRing)));
|
|
85
|
+
}
|
|
86
|
+
if (added.length === 0) {
|
|
87
|
+
// Distinguish "found nothing" from "was unsure everywhere". The second
|
|
88
|
+
// is the signature of wrong preprocessing or the wrong magnification,
|
|
89
|
+
// and reads as a silent no-op unless it is said out loud.
|
|
90
|
+
this.status$.next(result.unassignedFraction > 0.9
|
|
91
|
+
? 'No layers found — the model was unsure across the whole view. ' +
|
|
92
|
+
'Check the magnification matches the model.'
|
|
93
|
+
: 'No layers found.');
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
// Replace this tool's own previous output, keep everything else.
|
|
97
|
+
// Hand-drawn regions and other tools' output carry a different `source`
|
|
98
|
+
// (or none) and are untouched.
|
|
99
|
+
const kept = (viz.getRegions() ?? []).filter((x) => x.source !== RETINAL_REGION_SOURCE);
|
|
100
|
+
viz.setRegions([...kept, ...added]);
|
|
101
|
+
this.status$.next(`Added ${added.length} region(s).`);
|
|
102
|
+
return added.length;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
this.status$.next(err instanceof Error ? err.message : 'Retinal layer segmentation failed.');
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
this.busy$.next(false);
|
|
110
|
+
this.progress$.next(-1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Re-crop `rect` at `1/target` of full resolution. Returns null on any
|
|
115
|
+
* failure, so the caller falls back to the displayed pixels — a coarser crop
|
|
116
|
+
* beats a failed run.
|
|
117
|
+
*/
|
|
118
|
+
async fetchCropAtScale(rect, target) {
|
|
119
|
+
if (!this.tileAccess)
|
|
120
|
+
return null;
|
|
121
|
+
let w = Math.round(rect.width / target);
|
|
122
|
+
let h = Math.round(rect.height / target);
|
|
123
|
+
// A factor of 1 over a whole-slide ROI would ask for hundreds of megapixels.
|
|
124
|
+
const MAX_PIXELS = 16_777_216; // 4096²
|
|
125
|
+
const pixels = w * h;
|
|
126
|
+
if (pixels > MAX_PIXELS) {
|
|
127
|
+
const k = Math.sqrt(MAX_PIXELS / pixels);
|
|
128
|
+
w = Math.round(w * k);
|
|
129
|
+
h = Math.round(h * k);
|
|
130
|
+
}
|
|
131
|
+
if (w < 64 || h < 64)
|
|
132
|
+
return null;
|
|
133
|
+
try {
|
|
134
|
+
const roi = {
|
|
135
|
+
x: Math.round(rect.x),
|
|
136
|
+
y: Math.round(rect.y),
|
|
137
|
+
width: Math.round(rect.width),
|
|
138
|
+
height: Math.round(rect.height),
|
|
139
|
+
};
|
|
140
|
+
const screen = { x: 0, y: 0, width: w, height: h };
|
|
141
|
+
const bytes = await firstValueFrom(this.tileAccess.zoomOnRegion(roi, screen, 0));
|
|
142
|
+
if (!bytes || bytes.byteLength === 0)
|
|
143
|
+
return null;
|
|
144
|
+
const bitmap = await createImageBitmap(new Blob([bytes]));
|
|
145
|
+
const canvas = document.createElement('canvas');
|
|
146
|
+
canvas.width = bitmap.width;
|
|
147
|
+
canvas.height = bitmap.height;
|
|
148
|
+
const ctx = canvas.getContext('2d');
|
|
149
|
+
if (!ctx)
|
|
150
|
+
return null;
|
|
151
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
152
|
+
// Read the dimensions BEFORE close(): closing an ImageBitmap zeroes its
|
|
153
|
+
// width/height, so returning them afterwards yields a 0x0 image with a
|
|
154
|
+
// full pixel buffer — inference then runs on nothing and finds nothing.
|
|
155
|
+
const width = bitmap.width;
|
|
156
|
+
const height = bitmap.height;
|
|
157
|
+
const out = ctx.getImageData(0, 0, width, height);
|
|
158
|
+
bitmap.close?.();
|
|
159
|
+
return { data: out.data, width, height };
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
makeRegion(xs, ys, className, holes = []) {
|
|
166
|
+
const poly = new Polygon();
|
|
167
|
+
poly.npoints = xs.length;
|
|
168
|
+
poly.xpoints = xs;
|
|
169
|
+
poly.ypoints = ys;
|
|
170
|
+
poly.coordinates = xs.map((x, i) => [x, ys[i]]);
|
|
171
|
+
poly.closed = true;
|
|
172
|
+
// Same closed-ring convention as the exterior: no repeated closing point.
|
|
173
|
+
// Left unset when empty so a solid region does not carry an empty array
|
|
174
|
+
// through export and round-tripping.
|
|
175
|
+
if (holes.length)
|
|
176
|
+
poly.holes = holes;
|
|
177
|
+
const region = new Region();
|
|
178
|
+
region.bounds = poly;
|
|
179
|
+
region.source = RETINAL_REGION_SOURCE;
|
|
180
|
+
// The class name drives preset colour resolution; leaving `color` unset lets
|
|
181
|
+
// an existing preset for this layer win.
|
|
182
|
+
region.label = className;
|
|
183
|
+
return region;
|
|
184
|
+
}
|
|
185
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalLayerToolService, deps: [{ token: TILE_ACCESS_PORT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
186
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalLayerToolService, providedIn: 'root' }); }
|
|
187
|
+
}
|
|
188
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalLayerToolService, decorators: [{
|
|
189
|
+
type: Injectable,
|
|
190
|
+
args: [{ providedIn: 'root' }]
|
|
191
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
192
|
+
type: Optional
|
|
193
|
+
}, {
|
|
194
|
+
type: Inject,
|
|
195
|
+
args: [TILE_ACCESS_PORT]
|
|
196
|
+
}] }] });
|
|
197
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0aW5hbC1sYXllci10b29sLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbGliL3Rvb2xiYXIvc2VnbWVudGF0aW9uL3JldGluYWwtbGF5ZXItdG9vbC5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLGNBQWMsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUV2RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBYSxNQUFNLHFCQUFxQixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBdUIsTUFBTSx3Q0FBd0MsQ0FBQzs7QUFPL0YsOEVBQThFO0FBQzlFLE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLGVBQWUsQ0FBQztBQUVyRDs7Ozs7Ozs7Ozs7O0dBWUc7QUFFSCxNQUFNLE9BQU8sdUJBQXVCO0lBS2xDO0lBQ0U7O2tFQUU4RDtJQUNoQixhQUFvQyxJQUFJO1FBQXhDLGVBQVUsR0FBVixVQUFVLENBQThCO1FBUi9FLFlBQU8sR0FBRyxJQUFJLGVBQWUsQ0FBUyxFQUFFLENBQUMsQ0FBQztRQUMxQyxVQUFLLEdBQUcsSUFBSSxlQUFlLENBQVUsS0FBSyxDQUFDLENBQUM7UUFDNUMsY0FBUyxHQUFHLElBQUksZUFBZSxDQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFPbEQsQ0FBQztJQUVKOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsYUFBYSxDQUNqQixHQUFnQixFQUNoQixTQUE2QixFQUM3QixPQUErQixFQUFFO1FBRWpDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxxQkFBcUIsRUFBRSxFQUFFLENBQUM7UUFDN0MsSUFBSSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsS0FBSyxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMs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YmxlLCBPcHRpb25hbCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQmVoYXZpb3JTdWJqZWN0LCBmaXJzdFZhbHVlRnJvbSB9IGZyb20gJ3J4anMnO1xuXG5pbXBvcnQgeyBSZWdpb24sIFBvbHlnb24sIFJlY3RhbmdsZSB9IGZyb20gJy4uLy4uL21vZGVscy9yZWdpb24nO1xuaW1wb3J0IHsgVElMRV9BQ0NFU1NfUE9SVCwgdHlwZSBUaWxlQWNjZXNzUG9ydCB9IGZyb20gJy4uLy4uL2NvbnRyYWN0cy9wb3J0cy90aWxlLWFjY2Vzcy5wb3J0JztcbmltcG9ydCB0eXBlIHsgSVZpc3VhbGl6ZXIgfSBmcm9tICcuLi8uLi9jb250cmFjdHMvdmlzdWFsaXplci5jb250cmFjdCc7XG5pbXBvcnQgdHlwZSB7XG4gIElTZW1hbnRpY1NlZ21lbnRlcixcbiAgU2VtYW50aWNTZWdtZW50T3B0aW9ucyxcbn0gZnJvbSAnLi4vLi4vY29udHJhY3RzL3NlbWFudGljLXNlZ21lbnRlci5jb250cmFjdCc7XG5cbi8qKiBNYXJrcyByZWdpb25zIHRoaXMgdG9vbCBwcm9kdWNlZCwgc28gYSByZS1ydW4gY2FuIHJlcGxhY2Ugb25seSBpdHMgb3duLiAqL1xuZXhwb3J0IGNvbnN0IFJFVElOQUxfUkVHSU9OX1NPVVJDRSA9ICdyZXRpbmFsLWxheWVyJztcblxuLyoqXG4gKiBSZXRpbmFsLWxheWVyIHNlZ21lbnRhdGlvbiBvdmVyIHRoZSBjdXJyZW50IHZpZXcuXG4gKlxuICogU2libGluZyBvZiB7QGxpbmsgWW9sb0RldGVjdFRvb2xTZXJ2aWNlfSwgYW5kIHN0cnVjdHVyZWQgdGhlIHNhbWUgd2F5OiBydW4gb25cbiAqIHdoYXRldmVyIGlzIG9uIHNjcmVlbiwgbWFwIHRoZSByZXN1bHRzIG9udG8gZnVsbC1pbWFnZSBjb29yZGluYXRlcyB0aHJvdWdoXG4gKiB0aGUgdmlld2VyJ3Mgc291cmNlIHJlY3QsIGFuZCBjb21taXQgdGhlbSBhcyByZWdpb25zLiBUaGUgZGlmZmVyZW5jZSBpcyB3aGF0XG4gKiBjb21lcyBiYWNrIOKAlCBsYXllcnMgdGhhdCB0aWxlIHRoZSBmaWVsZCByYXRoZXIgdGhhbiBkaXNjcmV0ZSBvYmplY3RzLCBzb1xuICogZXZlcnkgcmVnaW9uIGlzIG9uZSBjb25uZWN0ZWQgY29tcG9uZW50IG9mIG9uZSBsYXllciBhbmQgdGhlcmUgaXMgbm9cbiAqIHBlci1vYmplY3QgY29uZmlkZW5jZSB0byBmaWx0ZXIgb24uXG4gKlxuICogRXhwb3NlcyB0aGUgc2FtZSBgc3RhdHVzJGAgLyBgYnVzeSRgIC8gYHByb2dyZXNzJGAgc3VyZmFjZSBhcyB0aGUgY2VsbHBvc2UgYW5kXG4gKiBZT0xPIHRvb2xzIHNvIHRoZSBob3N0J3Mgc2hhcmVkIHNlZ21lbnRhdGlvbiB0b2FzdCBkcml2ZXMgaXQgdW5jaGFuZ2VkLlxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFJldGluYWxMYXllclRvb2xTZXJ2aWNlIHtcbiAgcmVhZG9ubHkgc3RhdHVzJCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8c3RyaW5nPignJyk7XG4gIHJlYWRvbmx5IGJ1c3kkID0gbmV3IEJlaGF2aW9yU3ViamVjdDxib29sZWFuPihmYWxzZSk7XG4gIHJlYWRvbmx5IHByb2dyZXNzJCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8bnVtYmVyPigtMSk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgLyoqIFNlcnZlci1zaWRlIGNyb3AsIHNvIHNlZ21lbnRhdGlvbiBjYW4gcnVuIGF0IHRoZSBtb2RlbCdzIHNjYWxlIHJhdGhlclxuICAgICAqICB0aGFuIHRoZSB2aWV3ZXIncy4gT3B0aW9uYWw6IHdpdGhvdXQgaXQgdGhlIHRvb2wgZmFsbHMgYmFjayB0byB0aGVcbiAgICAgKiAgZGlzcGxheWVkIHBpeGVscywgd2hpY2ggaXMgY29ycmVjdCBidXQgc2NhbGUtZGVwZW5kZW50LiAqL1xuICAgIEBPcHRpb25hbCgpIEBJbmplY3QoVElMRV9BQ0NFU1NfUE9SVCkgcHJpdmF0ZSB0aWxlQWNjZXNzOiBUaWxlQWNjZXNzUG9ydCB8IG51bGwgPSBudWxsLFxuICApIHt9XG5cbiAgLyoqXG4gICAqIFNlZ21lbnQgdGhlIGN1cnJlbnQgdmlldyBhbmQgY29tbWl0IHRoZSBsYXllcnMgYXMgcmVnaW9ucy5cbiAgICpcbiAgICogQHJldHVybnMgdGhlIG51bWJlciBvZiByZWdpb25zIGFkZGVkLlxuICAgKi9cbiAgYXN5bmMgc2VnbWVudEluVmlldyhcbiAgICB2aXo6IElWaXN1YWxpemVyLFxuICAgIHNlZ21lbnRlcjogSVNlbWFudGljU2VnbWVudGVyLFxuICAgIG9wdHM6IFNlbWFudGljU2VnbWVudE9wdGlvbnMgPSB7fSxcbiAgKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICBjb25zdCBwaXhlbHMgPSB2aXouZ2V0RGlzcGxheWVkUGl4ZWxEYXRhPy4oKTtcbiAgICBpZiAoIXBpeGVscyB8fCBwaXhlbHMud2lkdGggPT09IDAgfHwgcGl4ZWxzLmhlaWdodCA9PT0gMCkge1xuICAgICAgdGhpcy5zdGF0dXMkLm5leHQoJ05vIGltYWdlIG9uIHNjcmVlbiB0byBzZWdtZW50LicpO1xuICAgICAgcmV0dXJuIDA7XG4gICAgfVxuXG4gICAgdGhpcy5idXN5JC5uZXh0KHRydWUpO1xuICAgIHRoaXMucHJvZ3Jlc3MkLm5leHQoLTEpO1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZWN0ID0gdml6LmdldERpc3BsYXllZFNvdXJjZVJlY3Q/LigpID8/IG51bGw7XG4gICAgICBjb25zdCB0YXJnZXQgPSBOdW1iZXIob3B0cy5kb3duc2FtcGxpbmdGYWN0b3IgPz8gMCkgfHwgMDtcbiAgICAgIGNvbnN0IHZpZXdGYWN0b3IgPSByZWN0ICYmIHBpeGVscy53aWR0aCA+IDAgPyByZWN0LndpZHRoIC8gcGl4ZWxzLndpZHRoIDogMTtcblxuICAgICAgLy8gUmUtY3JvcCBhdCB0aGUgbW9kZWwncyBzY2FsZSB3aGVuIHRoZSB2aWV3IGlzIGNvYXJzZXIgdGhhbiBpdCB3YW50cy5cbiAgICAgIC8vIFRoZSBpbWFnZSBpcyB0aWxlLWJhY2tlZCwgc28gdGhhdCBkZXRhaWwgZXhpc3RzIHNlcnZlci1zaWRlIGV2ZW4gdGhvdWdoXG4gICAgICAvLyB0aGUgZGlzcGxheWVkIHBpeGVscyBoYXZlIGRpc2NhcmRlZCBpdC5cbiAgICAgIGxldCBzb3VyY2UgPSB7IGRhdGE6IHBpeGVscy5kYXRhLCB3aWR0aDogcGl4ZWxzLndpZHRoLCBoZWlnaHQ6IHBpeGVscy5oZWlnaHQgfTtcbiAgICAgIGlmIChyZWN0ICYmIHRhcmdldCA+IDAgJiYgdGFyZ2V0IDwgdmlld0ZhY3RvciAqIDAuOTgpIHtcbiAgICAgICAgdGhpcy5zdGF0dXMkLm5leHQoJ0ZldGNoaW5nIGltYWdlIGF0IG1vZGVsIHJlc29sdXRpb27igKYnKTtcbiAgICAgICAgY29uc3QgaGkgPSBhd2FpdCB0aGlzLmZldGNoQ3JvcEF0U2NhbGUocmVjdCwgdGFyZ2V0KTtcbiAgICAgICAgaWYgKGhpKSBzb3VyY2UgPSBoaTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgc2VnbWVudGVyLnNlZ21lbnRTZW1hbnRpYyhzb3VyY2UsIG9wdHMsIHtcbiAgICAgICAgb25Qcm9ncmVzczogKGYpID0+IHRoaXMucHJvZ3Jlc3MkLm5leHQoZiksXG4gICAgICAgIG9uU3RhdHVzOiAocykgPT4gdGhpcy5zdGF0dXMkLm5leHQocyksXG4gICAgICB9KTtcblxuICAgICAgLy8gVmlldyBwaXhlbHMgLT4gZnVsbC1yZXNvbHV0aW9uIGltYWdlIHBpeGVscy4gV2l0aG91dCB0aGlzIGV2ZXJ5IHJlZ2lvblxuICAgICAgLy8gd291bGQgc2l0IGF0IHZpZXctbG9jYWwgY29vcmRpbmF0ZXMsIGkuZS4gdGhlIHdyb25nIHBsYWNlIG9uIHRoZSBzbGlkZVxuICAgICAgLy8gZm9yIGFueSB6b29tZWQgb3IgcGFubmVkIHZpZXcuXG4gICAgICBjb25zdCBvZmZzZXRYID0gcmVjdCA/IHJlY3QueCA6IDA7XG4gICAgICBjb25zdCBvZmZzZXRZID0gcmVjdCA/IHJlY3QueSA6IDA7XG4gICAgICAvLyBBZ2FpbnN0IHRoZSBwaXhlbHMgYWN0dWFsbHkgaW5mZXJyZWQgb24sIHdoaWNoIG1heSBiZSB0aGUgcmUtY3JvcC5cbiAgICAgIGNvbnN0IHJhdGlvWCA9IHJlY3QgJiYgc291cmNlLndpZHRoID4gMCA/IHJlY3Qud2lkdGggLyBzb3VyY2Uud2lkdGggOiAxO1xuICAgICAgY29uc3QgcmF0aW9ZID0gcmVjdCAmJiBzb3VyY2UuaGVpZ2h0ID4gMCA/IHJlY3QuaGVpZ2h0IC8gc291cmNlLmhlaWdodCA6IDE7XG5cbiAgICAgIC8vIFZpZXcgcGl4ZWxzIC0+IGltYWdlIHBpeGVscywgZm9yIGFueSByaW5nLlxuICAgICAgY29uc3QgbWFwUmluZyA9IChyaW5nOiBBcnJheTxbbnVtYmVyLCBudW1iZXJdPik6IG51bWJlcltdW10gPT5cbiAgICAgICAgcmluZy5tYXAoKFt4LCB5XSkgPT4gW01hdGgucm91bmQob2Zmc2V0WCArIHggKiByYXRpb1gpLCBNYXRoLnJvdW5kKG9mZnNldFkgKyB5ICogcmF0aW9ZKV0pO1xuXG4gICAgICBjb25zdCBhZGRlZDogUmVnaW9uW10gPSBbXTtcbiAgICAgIGZvciAoY29uc3QgciBvZiByZXN1bHQucmVnaW9ucykge1xuICAgICAgICBpZiAoci5leHRlcmlvci5sZW5ndGggPCAzKSBjb250aW51ZTtcbiAgICAgICAgY29uc3QgZXh0ZXJpb3IgPSBtYXBSaW5nKHIuZXh0ZXJpb3IpO1xuICAgICAgICBhZGRlZC5wdXNoKFxuICAgICAgICAgIHRoaXMubWFrZVJlZ2lvbihcbiAgICAgICAgICAgIGV4dGVyaW9yLm1hcCgoW3hdKSA9PiB4KSxcbiAgICAgICAgICAgIGV4dGVyaW9yLm1hcCgoWywgeV0pID0+IHkpLFxuICAgICAgICAgICAgci5jbGFzc05hbWUsXG4gICAgICAgICAgICAvLyBIb2xlcyBhcmUgcmVhbCBmaW5kaW5ncyBoZXJlLCBub3Qgbm9pc2U6IGEgbGF5ZXIgd2l0aCBhIHZvaWRcbiAgICAgICAgICAgIC8vIHRocm91Z2ggaXQgaXMgYSBkb251dCwgYW5kIGRyb3BwaW5nIHRoZSBpbnRlcmlvciByaW5ncyBmaWxscyBpdFxuICAgICAgICAgICAgLy8gaW4gc2lsZW50bHkuIFJpbmdzIHRoYXQgc3Vydml2ZSBzaW1wbGlmaWNhdGlvbiB0byBmZXdlciB0aGFuIDNcbiAgICAgICAgICAgIC8vIHBvaW50cyBjYW5ub3QgYm91bmQgYW55dGhpbmcsIHNvIHRoZXkgYXJlIHNraXBwZWQgcmF0aGVyIHRoYW5cbiAgICAgICAgICAgIC8vIGVtaXR0ZWQgYXMgZGVnZW5lcmF0ZSBnZW9tZXRyeS5cbiAgICAgICAgICAgIHIuaG9sZXMuZmlsdGVyKChoKSA9PiBoLmxlbmd0aCA+PSAzKS5tYXAobWFwUmluZyksXG4gICAgICAgICAgKSxcbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgaWYgKGFkZGVkLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAvLyBEaXN0aW5ndWlzaCBcImZvdW5kIG5vdGhpbmdcIiBmcm9tIFwid2FzIHVuc3VyZSBldmVyeXdoZXJlXCIuIFRoZSBzZWNvbmRcbiAgICAgICAgLy8gaXMgdGhlIHNpZ25hdHVyZSBvZiB3cm9uZyBwcmVwcm9jZXNzaW5nIG9yIHRoZSB3cm9uZyBtYWduaWZpY2F0aW9uLFxuICAgICAgICAvLyBhbmQgcmVhZHMgYXMgYSBzaWxlbnQgbm8tb3AgdW5sZXNzIGl0IGlzIHNhaWQgb3V0IGxvdWQuXG4gICAgICAgIHRoaXMuc3RhdHVzJC5uZXh0KFxuICAgICAgICAgIHJlc3VsdC51bmFzc2lnbmVkRnJhY3Rpb24gPiAwLjlcbiAgICAgICAgICAgID8gJ05vIGxheWVycyBmb3VuZCDigJQgdGhlIG1vZGVsIHdhcyB1bnN1cmUgYWNyb3NzIHRoZSB3aG9sZSB2aWV3LiAnICtcbiAgICAgICAgICAgICAgICAnQ2hlY2sgdGhlIG1hZ25pZmljYXRpb24gbWF0Y2hlcyB0aGUgbW9kZWwuJ1xuICAgICAgICAgICAgOiAnTm8gbGF5ZXJzIGZvdW5kLicsXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiAwO1xuICAgICAgfVxuXG4gICAgICAvLyBSZXBsYWNlIHRoaXMgdG9vbCdzIG93biBwcmV2aW91cyBvdXRwdXQsIGtlZXAgZXZlcnl0aGluZyBlbHNlLlxuICAgICAgLy8gSGFuZC1kcmF3biByZWdpb25zIGFuZCBvdGhlciB0b29scycgb3V0cHV0IGNhcnJ5IGEgZGlmZmVyZW50IGBzb3VyY2VgXG4gICAgICAvLyAob3Igbm9uZSkgYW5kIGFyZSB1bnRvdWNoZWQuXG4gICAgICBjb25zdCBrZXB0ID0gKHZpei5nZXRSZWdpb25zKCkgPz8gW10pLmZpbHRlcigoeCkgPT4geC5zb3VyY2UgIT09IFJFVElOQUxfUkVHSU9OX1NPVVJDRSk7XG4gICAgICB2aXouc2V0UmVnaW9ucyhbLi4ua2VwdCwgLi4uYWRkZWRdKTtcbiAgICAgIHRoaXMuc3RhdHVzJC5uZXh0KGBBZGRlZCAke2FkZGVkLmxlbmd0aH0gcmVnaW9uKHMpLmApO1xuICAgICAgcmV0dXJuIGFkZGVkLmxlbmd0aDtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuc3RhdHVzJC5uZXh0KGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiAnUmV0aW5hbCBsYXllciBzZWdtZW50YXRpb24gZmFpbGVkLicpO1xuICAgICAgcmV0dXJuIDA7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHRoaXMuYnVzeSQubmV4dChmYWxzZSk7XG4gICAgICB0aGlzLnByb2dyZXNzJC5uZXh0KC0xKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmUtY3JvcCBgcmVjdGAgYXQgYDEvdGFyZ2V0YCBvZiBmdWxsIHJlc29sdXRpb24uIFJldHVybnMgbnVsbCBvbiBhbnlcbiAgICogZmFpbHVyZSwgc28gdGhlIGNhbGxlciBmYWxscyBiYWNrIHRvIHRoZSBkaXNwbGF5ZWQgcGl4ZWxzIOKAlCBhIGNvYXJzZXIgY3JvcFxuICAgKiBiZWF0cyBhIGZhaWxlZCBydW4uXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGZldGNoQ3JvcEF0U2NhbGUoXG4gICAgcmVjdDogeyB4OiBudW1iZXI7IHk6IG51bWJlcjsgd2lkdGg6IG51bWJlcjsgaGVpZ2h0OiBudW1iZXIgfSxcbiAgICB0YXJnZXQ6IG51bWJlcixcbiAgKTogUHJvbWlzZTx7IGRhdGE6IFVpbnQ4Q2xhbXBlZEFycmF5OyB3aWR0aDogbnVtYmVyOyBoZWlnaHQ6IG51bWJlciB9IHwgbnVsbD4ge1xuICAgIGlmICghdGhpcy50aWxlQWNjZXNzKSByZXR1cm4gbnVsbDtcbiAgICBsZXQgdyA9IE1hdGgucm91bmQocmVjdC53aWR0aCAvIHRhcmdldCk7XG4gICAgbGV0IGggPSBNYXRoLnJvdW5kKHJlY3QuaGVpZ2h0IC8gdGFyZ2V0KTtcblxuICAgIC8vIEEgZmFjdG9yIG9mIDEgb3ZlciBhIHdob2xlLXNsaWRlIFJPSSB3b3VsZCBhc2sgZm9yIGh1bmRyZWRzIG9mIG1lZ2FwaXhlbHMuXG4gICAgY29uc3QgTUFYX1BJWEVMUyA9IDE2Xzc3N18yMTY7IC8vIDQwOTbCslxuICAgIGNvbnN0IHBpeGVscyA9IHcgKiBoO1xuICAgIGlmIChwaXhlbHMgPiBNQVhfUElYRUxTKSB7XG4gICAgICBjb25zdCBrID0gTWF0aC5zcXJ0KE1BWF9QSVhFTFMgLyBwaXhlbHMpO1xuICAgICAgdyA9IE1hdGgucm91bmQodyAqIGspO1xuICAgICAgaCA9IE1hdGgucm91bmQoaCAqIGspO1xuICAgIH1cbiAgICBpZiAodyA8IDY0IHx8IGggPCA2NCkgcmV0dXJuIG51bGw7XG5cbiAgICB0cnkge1xuICAgICAgY29uc3Qgcm9pID0ge1xuICAgICAgICB4OiBNYXRoLnJvdW5kKHJlY3QueCksXG4gICAgICAgIHk6IE1hdGgucm91bmQocmVjdC55KSxcbiAgICAgICAgd2lkdGg6IE1hdGgucm91bmQocmVjdC53aWR0aCksXG4gICAgICAgIGhlaWdodDogTWF0aC5yb3VuZChyZWN0LmhlaWdodCksXG4gICAgICB9IGFzIFJlY3RhbmdsZTtcbiAgICAgIGNvbnN0IHNjcmVlbiA9IHsgeDogMCwgeTogMCwgd2lkdGg6IHcsIGhlaWdodDogaCB9IGFzIFJlY3RhbmdsZTtcbiAgICAgIGNvbnN0IGJ5dGVzID0gYXdhaXQgZmlyc3RWYWx1ZUZyb20odGhpcy50aWxlQWNjZXNzLnpvb21PblJlZ2lvbihyb2ksIHNjcmVlbiwgMCkpO1xuICAgICAgaWYgKCFieXRlcyB8fCBieXRlcy5ieXRlTGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbDtcblxuICAgICAgY29uc3QgYml0bWFwID0gYXdhaXQgY3JlYXRlSW1hZ2VCaXRtYXAobmV3IEJsb2IoW2J5dGVzXSkpO1xuICAgICAgY29uc3QgY2FudmFzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnY2FudmFzJyk7XG4gICAgICBjYW52YXMud2lkdGggPSBiaXRtYXAud2lkdGg7XG4gICAgICBjYW52YXMuaGVpZ2h0ID0gYml0bWFwLmhlaWdodDtcbiAgICAgIGNvbnN0IGN0eCA9IGNhbnZhcy5nZXRDb250ZXh0KCcyZCcpO1xuICAgICAgaWYgKCFjdHgpIHJldHVybiBudWxsO1xuICAgICAgY3R4LmRyYXdJbWFnZShiaXRtYXAsIDAsIDApO1xuICAgICAgLy8gUmVhZCB0aGUgZGltZW5zaW9ucyBCRUZPUkUgY2xvc2UoKTogY2xvc2luZyBhbiBJbWFnZUJpdG1hcCB6ZXJvZXMgaXRzXG4gICAgICAvLyB3aWR0aC9oZWlnaHQsIHNvIHJldHVybmluZyB0aGVtIGFmdGVyd2FyZHMgeWllbGRzIGEgMHgwIGltYWdlIHdpdGggYVxuICAgICAgLy8gZnVsbCBwaXhlbCBidWZmZXIg4oCUIGluZmVyZW5jZSB0aGVuIHJ1bnMgb24gbm90aGluZyBhbmQgZmluZHMgbm90aGluZy5cbiAgICAgIGNvbnN0IHdpZHRoID0gYml0bWFwLndpZHRoO1xuICAgICAgY29uc3QgaGVpZ2h0ID0gYml0bWFwLmhlaWdodDtcbiAgICAgIGNvbnN0IG91dCA9IGN0eC5nZXRJbWFnZURhdGEoMCwgMCwgd2lkdGgsIGhlaWdodCk7XG4gICAgICBiaXRtYXAuY2xvc2U/LigpO1xuICAgICAgcmV0dXJuIHsgZGF0YTogb3V0LmRhdGEsIHdpZHRoLCBoZWlnaHQgfTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgbWFrZVJlZ2lvbih4czogbnVtYmVyW10sIHlzOiBudW1iZXJbXSwgY2xhc3NOYW1lOiBzdHJpbmcsIGhvbGVzOiBudW1iZXJbXVtdW10gPSBbXSk6IFJlZ2lvbiB7XG4gICAgY29uc3QgcG9seSA9IG5ldyBQb2x5Z29uKCk7XG4gICAgcG9seS5ucG9pbnRzID0geHMubGVuZ3RoO1xuICAgIHBvbHkueHBvaW50cyA9IHhzO1xuICAgIHBvbHkueXBvaW50cyA9IHlzO1xuICAgIHBvbHkuY29vcmRpbmF0ZXMgPSB4cy5tYXAoKHgsIGkpID0+IFt4LCB5c1tpXV0pO1xuICAgIHBvbHkuY2xvc2VkID0gdHJ1ZTtcbiAgICAvLyBTYW1lIGNsb3NlZC1yaW5nIGNvbnZlbnRpb24gYXMgdGhlIGV4dGVyaW9yOiBubyByZXBlYXRlZCBjbG9zaW5nIHBvaW50LlxuICAgIC8vIExlZnQgdW5zZXQgd2hlbiBlbXB0eSBzbyBhIHNvbGlkIHJlZ2lvbiBkb2VzIG5vdCBjYXJyeSBhbiBlbXB0eSBhcnJheVxuICAgIC8vIHRocm91Z2ggZXhwb3J0IGFuZCByb3VuZC10cmlwcGluZy5cbiAgICBpZiAoaG9sZXMubGVuZ3RoKSBwb2x5LmhvbGVzID0gaG9sZXM7XG5cbiAgICBjb25zdCByZWdpb24gPSBuZXcgUmVnaW9uKCk7XG4gICAgcmVnaW9uLmJvdW5kcyA9IHBvbHk7XG4gICAgcmVnaW9uLnNvdXJjZSA9IFJFVElOQUxfUkVHSU9OX1NPVVJDRTtcbiAgICAvLyBUaGUgY2xhc3MgbmFtZSBkcml2ZXMgcHJlc2V0IGNvbG91ciByZXNvbHV0aW9uOyBsZWF2aW5nIGBjb2xvcmAgdW5zZXQgbGV0c1xuICAgIC8vIGFuIGV4aXN0aW5nIHByZXNldCBmb3IgdGhpcyBsYXllciB3aW4uXG4gICAgcmVnaW9uLmxhYmVsID0gY2xhc3NOYW1lO1xuICAgIHJldHVybiByZWdpb247XG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of retinal-layer semantic-segmentation models.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors {@link YOLO_MODELS}: public HF-hosted ONNX by default, repointable at
|
|
5
|
+
* private hosting via {@link setRetinalModelUrls}.
|
|
6
|
+
*
|
|
7
|
+
* ONLY ONE CHECKPOINT IS ENABLED, AND THAT IS DELIBERATE
|
|
8
|
+
* The server offers four. Measured against the ground-truth masks shipped
|
|
9
|
+
* alongside them, only VNet reproduces them:
|
|
10
|
+
*
|
|
11
|
+
* | checkpoint | mIoU |
|
|
12
|
+
* |---|---|
|
|
13
|
+
* | **VNet 2D** | **0.9061** |
|
|
14
|
+
* | ResUNet-a 2D (base) | 0.3065 |
|
|
15
|
+
* | ResUNet-a 2D 20x | 0.2869 |
|
|
16
|
+
* | ResUNet-a 2D 40x | 0.2718 |
|
|
17
|
+
*
|
|
18
|
+
* The ResUNet-a numbers are not quantization damage — their ONNX exports match
|
|
19
|
+
* their own Keras originals exactly. They disagree with the *masks*, folding
|
|
20
|
+
* ground-truth class 1 into their class 2 and class 3 into background, and no
|
|
21
|
+
* relabelling rescues them (the best of all 24 class permutations reaches only
|
|
22
|
+
* 0.36). Either those masks belong to a different model generation or the class
|
|
23
|
+
* definitions moved — an open question for whoever trained them.
|
|
24
|
+
*
|
|
25
|
+
* So their weights stay unpublished (their Hub repos are private) and their
|
|
26
|
+
* entries here carry an empty `modelUrl`, which {@link isRetinalModelReady}
|
|
27
|
+
* reports as not-ready. They are listed rather than deleted so a host shows
|
|
28
|
+
* them as unavailable-in-browser instead of pretending the server's choice does
|
|
29
|
+
* not exist — and so enabling one later is a URL, not a code change.
|
|
30
|
+
*
|
|
31
|
+
* Export tooling: the sibling `browser-onnx-tools` project.
|
|
32
|
+
*/
|
|
33
|
+
const HF = 'https://huggingface.co/Ballon999';
|
|
34
|
+
const RESUNET_UNAVAILABLE = 'Not available in the browser: this checkpoint scores mIoU ~0.3 against its own ' +
|
|
35
|
+
'ground-truth masks, pending a review of its class definitions. Run this step on the server.';
|
|
36
|
+
export const RETINAL_MODELS = [
|
|
37
|
+
{
|
|
38
|
+
id: 'vnet-2d-retinal',
|
|
39
|
+
label: 'VNet 2D (40x)',
|
|
40
|
+
modelUrl: `${HF}/vnet-2d-retinal-layer-onnx/resolve/main/model.fp16w.onnx`,
|
|
41
|
+
patchSize: 512,
|
|
42
|
+
// Nearly 6x the YOLO checkpoints. Worth surfacing before a user on a
|
|
43
|
+
// metered connection triggers it by clicking a toolbar button.
|
|
44
|
+
sizeMb: 590,
|
|
45
|
+
miou: 0.9061,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'resunet-a-2d-retinal',
|
|
49
|
+
label: 'ResUNet-a 2D (40x)',
|
|
50
|
+
modelUrl: '',
|
|
51
|
+
patchSize: 512,
|
|
52
|
+
sizeMb: 102,
|
|
53
|
+
miou: 0.3065,
|
|
54
|
+
unavailableReason: RESUNET_UNAVAILABLE,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'resunet-a-2d-retinal-20x',
|
|
58
|
+
label: 'ResUNet-a 2D (20x)',
|
|
59
|
+
modelUrl: '',
|
|
60
|
+
patchSize: 256,
|
|
61
|
+
sizeMb: 102,
|
|
62
|
+
miou: 0.2869,
|
|
63
|
+
unavailableReason: RESUNET_UNAVAILABLE,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'resunet-a-2d-retinal-40x',
|
|
67
|
+
label: 'ResUNet-a 2D (40x, alt)',
|
|
68
|
+
modelUrl: '',
|
|
69
|
+
patchSize: 512,
|
|
70
|
+
sizeMb: 102,
|
|
71
|
+
miou: 0.2718,
|
|
72
|
+
unavailableReason: RESUNET_UNAVAILABLE,
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
export const DEFAULT_RETINAL_MODEL_ID = 'vnet-2d-retinal';
|
|
76
|
+
let defaultModelId = DEFAULT_RETINAL_MODEL_ID;
|
|
77
|
+
/** Look up a model definition by id. Returns undefined for an unknown id. */
|
|
78
|
+
export function getRetinalModel(id) {
|
|
79
|
+
return RETINAL_MODELS.find((m) => m.id === id);
|
|
80
|
+
}
|
|
81
|
+
/** Whether a model has usable weights configured. */
|
|
82
|
+
export function isRetinalModelReady(id) {
|
|
83
|
+
const m = getRetinalModel(id);
|
|
84
|
+
return !!m && m.modelUrl.trim().length > 0;
|
|
85
|
+
}
|
|
86
|
+
/** Only the models a browser can actually run. */
|
|
87
|
+
export function readyRetinalModels() {
|
|
88
|
+
return RETINAL_MODELS.filter((m) => m.modelUrl.trim().length > 0);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Repoint a model at different hosting (a GCS bucket, a same-origin proxy), or
|
|
92
|
+
* enable one of the disabled entries. Call once at app init, before first use.
|
|
93
|
+
* An empty `modelUrl` disables the model.
|
|
94
|
+
*/
|
|
95
|
+
export function setRetinalModelUrls(id, urls) {
|
|
96
|
+
const m = getRetinalModel(id);
|
|
97
|
+
if (!m)
|
|
98
|
+
return;
|
|
99
|
+
if (urls.modelUrl !== undefined)
|
|
100
|
+
m.modelUrl = urls.modelUrl;
|
|
101
|
+
if (urls.metaUrl !== undefined)
|
|
102
|
+
m.metaUrl = urls.metaUrl;
|
|
103
|
+
// A host that supplies weights has answered the question the reason
|
|
104
|
+
// describes. Decided from the model's post-update state rather than from the
|
|
105
|
+
// argument: a whitespace-only url, or a call that sets only `metaUrl`, leaves
|
|
106
|
+
// the model just as disabled — and clearing the reason there would strip the
|
|
107
|
+
// one explanation the user gets while `isRetinalModelReady` still says no.
|
|
108
|
+
if (isRetinalModelReady(id))
|
|
109
|
+
delete m.unavailableReason;
|
|
110
|
+
}
|
|
111
|
+
/** Change which model runs when a caller does not name one. */
|
|
112
|
+
export function setDefaultRetinalModel(id) {
|
|
113
|
+
if (getRetinalModel(id))
|
|
114
|
+
defaultModelId = id;
|
|
115
|
+
}
|
|
116
|
+
export function getDefaultRetinalModelId() {
|
|
117
|
+
return defaultModelId;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0aW5hbC1tb2RlbC1yZWdpc3RyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvdG9vbGJhci9zZWdtZW50YXRpb24vcmV0aW5hbC1tb2RlbC1yZWdpc3RyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQStCRztBQXNCSCxNQUFNLEVBQUUsR0FBRyxrQ0FBa0MsQ0FBQztBQUU5QyxNQUFNLG1CQUFtQixHQUN2QixpRkFBaUY7SUFDakYsNkZBQTZGLENBQUM7QUFFaEcsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFzQjtJQUMvQztRQUNFLEVBQUUsRUFBRSxpQkFBaUI7UUFDckIsS0FBSyxFQUFFLGVBQWU7UUFDdEIsUUFBUSxFQUFFLEdBQUcsRUFBRSwyREFBMkQ7UUFDMUUsU0FBUyxFQUFFLEdBQUc7UUFDZCxxRUFBcUU7UUFDckUsK0RBQStEO1FBQy9ELE1BQU0sRUFBRSxHQUFHO1FBQ1gsSUFBSSxFQUFFLE1BQU07S0FDYjtJQUNEO1FBQ0UsRUFBRSxFQUFFLHNCQUFzQjtRQUMxQixLQUFLLEVBQUUsb0JBQW9CO1FBQzNCLFFBQVEsRUFBRSxFQUFFO1FBQ1osU0FBUyxFQUFFLEdBQUc7UUFDZCxNQUFNLEVBQUUsR0FBRztRQUNYLElBQUksRUFBRSxNQUFNO1FBQ1osaUJBQWlCLEVBQUUsbUJBQW1CO0tBQ3ZDO0lBQ0Q7UUFDRSxFQUFFLEVBQUUsMEJBQTBCO1FBQzlCLEtBQUssRUFBRSxvQkFBb0I7UUFDM0IsUUFBUSxFQUFFLEVBQUU7UUFDWixTQUFTLEVBQUUsR0FBRztRQUNkLE1BQU0sRUFBRSxHQUFHO1FBQ1gsSUFBSSxFQUFFLE1BQU07UUFDWixpQkFBaUIsRUFBRSxtQkFBbUI7S0FDdkM7SUFDRDtRQUNFLEVBQUUsRUFBRSwwQkFBMEI7UUFDOUIsS0FBSyxFQUFFLHlCQUF5QjtRQUNoQyxRQUFRLEVBQUUsRUFBRTtRQUNaLFNBQVMsRUFBRSxHQUFHO1FBQ2QsTUFBTSxFQUFFLEdBQUc7UUFDWCxJQUFJLEVBQUUsTUFBTTtRQUNaLGlCQUFpQixFQUFFLG1CQUFtQjtLQUN2QztDQUNGLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxpQkFBaUIsQ0FBQztBQUUxRCxJQUFJLGNBQWMsR0FBRyx3QkFBd0IsQ0FBQztBQUU5Qyw2RUFBNkU7QUFDN0UsTUFBTSxVQUFVLGVBQWUsQ0FBQyxFQUFVO0lBQ3hDLE9BQU8sY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztBQUNqRCxDQUFDO0FBRUQscURBQXFEO0FBQ3JELE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxFQUFVO0lBQzVDLE1BQU0sQ0FBQyxHQUFHLGVBQWUsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUM5QixPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0FBQzdDLENBQUM7QUFFRCxrREFBa0Q7QUFDbEQsTUFBTSxVQUFVLGtCQUFrQjtJQUNoQyxPQUFPLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ3BFLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLG1CQUFtQixDQUFDLEVBQVUsRUFBRSxJQUE2QztJQUMzRixNQUFNLENBQUMsR0FBRyxlQUFlLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDOUIsSUFBSSxDQUFDLENBQUM7UUFBRSxPQUFPO0lBQ2YsSUFBSSxJQUFJLENBQUMsUUFBUSxLQUFLLFNBQVM7UUFBRSxDQUFDLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUM7SUFDNUQsSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLFNBQVM7UUFBRSxDQUFDLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDekQsb0VBQW9FO0lBQ3BFLDZFQUE2RTtJQUM3RSw4RUFBOEU7SUFDOUUsNkVBQTZFO0lBQzdFLDJFQUEyRTtJQUMzRSxJQUFJLG1CQUFtQixDQUFDLEVBQUUsQ0FBQztRQUFFLE9BQU8sQ0FBQyxDQUFDLGlCQUFpQixDQUFDO0FBQzFELENBQUM7QUFFRCwrREFBK0Q7QUFDL0QsTUFBTSxVQUFVLHNCQUFzQixDQUFDLEVBQVU7SUFDL0MsSUFBSSxlQUFlLENBQUMsRUFBRSxDQUFDO1FBQUUsY0FBYyxHQUFHLEVBQUUsQ0FBQztBQUMvQyxDQUFDO0FBRUQsTUFBTSxVQUFVLHdCQUF3QjtJQUN0QyxPQUFPLGNBQWMsQ0FBQztBQUN4QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWdpc3RyeSBvZiByZXRpbmFsLWxheWVyIHNlbWFudGljLXNlZ21lbnRhdGlvbiBtb2RlbHMuXG4gKlxuICogTWlycm9ycyB7QGxpbmsgWU9MT19NT0RFTFN9OiBwdWJsaWMgSEYtaG9zdGVkIE9OTlggYnkgZGVmYXVsdCwgcmVwb2ludGFibGUgYXRcbiAqIHByaXZhdGUgaG9zdGluZyB2aWEge0BsaW5rIHNldFJldGluYWxNb2RlbFVybHN9LlxuICpcbiAqIE9OTFkgT05FIENIRUNLUE9JTlQgSVMgRU5BQkxFRCwgQU5EIFRIQVQgSVMgREVMSUJFUkFURVxuICogVGhlIHNlcnZlciBvZmZlcnMgZm91ci4gTWVhc3VyZWQgYWdhaW5zdCB0aGUgZ3JvdW5kLXRydXRoIG1hc2tzIHNoaXBwZWRcbiAqIGFsb25nc2lkZSB0aGVtLCBvbmx5IFZOZXQgcmVwcm9kdWNlcyB0aGVtOlxuICpcbiAqIHwgY2hlY2twb2ludCB8IG1Jb1UgfFxuICogfC0tLXwtLS18XG4gKiB8ICoqVk5ldCAyRCoqIHwgKiowLjkwNjEqKiB8XG4gKiB8IFJlc1VOZXQtYSAyRCAoYmFzZSkgfCAwLjMwNjUgfFxuICogfCBSZXNVTmV0LWEgMkQgMjB4IHwgMC4yODY5IHxcbiAqIHwgUmVzVU5ldC1hIDJEIDQweCB8IDAuMjcxOCB8XG4gKlxuICogVGhlIFJlc1VOZXQtYSBudW1iZXJzIGFyZSBub3QgcXVhbnRpemF0aW9uIGRhbWFnZSDigJQgdGhlaXIgT05OWCBleHBvcnRzIG1hdGNoXG4gKiB0aGVpciBvd24gS2VyYXMgb3JpZ2luYWxzIGV4YWN0bHkuIFRoZXkgZGlzYWdyZWUgd2l0aCB0aGUgKm1hc2tzKiwgZm9sZGluZ1xuICogZ3JvdW5kLXRydXRoIGNsYXNzIDEgaW50byB0aGVpciBjbGFzcyAyIGFuZCBjbGFzcyAzIGludG8gYmFja2dyb3VuZCwgYW5kIG5vXG4gKiByZWxhYmVsbGluZyByZXNjdWVzIHRoZW0gKHRoZSBiZXN0IG9mIGFsbCAyNCBjbGFzcyBwZXJtdXRhdGlvbnMgcmVhY2hlcyBvbmx5XG4gKiAwLjM2KS4gRWl0aGVyIHRob3NlIG1hc2tzIGJlbG9uZyB0byBhIGRpZmZlcmVudCBtb2RlbCBnZW5lcmF0aW9uIG9yIHRoZSBjbGFzc1xuICogZGVmaW5pdGlvbnMgbW92ZWQg4oCUIGFuIG9wZW4gcXVlc3Rpb24gZm9yIHdob2V2ZXIgdHJhaW5lZCB0aGVtLlxuICpcbiAqIFNvIHRoZWlyIHdlaWdodHMgc3RheSB1bnB1Ymxpc2hlZCAodGhlaXIgSHViIHJlcG9zIGFyZSBwcml2YXRlKSBhbmQgdGhlaXJcbiAqIGVudHJpZXMgaGVyZSBjYXJyeSBhbiBlbXB0eSBgbW9kZWxVcmxgLCB3aGljaCB7QGxpbmsgaXNSZXRpbmFsTW9kZWxSZWFkeX1cbiAqIHJlcG9ydHMgYXMgbm90LXJlYWR5LiBUaGV5IGFyZSBsaXN0ZWQgcmF0aGVyIHRoYW4gZGVsZXRlZCBzbyBhIGhvc3Qgc2hvd3NcbiAqIHRoZW0gYXMgdW5hdmFpbGFibGUtaW4tYnJvd3NlciBpbnN0ZWFkIG9mIHByZXRlbmRpbmcgdGhlIHNlcnZlcidzIGNob2ljZSBkb2VzXG4gKiBub3QgZXhpc3Qg4oCUIGFuZCBzbyBlbmFibGluZyBvbmUgbGF0ZXIgaXMgYSBVUkwsIG5vdCBhIGNvZGUgY2hhbmdlLlxuICpcbiAqIEV4cG9ydCB0b29saW5nOiB0aGUgc2libGluZyBgYnJvd3Nlci1vbm54LXRvb2xzYCBwcm9qZWN0LlxuICovXG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0aW5hbE1vZGVsRGVmIHtcbiAgaWQ6IHN0cmluZztcbiAgbGFiZWw6IHN0cmluZztcbiAgLyoqIFVSTCBvZiB0aGUgYC5vbm54YCB3ZWlnaHRzLiAqKkVtcHR5IGRpc2FibGVzIHRoZSBtb2RlbC4qKiAqL1xuICBtb2RlbFVybDogc3RyaW5nO1xuICAvKipcbiAgICogVVJMIG9mIHRoZSBgbW9kZWwuanNvbmAgc2lkZWNhci4gT21pdCB0byB1c2UgYG1vZGVsLmpzb25gIGFsb25nc2lkZSB0aGVcbiAgICogd2VpZ2h0cywgd2hpY2ggaXMgaG93IGV2ZXJ5IHB1Ymxpc2hlZCByZXBvIGlzIGxhaWQgb3V0LlxuICAgKi9cbiAgbWV0YVVybD86IHN0cmluZztcbiAgLyoqIFBhdGNoIHNpemUgdGhlIGNoZWNrcG9pbnQgd2FzIHRyYWluZWQgYXQsIGZvciBjYWxsZXJzIHRoYXQgc2l6ZSByZWFkcyB0byBpdC4gKi9cbiAgcGF0Y2hTaXplOiBudW1iZXI7XG4gIC8qKiBEb3dubG9hZCBzaXplIG9mIHRoZSBgZnAxNndgIHdlaWdodHMsIE1CIOKAlCB0aGVzZSBhcmUgbGFyZ2UgZW5vdWdoIHRvIHdhcm4gYWJvdXQuICovXG4gIHNpemVNYjogbnVtYmVyO1xuICAvKiogTWVhc3VyZWQgbUlvVSBhZ2FpbnN0IHRoZSBidWNrZXQncyBncm91bmQtdHJ1dGggbWFza3MuICovXG4gIG1pb3U6IG51bWJlcjtcbiAgLyoqIFNldCB3aGVuIHRoZSBtb2RlbCBpcyBrbm93aW5nbHkgZGlzYWJsZWQ7IHNob3duIHRvIHRoZSB1c2VyLiAqL1xuICB1bmF2YWlsYWJsZVJlYXNvbj86IHN0cmluZztcbn1cblxuY29uc3QgSEYgPSAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9CYWxsb245OTknO1xuXG5jb25zdCBSRVNVTkVUX1VOQVZBSUxBQkxFID1cbiAgJ05vdCBhdmFpbGFibGUgaW4gdGhlIGJyb3dzZXI6IHRoaXMgY2hlY2twb2ludCBzY29yZXMgbUlvVSB+MC4zIGFnYWluc3QgaXRzIG93biAnICtcbiAgJ2dyb3VuZC10cnV0aCBtYXNrcywgcGVuZGluZyBhIHJldmlldyBvZiBpdHMgY2xhc3MgZGVmaW5pdGlvbnMuIFJ1biB0aGlzIHN0ZXAgb24gdGhlIHNlcnZlci4nO1xuXG5leHBvcnQgY29uc3QgUkVUSU5BTF9NT0RFTFM6IFJldGluYWxNb2RlbERlZltdID0gW1xuICB7XG4gICAgaWQ6ICd2bmV0LTJkLXJldGluYWwnLFxuICAgIGxhYmVsOiAnVk5ldCAyRCAoNDB4KScsXG4gICAgbW9kZWxVcmw6IGAke0hGfS92bmV0LTJkLXJldGluYWwtbGF5ZXItb25ueC9yZXNvbHZlL21haW4vbW9kZWwuZnAxNncub25ueGAsXG4gICAgcGF0Y2hTaXplOiA1MTIsXG4gICAgLy8gTmVhcmx5IDZ4IHRoZSBZT0xPIGNoZWNrcG9pbnRzLiBXb3J0aCBzdXJmYWNpbmcgYmVmb3JlIGEgdXNlciBvbiBhXG4gICAgLy8gbWV0ZXJlZCBjb25uZWN0aW9uIHRyaWdnZXJzIGl0IGJ5IGNsaWNraW5nIGEgdG9vbGJhciBidXR0b24uXG4gICAgc2l6ZU1iOiA1OTAsXG4gICAgbWlvdTogMC45MDYxLFxuICB9LFxuICB7XG4gICAgaWQ6ICdyZXN1bmV0LWEtMmQtcmV0aW5hbCcsXG4gICAgbGFiZWw6ICdSZXNVTmV0LWEgMkQgKDQweCknLFxuICAgIG1vZGVsVXJsOiAnJyxcbiAgICBwYXRjaFNpemU6IDUxMixcbiAgICBzaXplTWI6IDEwMixcbiAgICBtaW91OiAwLjMwNjUsXG4gICAgdW5hdmFpbGFibGVSZWFzb246IFJFU1VORVRfVU5BVkFJTEFCTEUsXG4gIH0sXG4gIHtcbiAgICBpZDogJ3Jlc3VuZXQtYS0yZC1yZXRpbmFsLTIweCcsXG4gICAgbGFiZWw6ICdSZXNVTmV0LWEgMkQgKDIweCknLFxuICAgIG1vZGVsVXJsOiAnJyxcbiAgICBwYXRjaFNpemU6IDI1NixcbiAgICBzaXplTWI6IDEwMixcbiAgICBtaW91OiAwLjI4NjksXG4gICAgdW5hdmFpbGFibGVSZWFzb246IFJFU1VORVRfVU5BVkFJTEFCTEUsXG4gIH0sXG4gIHtcbiAgICBpZDogJ3Jlc3VuZXQtYS0yZC1yZXRpbmFsLTQweCcsXG4gICAgbGFiZWw6ICdSZXNVTmV0LWEgMkQgKDQweCwgYWx0KScsXG4gICAgbW9kZWxVcmw6ICcnLFxuICAgIHBhdGNoU2l6ZTogNTEyLFxuICAgIHNpemVNYjogMTAyLFxuICAgIG1pb3U6IDAuMjcxOCxcbiAgICB1bmF2YWlsYWJsZVJlYXNvbjogUkVTVU5FVF9VTkFWQUlMQUJMRSxcbiAgfSxcbl07XG5cbmV4cG9ydCBjb25zdCBERUZBVUxUX1JFVElOQUxfTU9ERUxfSUQgPSAndm5ldC0yZC1yZXRpbmFsJztcblxubGV0IGRlZmF1bHRNb2RlbElkID0gREVGQVVMVF9SRVRJTkFMX01PREVMX0lEO1xuXG4vKiogTG9vayB1cCBhIG1vZGVsIGRlZmluaXRpb24gYnkgaWQuIFJldHVybnMgdW5kZWZpbmVkIGZvciBhbiB1bmtub3duIGlkLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFJldGluYWxNb2RlbChpZDogc3RyaW5nKTogUmV0aW5hbE1vZGVsRGVmIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIFJFVElOQUxfTU9ERUxTLmZpbmQoKG0pID0+IG0uaWQgPT09IGlkKTtcbn1cblxuLyoqIFdoZXRoZXIgYSBtb2RlbCBoYXMgdXNhYmxlIHdlaWdodHMgY29uZmlndXJlZC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc1JldGluYWxNb2RlbFJlYWR5KGlkOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgY29uc3QgbSA9IGdldFJldGluYWxNb2RlbChpZCk7XG4gIHJldHVybiAhIW0gJiYgbS5tb2RlbFVybC50cmltKCkubGVuZ3RoID4gMDtcbn1cblxuLyoqIE9ubHkgdGhlIG1vZGVscyBhIGJyb3dzZXIgY2FuIGFjdHVhbGx5IHJ1bi4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWFkeVJldGluYWxNb2RlbHMoKTogUmV0aW5hbE1vZGVsRGVmW10ge1xuICByZXR1cm4gUkVUSU5BTF9NT0RFTFMuZmlsdGVyKChtKSA9PiBtLm1vZGVsVXJsLnRyaW0oKS5sZW5ndGggPiAwKTtcbn1cblxuLyoqXG4gKiBSZXBvaW50IGEgbW9kZWwgYXQgZGlmZmVyZW50IGhvc3RpbmcgKGEgR0NTIGJ1Y2tldCwgYSBzYW1lLW9yaWdpbiBwcm94eSksIG9yXG4gKiBlbmFibGUgb25lIG9mIHRoZSBkaXNhYmxlZCBlbnRyaWVzLiBDYWxsIG9uY2UgYXQgYXBwIGluaXQsIGJlZm9yZSBmaXJzdCB1c2UuXG4gKiBBbiBlbXB0eSBgbW9kZWxVcmxgIGRpc2FibGVzIHRoZSBtb2RlbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNldFJldGluYWxNb2RlbFVybHMoaWQ6IHN0cmluZywgdXJsczogeyBtb2RlbFVybD86IHN0cmluZzsgbWV0YVVybD86IHN0cmluZyB9KTogdm9pZCB7XG4gIGNvbnN0IG0gPSBnZXRSZXRpbmFsTW9kZWwoaWQpO1xuICBpZiAoIW0pIHJldHVybjtcbiAgaWYgKHVybHMubW9kZWxVcmwgIT09IHVuZGVmaW5lZCkgbS5tb2RlbFVybCA9IHVybHMubW9kZWxVcmw7XG4gIGlmICh1cmxzLm1ldGFVcmwgIT09IHVuZGVmaW5lZCkgbS5tZXRhVXJsID0gdXJscy5tZXRhVXJsO1xuICAvLyBBIGhvc3QgdGhhdCBzdXBwbGllcyB3ZWlnaHRzIGhhcyBhbnN3ZXJlZCB0aGUgcXVlc3Rpb24gdGhlIHJlYXNvblxuICAvLyBkZXNjcmliZXMuIERlY2lkZWQgZnJvbSB0aGUgbW9kZWwncyBwb3N0LXVwZGF0ZSBzdGF0ZSByYXRoZXIgdGhhbiBmcm9tIHRoZVxuICAvLyBhcmd1bWVudDogYSB3aGl0ZXNwYWNlLW9ubHkgdXJsLCBvciBhIGNhbGwgdGhhdCBzZXRzIG9ubHkgYG1ldGFVcmxgLCBsZWF2ZXNcbiAgLy8gdGhlIG1vZGVsIGp1c3QgYXMgZGlzYWJsZWQg4oCUIGFuZCBjbGVhcmluZyB0aGUgcmVhc29uIHRoZXJlIHdvdWxkIHN0cmlwIHRoZVxuICAvLyBvbmUgZXhwbGFuYXRpb24gdGhlIHVzZXIgZ2V0cyB3aGlsZSBgaXNSZXRpbmFsTW9kZWxSZWFkeWAgc3RpbGwgc2F5cyBuby5cbiAgaWYgKGlzUmV0aW5hbE1vZGVsUmVhZHkoaWQpKSBkZWxldGUgbS51bmF2YWlsYWJsZVJlYXNvbjtcbn1cblxuLyoqIENoYW5nZSB3aGljaCBtb2RlbCBydW5zIHdoZW4gYSBjYWxsZXIgZG9lcyBub3QgbmFtZSBvbmUuICovXG5leHBvcnQgZnVuY3Rpb24gc2V0RGVmYXVsdFJldGluYWxNb2RlbChpZDogc3RyaW5nKTogdm9pZCB7XG4gIGlmIChnZXRSZXRpbmFsTW9kZWwoaWQpKSBkZWZhdWx0TW9kZWxJZCA9IGlkO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGVmYXVsdFJldGluYWxNb2RlbElkKCk6IHN0cmluZyB7XG4gIHJldHVybiBkZWZhdWx0TW9kZWxJZDtcbn1cbiJdfQ==
|