@jax-data-science/sci-image-visualizer 0.2.19 → 0.3.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.
Files changed (35) hide show
  1. package/esm2022/index.mjs +2 -9
  2. package/esm2022/lib/contracts/instance-segmenter.contract.mjs +10 -10
  3. package/esm2022/lib/contracts/semantic-segmenter.contract.mjs +10 -9
  4. package/esm2022/lib/contracts/toolbar-tool.contract.mjs +64 -0
  5. package/esm2022/lib/toolbar/model-info.mjs +32 -0
  6. package/esm2022/lib/toolbar/toolbar.component.mjs +82 -55
  7. package/esm2022/lib/visualizer.component.mjs +131 -138
  8. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-iPNL2JyC.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs} +2 -2
  9. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-iPNL2JyC.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map} +1 -1
  10. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +710 -1565
  11. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  12. package/index.d.ts +1 -6
  13. package/lib/contracts/instance-segmenter.contract.d.ts +7 -3
  14. package/lib/contracts/semantic-segmenter.contract.d.ts +7 -2
  15. package/lib/contracts/toolbar-tool.contract.d.ts +190 -0
  16. package/lib/toolbar/model-info.d.ts +17 -0
  17. package/lib/toolbar/toolbar.component.d.ts +53 -30
  18. package/lib/visualizer.component.d.ts +49 -58
  19. package/package.json +2 -4
  20. package/src/lib/assets/cell-detection.svg +37 -0
  21. package/src/lib/assets/cells.svg +1 -1
  22. package/src/lib/assets/sam-box-prompt.svg +61 -0
  23. package/src/lib/assets/sam-point-prompt.svg +91 -0
  24. package/esm2022/lib/toolbar/segmentation/retinal-layer-tool.service.mjs +0 -197
  25. package/esm2022/lib/toolbar/segmentation/retinal-model-registry.mjs +0 -119
  26. package/esm2022/lib/toolbar/segmentation/retinal-segmenter.service.mjs +0 -172
  27. package/esm2022/lib/toolbar/segmentation/yolo-detect-tool.service.mjs +0 -208
  28. package/esm2022/lib/toolbar/segmentation/yolo-model-registry.mjs +0 -129
  29. package/esm2022/lib/toolbar/segmentation/yolo-segmenter.service.mjs +0 -172
  30. package/lib/toolbar/segmentation/retinal-layer-tool.service.d.ts +0 -49
  31. package/lib/toolbar/segmentation/retinal-model-registry.d.ts +0 -71
  32. package/lib/toolbar/segmentation/retinal-segmenter.service.d.ts +0 -60
  33. package/lib/toolbar/segmentation/yolo-detect-tool.service.d.ts +0 -51
  34. package/lib/toolbar/segmentation/yolo-model-registry.d.ts +0 -59
  35. package/lib/toolbar/segmentation/yolo-segmenter.service.d.ts +0 -50
@@ -1 +1 @@
1
- <svg class="svg-icon" style="width: 1.0048828125em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1029 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M519.04 2.048c-280.8 0-509.216 228.448-509.216 509.216 0 280.8 228.416 509.216 509.216 509.216 280.832 0 509.248-228.448 509.248-509.216C1028.288 230.496 799.872 2.048 519.04 2.048zM519.04 939.456c-236.128 0-428.16-192.064-428.16-428.192 0-236.064 192.032-428.16 428.16-428.16S947.232 275.2 947.232 511.264C947.232 747.392 755.168 939.456 519.04 939.456z" /><path d="M692.448 574.464m-123.968 0a3.874 3.874 0 1 0 247.936 0 3.874 3.874 0 1 0-247.936 0Z" /><path d="M416.192 253.824m-92.064 0a2.877 2.877 0 1 0 184.128 0 2.877 2.877 0 1 0-184.128 0Z" /><path d="M317.248 561.824m-111.328 0a3.479 3.479 0 1 0 222.656 0 3.479 3.479 0 1 0-222.656 0Z" /><path d="M512.672 780.704m-46.912 0a1.466 1.466 0 1 0 93.824 0 1.466 1.466 0 1 0-93.824 0Z" /><path d="M711.936 318.784m-50.656 0a1.583 1.583 0 1 0 101.312 0 1.583 1.583 0 1 0-101.312 0Z" /><path d="M527.52 446.368m-42.912 0a1.341 1.341 0 1 0 85.824 0 1.341 1.341 0 1 0-85.824 0Z" /></svg>
1
+ <svg class="svg-icon" style="width: 1.0048828125em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1029 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M 164.633,51.2 H 864.367 A 110.933,110.933 0 0 1 975.3,162.133 V 861.867 A 110.933,110.933 0 0 1 864.367,972.8 H 164.633 A 110.933,110.933 0 0 1 53.7,861.867 V 162.133 A 110.933,110.933 0 0 1 164.633,51.2 Z M 164.633,109.227 H 864.367 A 52.907,52.907 0 0 1 917.273,162.133 V 861.867 A 52.907,52.907 0 0 1 864.367,914.773 H 164.633 A 52.907,52.907 0 0 1 111.727,861.867 V 162.133 A 52.907,52.907 0 0 1 164.633,109.227 Z" fill-rule="evenodd" /><path d="M692.448 574.464m-123.968 0a3.874 3.874 0 1 0 247.936 0 3.874 3.874 0 1 0-247.936 0Z" /><path d="M416.192 253.824m-92.064 0a2.877 2.877 0 1 0 184.128 0 2.877 2.877 0 1 0-184.128 0Z" /><path d="M317.248 561.824m-111.328 0a3.479 3.479 0 1 0 222.656 0 3.479 3.479 0 1 0-222.656 0Z" /><path d="M512.672 780.704m-46.912 0a1.466 1.466 0 1 0 93.824 0 1.466 1.466 0 1 0-93.824 0Z" /><path d="M711.936 318.784m-50.656 0a1.583 1.583 0 1 0 101.312 0 1.583 1.583 0 1 0-101.312 0Z" /><path d="M527.52 446.368m-42.912 0a1.341 1.341 0 1 0 85.824 0 1.341 1.341 0 1 0-85.824 0Z" /></svg>
@@ -0,0 +1,61 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ version="1.1"
4
+ viewBox="0 0 60 60"
5
+ id="svg2"
6
+ sodipodi:docname="sam-box-prompt.svg"
7
+ width="60"
8
+ height="60"
9
+ inkscape:version="1.4.4 (dcaf3e7, 2026-05-05)"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlns:svg="http://www.w3.org/2000/svg">
14
+ <defs
15
+ id="defs2" />
16
+ <sodipodi:namedview
17
+ id="namedview2"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#000000"
20
+ borderopacity="0.25"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:current-layer="svg2" />
26
+ <!-- One object per component, each with an identity rotate() about its own
27
+ centre. The cells match sam-point-prompt.svg: same absolute radii, and
28
+ the same position relative to the blob (offsets scaled by the two blobs'
29
+ size ratio, since this blob is the smaller of the two). Each ring keeps
30
+ its outer and inner contour in one path — the inner one is its hole. -->
31
+ <path
32
+ d="M 9.5,3 H 50.5 A 6.5,6.5 0 0 1 57,9.5 V 50.5 A 6.5,6.5 0 0 1 50.5,57 H 9.5 A 6.5,6.5 0 0 1 3,50.5 V 9.5 A 6.5,6.5 0 0 1 9.5,3 Z M 50.5,6.4 H 9.5 A 3.1,3.1 0 0 0 6.4,9.5 V 50.5 A 3.1,3.1 0 0 0 9.5,53.6 H 50.5 A 3.1,3.1 0 0 0 53.6,50.5 V 9.5 A 3.1,3.1 0 0 0 50.5,6.4 Z"
33
+ fill-rule="nonzero"
34
+ transform="rotate(0, 30, 30)"
35
+ id="frame"
36
+ inkscape:label="box prompt frame" />
37
+ <path
38
+ d="M 47.488,30 C 47.619,32.002 47.456,34.173 46.898,36.15 C 46.339,38.127 45.578,40.506 44.137,41.862 C 42.696,43.219 40.19,43.902 38.252,44.292 C 36.313,44.683 34.346,43.944 32.505,44.205 C 30.663,44.467 29.195,45.503 27.203,45.863 C 25.211,46.223 22.452,47.022 20.553,46.363 C 18.653,45.704 16.873,43.676 15.807,41.909 C 14.741,40.143 14.545,37.751 14.158,35.766 C 13.772,33.781 13.356,31.874 13.488,30 C 13.619,28.126 14.181,26.187 14.948,24.522 C 15.715,22.856 17.041,21.62 18.092,20.008 C 19.142,18.395 19.833,16.395 21.252,14.847 C 22.67,13.3 24.625,11.133 26.601,10.722 C 28.577,10.311 31.282,11.347 33.107,12.379 C 34.932,13.412 36.095,15.639 37.553,16.919 C 39.01,18.198 40.427,18.851 41.853,20.054 C 43.278,21.258 45.169,22.48 46.108,24.137 C 47.047,25.795 47.356,27.998 47.488,30 Z M 42.886,25.31 C 42.135,23.984 40.623,23.006 39.482,22.044 C 38.341,21.081 37.208,20.558 36.042,19.535 C 34.876,18.512 33.946,16.73 32.486,15.904 C 31.025,15.077 28.861,14.249 27.281,14.578 C 25.7,14.907 24.136,16.64 23.001,17.878 C 21.867,19.116 21.314,20.716 20.473,22.006 C 19.633,23.296 18.572,24.285 17.959,25.617 C 17.345,26.95 16.896,28.501 16.79,30 C 16.685,31.499 17.017,33.025 17.327,34.613 C 17.636,36.201 17.793,38.114 18.646,39.527 C 19.498,40.94 20.923,42.564 22.442,43.091 C 23.961,43.618 26.169,42.978 27.762,42.69 C 29.356,42.403 30.531,41.574 32.004,41.364 C 33.477,41.155 35.05,41.746 36.601,41.434 C 38.152,41.121 40.157,40.576 41.31,39.49 C 42.463,38.404 43.071,36.502 43.518,34.92 C 43.965,33.339 44.096,31.602 43.99,30 C 43.885,28.398 43.638,26.636 42.886,25.31 Z"
39
+ fill-rule="nonzero"
40
+ transform="rotate(0, 30.497, 28.623)"
41
+ id="blob"
42
+ inkscape:label="blob" />
43
+ <path
44
+ d="M 33.121,21.58 A 4.177,4.177 0 1 1 24.768,21.58 A 4.177,4.177 0 1 1 33.121,21.58 Z"
45
+ fill-rule="nonzero"
46
+ transform="rotate(0, 28.944, 21.58)"
47
+ id="cell-1"
48
+ inkscape:label="cell 1" />
49
+ <path
50
+ d="M 29.662,33.008 A 4.984,4.984 0 1 1 19.693,33.008 A 4.984,4.984 0 1 1 29.662,33.008 Z"
51
+ fill-rule="nonzero"
52
+ transform="rotate(0, 24.677, 33.008)"
53
+ id="cell-2"
54
+ inkscape:label="cell 2" />
55
+ <path
56
+ d="M 41.815,26.755 A 2.949,2.949 0 1 1 35.917,26.755 A 2.949,2.949 0 1 1 41.815,26.755 Z"
57
+ fill-rule="nonzero"
58
+ transform="rotate(0, 38.866, 26.755)"
59
+ id="cell-3"
60
+ inkscape:label="cell 3" />
61
+ </svg>
@@ -0,0 +1,91 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ version="1.1"
4
+ viewBox="0 0 60 60"
5
+ id="svg2"
6
+ sodipodi:docname="sam-point-prompt.svg"
7
+ width="60"
8
+ height="60"
9
+ inkscape:version="1.4.4 (dcaf3e7, 2026-05-05)"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlns:svg="http://www.w3.org/2000/svg">
14
+ <defs
15
+ id="defs2" />
16
+ <sodipodi:namedview
17
+ id="namedview2"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#000000"
20
+ borderopacity="0.25"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:current-layer="svg2" />
26
+ <!-- One object per component, each with an identity rotate() about its own
27
+ centre. Stacking is document order, so the cursor and rays paint over
28
+ everything else. The gap around them is a HOLE cut into the shapes they
29
+ overlap (white paint cannot survive the toolbar's recolour filter), so it
30
+ does NOT follow the cursor: move or turn the cursor and re-cut. -->
31
+ <path
32
+ d="M 26.753906 6.0761719 C 26.424535 6.0872988 26.105906 6.1220469 25.800781 6.1855469 C 23.359781 6.6935469 20.945359 9.36925 19.193359 11.28125 C 17.441359 13.19325 16.586063 15.66425 15.289062 17.65625 C 13.991063 19.64725 12.35325 21.174422 11.40625 23.232422 C 10.45825 25.289422 9.7655156 27.685 9.6035156 30 C 9.4405156 32.315 9.9536406 34.671047 10.431641 37.123047 C 10.908641 39.574047 11.15175 42.529938 12.46875 44.710938 C 13.78475 46.892938 15.983078 49.400844 18.330078 50.214844 C 20.676078 51.028844 24.083922 50.040703 26.544922 49.595703 C 28.974057 49.157452 30.771942 47.90353 33.005859 47.560547 L 33.005859 44.664062 C 32.867613 44.673655 32.730724 44.687566 32.59375 44.707031 C 30.68775 44.978031 29.166516 46.050828 27.103516 46.423828 C 25.041516 46.795828 22.18475 47.623406 20.21875 46.941406 C 18.25275 46.259406 16.409641 44.158078 15.306641 42.330078 C 14.203641 40.501078 13.999609 38.02375 13.599609 35.96875 C 13.199609 33.91375 12.768297 31.94 12.904297 30 C 13.040297 28.06 13.623969 26.052125 14.417969 24.328125 C 15.211969 22.604125 16.584875 21.323297 17.671875 19.654297 C 18.759875 17.985297 19.475359 15.9155 20.943359 14.3125 C 22.411359 12.7105 24.434469 10.467969 26.480469 10.042969 C 28.525469 9.6169688 31.326797 10.687813 33.216797 11.757812 C 35.105797 12.826812 36.309359 15.133031 37.818359 16.457031 C 39.327359 17.781031 40.795484 18.458125 42.271484 19.703125 C 43.747484 20.949125 45.703781 22.213687 46.675781 23.929688 C 47.648781 25.645688 47.969469 27.927 48.105469 30 C 48.241469 32.073 48.072141 34.320188 47.494141 36.367188 C 47.17036 37.513862 46.78069 38.791076 46.238281 39.939453 L 49.154297 42.306641 C 49.918701 40.8271 50.443935 39.116961 50.873047 37.597656 C 51.562047 35.155656 51.765516 32.473 51.603516 30 C 51.440516 27.527 51.058437 24.804813 49.898438 22.757812 C 48.738438 20.709812 46.402625 19.201844 44.640625 17.714844 C 42.879625 16.228844 41.130078 15.421797 39.330078 13.841797 C 37.529078 12.260797 36.092891 9.5084219 33.837891 8.2324219 C 31.864766 7.1167969 29.059504 5.9982832 26.753906 6.0761719 z "
33
+ fill-rule="nonzero"
34
+ transform="rotate(0, 30.614, 28.299)"
35
+ id="blob"
36
+ inkscape:label="blob (halo gap cut in)" />
37
+ <path
38
+ d="m 32.855664,19.599131 a 4.1595995,4.1939245 0 1 1 -8.319199,0 4.1595995,4.1939245 0 1 1 8.319199,0 z"
39
+ fill-rule="nonzero"
40
+ transform="rotate(0, 28.696, 19.599)"
41
+ id="cell-1"
42
+ inkscape:label="cell 1" />
43
+ <path
44
+ d="M 23.273438 28.732422 A 4.9833982 4.9833559 0 0 0 18.441406 33.714844 A 4.9833982 4.9833559 0 0 0 25.453125 38.269531 C 25.675362 37.549617 26.244545 36.876078 26.953125 36.519531 L 27.808594 36.087891 A 4.9833982 4.9833559 0 0 0 27.986328 35.726562 L 26.802734 35.541016 C 25.98582 35.413144 25.205113 34.912114 24.783203 34.255859 C 24.361293 33.599605 24.255759 32.867793 24.361328 32.193359 C 24.466897 31.518925 24.792268 30.853362 25.394531 30.357422 C 25.707847 30.099419 26.099902 29.911627 26.519531 29.808594 A 4.9833982 4.9833559 0 0 0 23.273438 28.732422 z "
45
+ fill-rule="nonzero"
46
+ transform="rotate(0, 23.214, 33.716)"
47
+ id="cell-2"
48
+ inkscape:label="cell 2 (halo gap cut in)" />
49
+ <path
50
+ d="M 41.033203 22.958984 A 2.8632495 3.0348742 0 0 0 38.871094 23.90625 C 39.069664 23.935024 39.263693 23.984186 39.449219 24.048828 C 40.093828 24.273427 40.690224 24.713235 41.070312 25.394531 C 41.450404 26.075827 41.525953 27.000464 41.253906 27.78125 L 40.820312 29.023438 A 2.8632495 3.0348742 0 0 0 43.816406 25.992188 A 2.8632495 3.0348742 0 0 0 41.033203 22.958984 z "
51
+ fill-rule="nonzero"
52
+ transform="rotate(0, 41.344, 25.992)"
53
+ id="cell-3"
54
+ inkscape:label="cell 3 (halo gap cut in)" />
55
+ <path
56
+ d="m 49.323286,44.760886 -5.831,0.595 3.451,7.497 -3.927,1.785 -3.451,-7.735 -4.76,4.522 v -18.445 z"
57
+ fill-rule="nonzero"
58
+ transform="rotate(0, 42.064, 43.809)"
59
+ id="cursor"
60
+ inkscape:label="cursor" />
61
+ <path
62
+ d="m 32.605984,38.219843 -3.826299,1.927112 a 1.1304695,1.1304695 0 0 1 -1.017301,-2.019144 l 3.826299,-1.927112 a 1.1304695,1.1304695 0 0 1 1.017301,2.019144 z"
63
+ fill-rule="nonzero"
64
+ transform="rotate(0, 30.184, 38.174)"
65
+ id="ray-1"
66
+ inkscape:label="click ray 1" />
67
+ <path
68
+ d="m 31.31284,34.427384 -4.231487,-0.663997 a 1.1305861,1.1305861 0.81340931 0 1 0.349684,-2.23397 l 4.232821,0.664468 a 1.1304569,1.1304569 81.489795 0 1 -0.351018,2.233499 z"
69
+ fill-rule="nonzero"
70
+ transform="rotate(0, 29.372, 32.978)"
71
+ id="ray-2"
72
+ inkscape:label="click ray 2" />
73
+ <path
74
+ d="m 32.558134,30.766933 -1.844,-3.867 a 1.130532,1.130532 0 0 1 2.041,-0.973 l 1.844,3.867 a 1.130532,1.130532 0 0 1 -2.041,0.973 z"
75
+ fill-rule="nonzero"
76
+ transform="rotate(0, 32.657, 28.347)"
77
+ id="ray-3"
78
+ inkscape:label="click ray 3" />
79
+ <path
80
+ d="m 36.008063,30.490105 1.410188,-4.045456 a 1.1304696,1.1304696 0 0 1 2.135052,0.743908 l -1.410188,4.045456 a 1.1304696,1.1304696 0 0 1 -2.135052,-0.743908 z"
81
+ fill-rule="nonzero"
82
+ transform="rotate(0, 37.781, 28.839)"
83
+ id="ray-4"
84
+ inkscape:label="click ray 4" />
85
+ <path
86
+ d="m 39.250942,32.345819 3.848139,-1.881008 a 1.1305858,1.1305858 0 0 1 0.99377,2.031088 l -3.849503,1.881389 a 1.1304574,1.1304574 0 0 1 -0.992406,-2.031469 z"
87
+ fill-rule="nonzero"
88
+ transform="rotate(0, 41.672, 32.421)"
89
+ id="ray-5"
90
+ inkscape:label="click ray 5" />
91
+ </svg>
@@ -1,197 +0,0 @@
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==
@@ -1,119 +0,0 @@
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==