plexora 0.0.1__py3-none-any.whl

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 (248) hide show
  1. plexora/__init__.py +129 -0
  2. plexora/__main__.py +15 -0
  3. plexora/_url.py +105 -0
  4. plexora/api/__init__.py +55 -0
  5. plexora/api/dataset.py +398 -0
  6. plexora/api/http.py +19 -0
  7. plexora/api/plugin.py +541 -0
  8. plexora/api/store.py +221 -0
  9. plexora/cli.py +777 -0
  10. plexora/client/dist/354_bundle.js +2 -0
  11. plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
  12. plexora/client/dist/418_bundle.js +2 -0
  13. plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
  14. plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
  15. plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
  16. plexora/client/dist/770_bundle.js +1 -0
  17. plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
  18. plexora/client/dist/vendor_bundle.js +2 -0
  19. plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
  20. plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
  21. plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
  22. plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
  23. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
  24. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
  25. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
  26. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
  27. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
  28. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
  29. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
  30. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
  31. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
  32. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
  33. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
  34. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
  35. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
  36. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
  37. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
  38. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
  39. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
  40. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
  41. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
  42. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
  43. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
  44. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
  45. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
  46. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
  47. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
  48. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
  49. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
  50. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
  51. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
  52. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
  53. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
  54. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
  55. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
  56. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
  57. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
  58. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
  59. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
  60. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
  61. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
  62. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
  63. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
  64. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
  65. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
  66. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
  67. plexora/client/src/css/import.css +474 -0
  68. plexora/client/src/css/main.css +903 -0
  69. plexora/client/src/css/openProject.css +363 -0
  70. plexora/client/src/css/quickView.css +166 -0
  71. plexora/client/src/css/tokens.css +51 -0
  72. plexora/client/src/css/viewer.css +1655 -0
  73. plexora/client/src/img/apple-touch-icon.png +0 -0
  74. plexora/client/src/img/favicon.ico +0 -0
  75. plexora/client/src/img/logo.ai +8892 -33
  76. plexora/client/src/img/logo.svg +1 -0
  77. plexora/client/src/img/logo_with_text.ai +9011 -34
  78. plexora/client/src/img/logo_with_text.svg +1 -0
  79. plexora/client/src/js/main.js +704 -0
  80. plexora/client/src/js/pluginRegistry.js +108 -0
  81. plexora/client/src/js/services/appStatus.js +286 -0
  82. plexora/client/src/js/services/browsePicker.js +73 -0
  83. plexora/client/src/js/services/dataLayer.js +400 -0
  84. plexora/client/src/js/services/datasetContext.js +143 -0
  85. plexora/client/src/js/services/glRenderer.js +143 -0
  86. plexora/client/src/js/services/importFormValidation.js +257 -0
  87. plexora/client/src/js/services/numericData.js +100 -0
  88. plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
  89. plexora/client/src/js/services/simpleEventHandler.js +25 -0
  90. plexora/client/src/js/vendor.js +29 -0
  91. plexora/client/src/js/views/channelList.js +663 -0
  92. plexora/client/src/js/views/colorSwatchPicker.js +202 -0
  93. plexora/client/src/js/views/columnClassifier.js +146 -0
  94. plexora/client/src/js/views/coordinateField.js +133 -0
  95. plexora/client/src/js/views/dataSourceField.js +276 -0
  96. plexora/client/src/js/views/imageViewer.js +3306 -0
  97. plexora/client/src/js/views/miniMap.js +787 -0
  98. plexora/client/src/js/views/navbarControls.js +178 -0
  99. plexora/client/src/js/views/openProjectPage.js +239 -0
  100. plexora/client/src/js/views/projectEdit.js +335 -0
  101. plexora/client/src/js/views/quickViewLanding.js +136 -0
  102. plexora/client/src/js/views/rainbow.js +326 -0
  103. plexora/client/src/js/views/requirementsModal.js +572 -0
  104. plexora/client/src/js/views/rgbImageViewer.js +115 -0
  105. plexora/client/src/js/views/searchableSelect.js +411 -0
  106. plexora/client/src/js/views/segmentationProgress.js +122 -0
  107. plexora/client/src/js/views/toolLoader.js +747 -0
  108. plexora/client/src/js/views/viewerControls.js +689 -0
  109. plexora/client/src/js/views/viewerManager.js +434 -0
  110. plexora/client/src/js/views/viewerSidebar.js +1117 -0
  111. plexora/client/src/js/workers/tileDecoder.js +130 -0
  112. plexora/client/src/shaders/frag.glsl +528 -0
  113. plexora/client/src/shaders/vert.glsl +12 -0
  114. plexora/client/templates/base.html +212 -0
  115. plexora/client/templates/index.html +238 -0
  116. plexora/client/templates/open_project.html +96 -0
  117. plexora/client/templates/project_columns.html +86 -0
  118. plexora/client/templates/project_edit.html +196 -0
  119. plexora/client/templates/upload.html +118 -0
  120. plexora/connect.py +528 -0
  121. plexora/datasource.py +782 -0
  122. plexora/jupyter.py +406 -0
  123. plexora/notebook_env.py +232 -0
  124. plexora/paths.py +486 -0
  125. plexora/plugins/__init__.py +12 -0
  126. plexora/plugins/cell_explorer/__init__.py +100 -0
  127. plexora/plugins/cell_explorer/server/__init__.py +11 -0
  128. plexora/plugins/cell_explorer/server/routes.py +167 -0
  129. plexora/plugins/cell_explorer/server/state.py +281 -0
  130. plexora/plugins/cell_explorer/server/values.py +145 -0
  131. plexora/plugins/cell_explorer/server/variables.py +367 -0
  132. plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
  133. plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
  134. plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
  135. plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
  136. plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
  137. plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
  138. plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
  139. plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
  140. plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
  141. plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
  142. plexora/plugins/figure_builder/__init__.py +125 -0
  143. plexora/plugins/figure_builder/server/__init__.py +7 -0
  144. plexora/plugins/figure_builder/server/compose.py +365 -0
  145. plexora/plugins/figure_builder/server/export.py +784 -0
  146. plexora/plugins/figure_builder/server/export_jobs.py +148 -0
  147. plexora/plugins/figure_builder/server/operations.py +556 -0
  148. plexora/plugins/figure_builder/server/pixels.py +142 -0
  149. plexora/plugins/figure_builder/server/provenance.py +174 -0
  150. plexora/plugins/figure_builder/server/render.py +278 -0
  151. plexora/plugins/figure_builder/server/repository.py +701 -0
  152. plexora/plugins/figure_builder/server/routes.py +594 -0
  153. plexora/plugins/figure_builder/server/schema.py +1023 -0
  154. plexora/plugins/figure_builder/server/sources.py +120 -0
  155. plexora/plugins/figure_builder/server/textmetrics.py +155 -0
  156. plexora/plugins/figure_builder/static/figureActions.js +363 -0
  157. plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
  158. plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
  159. plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
  160. plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
  161. plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
  162. plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
  163. plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
  164. plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
  165. plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
  166. plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
  167. plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
  168. plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
  169. plexora/plugins/figure_builder/static/figureRichText.js +634 -0
  170. plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
  171. plexora/plugins/figure_builder/static/figureSchema.js +218 -0
  172. plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
  173. plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
  174. plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
  175. plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
  176. plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
  177. plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
  178. plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
  179. plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
  180. plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
  181. plexora/plugins/gating/__init__.py +76 -0
  182. plexora/plugins/gating/server/__init__.py +0 -0
  183. plexora/plugins/gating/server/anndata_gates.py +367 -0
  184. plexora/plugins/gating/server/database.py +16 -0
  185. plexora/plugins/gating/server/model.py +320 -0
  186. plexora/plugins/gating/server/routes.py +235 -0
  187. plexora/plugins/gating/static/csvGatingList.js +947 -0
  188. plexora/plugins/gating/static/gating.css +289 -0
  189. plexora/plugins/gating/static/gatingApi.js +221 -0
  190. plexora/plugins/gating/static/gatingSidebarController.js +513 -0
  191. plexora/plugins/gating/templates/gating/legacy.html +26 -0
  192. plexora/plugins/gating/templates/gating/panel.html +69 -0
  193. plexora/plugins/roi/__init__.py +95 -0
  194. plexora/plugins/roi/server/__init__.py +8 -0
  195. plexora/plugins/roi/server/adapters.py +655 -0
  196. plexora/plugins/roi/server/geojson.py +267 -0
  197. plexora/plugins/roi/server/geometry.py +181 -0
  198. plexora/plugins/roi/server/mapping.py +169 -0
  199. plexora/plugins/roi/server/operations.py +293 -0
  200. plexora/plugins/roi/server/repository.py +215 -0
  201. plexora/plugins/roi/server/routes.py +409 -0
  202. plexora/plugins/roi/server/schema.py +323 -0
  203. plexora/plugins/roi/static/roi.css +583 -0
  204. plexora/plugins/roi/static/roiApi.js +177 -0
  205. plexora/plugins/roi/static/roiFigureBridge.js +107 -0
  206. plexora/plugins/roi/static/roiGeometry.js +350 -0
  207. plexora/plugins/roi/static/roiRenderer.js +278 -0
  208. plexora/plugins/roi/static/roiSidebarController.js +1038 -0
  209. plexora/plugins/roi/static/roiState.js +518 -0
  210. plexora/plugins/roi/static/roiTools.js +993 -0
  211. plexora/plugins/roi/templates/roi/panel.html +240 -0
  212. plexora/proxy.py +57 -0
  213. plexora/server/models/adapters/__init__.py +94 -0
  214. plexora/server/models/adapters/anndata_adapter.py +388 -0
  215. plexora/server/models/adapters/base.py +93 -0
  216. plexora/server/models/adapters/classify.py +192 -0
  217. plexora/server/models/adapters/csv_adapter.py +105 -0
  218. plexora/server/models/adapters/inspection.py +265 -0
  219. plexora/server/models/adapters/spatialdata_adapter.py +248 -0
  220. plexora/server/models/centroid_tiles.py +305 -0
  221. plexora/server/models/data_model.py +1659 -0
  222. plexora/server/models/database_model.py +169 -0
  223. plexora/server/models/project.py +1270 -0
  224. plexora/server/plugins.py +199 -0
  225. plexora/server/routes/browse_routes.py +41 -0
  226. plexora/server/routes/data_routes.py +294 -0
  227. plexora/server/routes/import_routes.py +603 -0
  228. plexora/server/routes/page_routes.py +139 -0
  229. plexora/server/routes/project_routes.py +438 -0
  230. plexora/server/routes/quick_view_routes.py +62 -0
  231. plexora/server/routes/system_routes.py +39 -0
  232. plexora/server/routes/tool_routes.py +418 -0
  233. plexora/server/utils/addHEColumns.py +11 -0
  234. plexora/server/utils/fast_png.py +97 -0
  235. plexora/server/utils/fullConversion.py +50 -0
  236. plexora/server/utils/native_dialog.py +168 -0
  237. plexora/server/utils/pre_normalization.py +47 -0
  238. plexora/server/utils/segmentation_pyramid.py +598 -0
  239. plexora/server/utils/smallestenclosingcircle.py +126 -0
  240. plexora/server/utils/tiffsurgeon.py +372 -0
  241. plexora/server_cli.py +48 -0
  242. plexora/test.py +0 -0
  243. plexora-0.0.1.dist-info/METADATA +341 -0
  244. plexora-0.0.1.dist-info/RECORD +248 -0
  245. plexora-0.0.1.dist-info/WHEEL +5 -0
  246. plexora-0.0.1.dist-info/entry_points.txt +12 -0
  247. plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
  248. plexora-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Tile decode worker.
3
+ *
4
+ * Turns one compressed WebP channel tile into the single-channel Uint8Array the
5
+ * WebGL colorize pass uploads as an R8UI texture.
6
+ *
7
+ * This runs off the main thread because at high channel counts the decode is
8
+ * the dominant cost of panning. A CPU profile of a 15-channel pan attributed
9
+ * ~60% of all time to this work when it ran inline on the main thread:
10
+ * handleTileLoaded 24.6%, getImageData 23.5%, plus Blob/createImageBitmap/
11
+ * bitmap.close and the garbage collection of the ~3 MB of intermediates each
12
+ * tile allocates. At 7 channels the same work was only ~2%, which is why it
13
+ * initially looked not worth moving -- it scales with the number of tiles
14
+ * streaming in, i.e. with the channel count.
15
+ *
16
+ * createImageBitmap alone was already off-thread; what was not is everything
17
+ * after it -- the canvas draw, the getImageData readback, and the per-pixel
18
+ * loop over 1,048,576 pixels for a 1024x1024 tile.
19
+ *
20
+ * Only the default 8-bit WebP path lives here. The HD 16-bit and segmentation
21
+ * paths decode via UPNG.js, which is loaded as a plain global script and is not
22
+ * reachable from a worker; they also run far less often (HD is a parked-view
23
+ * mode, segmentation is a single layer rather than one per channel).
24
+ */
25
+
26
+ /**
27
+ * Decode a 16-bit grayscale PNG (the HD channel path) into its raw big-endian
28
+ * sample bytes, using the browser's native inflate instead of UPNG.js/pako.
29
+ *
30
+ * A CPU profile of a 7-channel HD pan put 81% of ALL time in pako's JavaScript
31
+ * inflate -- inflate_fast alone was 71% -- with UPNG's unfiltering another 7%.
32
+ * DecompressionStream does the same job in native code, and doing it here keeps
33
+ * it off the main thread entirely.
34
+ *
35
+ * Only handles what fast_png.py actually emits: non-interlaced, filter type 0
36
+ * on every scanline. Anything else returns null so the caller can fall back to
37
+ * UPNG on the main thread.
38
+ */
39
+ async function decodeGray16Png(buffer) {
40
+ const bytes = new Uint8Array(buffer);
41
+ const view = new DataView(buffer);
42
+ // PNG signature
43
+ if (bytes.length < 8 || bytes[0] !== 0x89 || bytes[1] !== 0x50) {
44
+ return null;
45
+ }
46
+ let pos = 8;
47
+ let width = 0, height = 0, bitDepth = 0, colorType = 0, interlace = 0;
48
+ const idat = [];
49
+ while (pos + 8 <= bytes.length) {
50
+ const length = view.getUint32(pos);
51
+ const type = String.fromCharCode(bytes[pos + 4], bytes[pos + 5], bytes[pos + 6], bytes[pos + 7]);
52
+ if (type === "IHDR") {
53
+ width = view.getUint32(pos + 8);
54
+ height = view.getUint32(pos + 12);
55
+ bitDepth = bytes[pos + 16];
56
+ colorType = bytes[pos + 17];
57
+ interlace = bytes[pos + 20];
58
+ } else if (type === "IDAT") {
59
+ idat.push(bytes.subarray(pos + 8, pos + 8 + length));
60
+ } else if (type === "IEND") {
61
+ break;
62
+ }
63
+ pos += 12 + length;
64
+ }
65
+ if (bitDepth !== 16 || colorType !== 0 || interlace !== 0 || !idat.length) {
66
+ return null;
67
+ }
68
+
69
+ // IDAT carries a zlib-wrapped stream, so "deflate" (not "deflate-raw").
70
+ const stream = new Blob(idat).stream().pipeThrough(new DecompressionStream("deflate"));
71
+ const inflated = new Uint8Array(await new Response(stream).arrayBuffer());
72
+
73
+ const stride = width * 2; // 16-bit grayscale = 2 bytes per pixel
74
+ if (inflated.length < height * (stride + 1)) {
75
+ return null;
76
+ }
77
+ const array = new Uint8Array(height * stride);
78
+ for (let row = 0; row < height; row++) {
79
+ const src = row * (stride + 1);
80
+ if (inflated[src] !== 0) {
81
+ return null; // a real filter we are not equipped to undo
82
+ }
83
+ array.set(inflated.subarray(src + 1, src + 1 + stride), row * stride);
84
+ }
85
+ return { array, width, height };
86
+ }
87
+
88
+ async function decodeWebp(buffer) {
89
+ const blob = new Blob([buffer], { type: "image/webp" });
90
+ // premultiplyAlpha/colorSpaceConversion "none" keep the stored byte
91
+ // values exact -- these tiles are quantized measurements, not photos,
92
+ // so any colour management would corrupt them.
93
+ const bitmap = await createImageBitmap(blob, {
94
+ premultiplyAlpha: "none",
95
+ colorSpaceConversion: "none",
96
+ });
97
+ const { width, height } = bitmap; // capture before close() zeroes them
98
+ const canvas = new OffscreenCanvas(width, height);
99
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
100
+ ctx.drawImage(bitmap, 0, 0);
101
+ const rgba = ctx.getImageData(0, 0, width, height).data;
102
+ bitmap.close();
103
+
104
+ // The server writes a grayscale value into every colour channel, so
105
+ // taking R reconstructs the original single-channel tile.
106
+ const array = new Uint8Array(width * height);
107
+ for (let i = 0; i < array.length; i++) {
108
+ array[i] = rgba[i * 4];
109
+ }
110
+ return { array, width, height };
111
+ }
112
+
113
+ self.onmessage = async (event) => {
114
+ const { id, buffer, kind } = event.data;
115
+ try {
116
+ const decoded = kind === "gray16png"
117
+ ? await decodeGray16Png(buffer)
118
+ : await decodeWebp(buffer);
119
+ if (!decoded) {
120
+ // Unsupported PNG shape -- tell the caller to decode it itself
121
+ // rather than guessing.
122
+ self.postMessage({ id, ok: false, unsupported: true, error: "unsupported PNG variant" });
123
+ return;
124
+ }
125
+ // Transfer the output (1-2 MB for a 1024x1024 tile) rather than copying.
126
+ self.postMessage({ id, ok: true, ...decoded }, [decoded.array.buffer]);
127
+ } catch (error) {
128
+ self.postMessage({ id, ok: false, error: String(error) });
129
+ }
130
+ };
@@ -0,0 +1,528 @@
1
+ #version 300 es
2
+ precision highp int;
3
+ precision highp float;
4
+ precision highp usampler2D;
5
+
6
+ uniform usampler2D u_ids;
7
+ uniform usampler2D u_tile;
8
+ uniform sampler2D u_cell_ranges;
9
+ uniform sampler2D u_centers;
10
+ uniform usampler2D u_pickings;
11
+ uniform sampler2D u_mag_0;
12
+ uniform sampler2D u_mag_1;
13
+ uniform sampler2D u_mag_2;
14
+ uniform sampler2D u_mag_3;
15
+ uniform ivec2 u_ids_shape;
16
+ uniform vec2 u_tile_shape;
17
+ uniform ivec2 u_cell_range_shape;
18
+ uniform ivec3 u_center_shape;
19
+ uniform ivec2 u_picking_shape;
20
+ uniform ivec2 u_magnitude_shape;
21
+ uniform ivec4 u_marker_sample;
22
+
23
+ uniform float u_tile_fraction;
24
+ uniform float u_tile_scale;
25
+ uniform float u_pie_radius;
26
+ uniform vec2 u_tile_origin;
27
+ uniform vec2 u_tile_range;
28
+ uniform vec3 u_tile_color;
29
+ uniform bvec2 u_draw_mode;
30
+ uniform vec2 u_x_bounds;
31
+ uniform vec2 u_y_bounds;
32
+ uniform int u_tile_fmt;
33
+ uniform int u_picked_end;
34
+ uniform int u_id_end;
35
+
36
+ in vec2 uv;
37
+ out vec4 color;
38
+
39
+ // Fixed maximum number of ids
40
+ const uint MAX = uint(16384) * uint(16384);
41
+ const uint bMAX = uint(ceil(log2(float(MAX))));
42
+ // Fixed range of radians
43
+ const vec2 TAU = vec2(0., 6.2831853);
44
+ const float PI = 3.14159265;
45
+ // Fixed maximum number of channels
46
+ const int kMAX = 4;
47
+
48
+ // square given float
49
+ float pow2(float v) {
50
+ return v * v;
51
+ }
52
+
53
+ // difference
54
+ float dx(vec2 v) {
55
+ return v[1] - v[0];
56
+ }
57
+
58
+ // rgba to one integer
59
+ uint unpack(uvec4 id) {
60
+ return id.x + uint(256)*id.y + uint(65536)*id.z + uint(16777216)*id.w;
61
+ }
62
+
63
+ // Check if value between min and max
64
+ bool check_range(float value, vec2 range) {
65
+ return value >= range.x && value <= range.y;
66
+ }
67
+
68
+ // Interpolate between domain and range
69
+ float linear(vec2 ran, float dom, float x) {
70
+ float b = ran[0];
71
+ float m = dx(ran) / dom;
72
+ return m * float(clamp(x, 0., dom)) + b;
73
+ }
74
+
75
+ // From screen to local tile coordinates
76
+ vec2 screen_to_tile(vec2 screen) {
77
+ float x = linear(u_x_bounds, 1., screen.x);
78
+ float y = linear(u_y_bounds, 1., screen.y);
79
+ return vec2(x, y) * u_tile_shape;
80
+ }
81
+
82
+ // From global to local tile coordinates
83
+ vec2 global_to_tile(vec2 v) {
84
+ vec2 c = v / u_tile_scale - u_tile_origin;
85
+ return vec2(c.x, u_tile_shape.y - c.y);
86
+ }
87
+
88
+ // Check if values in array match
89
+ bool check_same(uvec4 arr, ivec2 ii) {
90
+ if (arr[ii.x] == arr[ii.y]) return true;
91
+ return false;
92
+ }
93
+ bool check_same(uvec4 arr, ivec3 ii) {
94
+ if (arr[ii.x] == arr[ii.y]) return true;
95
+ if (arr[ii.y] == arr[ii.z]) return true;
96
+ if (arr[ii.x] == arr[ii.z]) return true;
97
+ return false;
98
+ }
99
+
100
+ // Float ratio of two integers
101
+ float division(int ai, int bi) {
102
+ return float(ai) / float(bi);
103
+ }
104
+
105
+ // Float modulo of two integers
106
+ float modulo(uint ai, uint bi) {
107
+ return mod(float(ai), float(bi));
108
+ }
109
+
110
+ // Turn 2D ratios to coordinates for 2D texture
111
+ vec2 to_texture_xy(vec2 s, float x, float y) {
112
+ return vec2((x + 0.5) / s.x, 1.0 - (y + 0.5) / s.y);
113
+ }
114
+
115
+ // Turn 2D integers to coordinates for wrapped texture
116
+ // Note: needed for 2D textures larger than normal limits
117
+ vec2 to_flat_texture_xy(ivec3 size, int cell_index, int d) {
118
+ uint idx = uint(cell_index * size.z + d);
119
+ float idx_x = modulo(idx, uint(size.x));
120
+ float idx_y = floor(division(int(idx), size.x));
121
+ return to_texture_xy(vec2(size.xy), idx_x, idx_y);
122
+ }
123
+
124
+ // Access cell center at cell index
125
+ vec2 sample_center(int cell_index) {
126
+ ivec3 size = u_center_shape;
127
+ vec2 center_x = to_flat_texture_xy(size, cell_index, 0);
128
+ vec2 center_y = to_flat_texture_xy(size, cell_index, 1);
129
+ float cx = texture(u_centers, center_x).r;
130
+ float cy = texture(u_centers, center_y).r;
131
+ return vec2(cx, cy);
132
+ }
133
+
134
+ // Access marker key magnitude at cell index
135
+ float sample_magnitude(int cell_index, int key) {
136
+ ivec3 size = ivec3(u_magnitude_shape, 1);
137
+ vec2 idx_2d = to_flat_texture_xy(size, cell_index, 0);
138
+ // Use any of available samplers
139
+ if (u_marker_sample[0] == key) {
140
+ return texture(u_mag_0, idx_2d).r;
141
+ }
142
+ else if (u_marker_sample[1] == key) {
143
+ return texture(u_mag_1, idx_2d).r;
144
+ }
145
+ else if (u_marker_sample[2] == key) {
146
+ return texture(u_mag_2, idx_2d).r;
147
+ }
148
+ else if (u_marker_sample[3] == key) {
149
+ return texture(u_mag_3, idx_2d).r;
150
+ }
151
+ else {
152
+ return -1.;
153
+ }
154
+ }
155
+
156
+ // Access marker key gating parameter
157
+ float sample_cell_range(float param, float key) {
158
+ vec2 size = vec2(u_cell_range_shape);
159
+ vec2 idx_2d = to_texture_xy(size, param, key);
160
+ return texture(u_cell_ranges, idx_2d).r;
161
+ }
162
+
163
+ // Access marker key gated min/max
164
+ vec2 sample_cell_range_bounds(float key) {
165
+ float min_range = sample_cell_range(0., key);
166
+ float max_range = sample_cell_range(1., key);
167
+ return vec2(min_range, max_range);
168
+ }
169
+
170
+ // Access marker key gated color
171
+ vec3 sample_cell_range_color(float key) {
172
+ float r = sample_cell_range(2., key);
173
+ float g = sample_cell_range(3., key);
174
+ float b = sample_cell_range(4., key);
175
+ return vec3(r, g, b);
176
+ }
177
+
178
+ // Check if angle is within one evenly divided slice
179
+ bool match_angle(int count, int total, float angle) {
180
+ float rad_min = linear(TAU, float(total), float(count) - 1.);
181
+ float rad_max = linear(TAU, float(total), float(count));
182
+ vec2 rad_range = vec2(rad_min, rad_max);
183
+ return check_range(angle, rad_range);
184
+ }
185
+
186
+ // Access cell id at cell index
187
+ uint sample_id(usampler2D sam, ivec2 size, uint idx) {
188
+ float idx_x = modulo(idx, uint(size.x));
189
+ float idx_y = floor(division(int(idx), int(size.x)));
190
+ vec2 ids_idx = to_texture_xy(vec2(size), idx_x, idx_y);
191
+ uvec4 m_value = texture(sam, ids_idx);
192
+ return unpack(m_value);
193
+ }
194
+
195
+ // Sample texture at given texel offset
196
+ uvec4 offset(usampler2D sam, vec2 size, vec2 pos, vec2 off) {
197
+ float x_pos = pos.x + off.x / size.x;
198
+ float y_pos = pos.y + off.y / size.y;
199
+ float x = linear(u_x_bounds, 1., x_pos);
200
+ float y = linear(u_y_bounds, 1., y_pos);
201
+ return texture(sam, vec2(x, y));
202
+ }
203
+
204
+ // Generic binary search of ids
205
+ int binary_search(usampler2D sam, ivec2 size, uint last, uint ikey) {
206
+ uint first = uint(0);
207
+ // Search within log(n) runtime
208
+ for (uint i = uint(0); i <= bMAX; i++) {
209
+ // Evaluate the midpoint
210
+ uint mid = (first + last) / uint(2);
211
+ uint here = sample_id(sam, size, mid);
212
+
213
+ // Break if list gone
214
+ if (first == last && ikey != here) {
215
+ break;
216
+ }
217
+
218
+ // Search below midpoint
219
+ if (here > ikey) last = mid;
220
+
221
+ // Search above midpoint
222
+ else if (ikey > here) first = mid;
223
+
224
+ // Found at midpoint
225
+ else return int(mid);
226
+ }
227
+ // Return -1 if id not in list
228
+ return -1;
229
+ }
230
+
231
+ // Sample index of cell at given offset
232
+ // Note: will be -1 if cell not in cell list
233
+ int sample_cell_index(vec2 off) {
234
+ // Find cell id at given offset
235
+ uint ikey = unpack(offset(u_tile, u_tile_shape, uv, off));
236
+
237
+ // Array size
238
+ // Return -1 if background
239
+ if (ikey == uint(0)) {
240
+ return -1;
241
+ }
242
+
243
+ uint n_i = uint(u_id_end);
244
+ uint n_p = uint(max(u_picked_end, 0));
245
+
246
+ // Use any possible id
247
+ if (u_picked_end < 0) {
248
+ return binary_search(u_ids, u_ids_shape, n_i, ikey);
249
+ }
250
+ // Use externally picked id
251
+ int is_picked = binary_search(u_pickings, u_picking_shape, n_p, ikey);
252
+ if (is_picked > -1) {
253
+ return binary_search(u_ids, u_ids_shape, n_i, ikey);
254
+ }
255
+ return -1;
256
+ }
257
+
258
+ // Limit to available marker keys
259
+ bool catch_key(int key) {
260
+ if (key >= u_cell_range_shape.y) {
261
+ return true;
262
+ }
263
+ return false;
264
+ }
265
+
266
+ // Count total OR-mode pie chart slices
267
+ int count_gated_keys(int cell_index) {
268
+ int gated_total = 0;
269
+ for (int key = 0; key <= kMAX; key++) {
270
+ if (catch_key(key)) {
271
+ return gated_total;
272
+ }
273
+ float scale = sample_magnitude(cell_index, key);
274
+ vec2 range = sample_cell_range_bounds(float(key));
275
+ if (check_range(scale, range)) {
276
+ gated_total = gated_total + 1;
277
+ }
278
+ }
279
+ return gated_total;
280
+ }
281
+
282
+ float dist (vec2 v) {
283
+ return sqrt(pow2(v.x) + pow2(v.y));
284
+ }
285
+
286
+ // Match channel to cell index
287
+ int to_and_gate(int cell_index) {
288
+ // Check each possible key for color
289
+ for (int key = 0; key <= kMAX; key++) {
290
+ if (catch_key(key)) {
291
+ return 0;
292
+ }
293
+ float scale = sample_magnitude(cell_index, key);
294
+ vec2 range = sample_cell_range_bounds(float(key));
295
+ if (!check_range(scale, range)) {
296
+ return -1;
297
+ }
298
+ }
299
+ return 0;
300
+ }
301
+
302
+ // Return angle within pie chart
303
+ float to_chart_angle(int cell_index, float radius) {
304
+ if (cell_index < 0) {
305
+ return -1.;
306
+ }
307
+ float pie_radius = radius * u_tile_fraction;
308
+ vec2 global_center = sample_center(cell_index);
309
+ vec2 center = global_to_tile(global_center);
310
+ vec2 pos = screen_to_tile(uv);
311
+ vec2 delta = pos - center;
312
+
313
+ if (dist(delta) > pie_radius) {
314
+ return -1.;
315
+ }
316
+ float rad = atan(delta.y, delta.x);
317
+ float angle = mod(rad + PI, TAU.y);
318
+ return angle;
319
+ }
320
+
321
+ // Match channel to cell index
322
+ int to_or_gate(int cell_index, float radius) {
323
+ float angle = 0.0;
324
+ int gated_count = 0;
325
+ int gated_total = count_gated_keys(cell_index);
326
+ // Outside of circle
327
+ if (radius > 0.0) {
328
+ angle = to_chart_angle(cell_index, radius);
329
+ if (angle < 0.0) {
330
+ return -1;
331
+ }
332
+ }
333
+ // Check each possible key for color
334
+ for (int key = 0; key <= kMAX; key++) {
335
+ if (catch_key(key)) {
336
+ return -1;
337
+ }
338
+ float scale = sample_magnitude(cell_index, key);
339
+ vec2 range = sample_cell_range_bounds(float(key));
340
+ if (check_range(scale, range)) {
341
+ gated_count = gated_count + 1;
342
+ if (radius <= 0.0) {
343
+ return key;
344
+ }
345
+ if (match_angle(gated_count, gated_total, angle)) {
346
+ return key;
347
+ }
348
+ }
349
+ }
350
+ return -1;
351
+ }
352
+
353
+ // Match channel to cell index
354
+ int to_gate(int cell_index, bvec2 mode, float radius) {
355
+ int n_gates = u_cell_range_shape.y;
356
+ if (n_gates < 1 || cell_index < 0) {
357
+ return -1;
358
+ }
359
+ bool or_mode = mode.y;
360
+ if (or_mode) {
361
+ return to_or_gate(cell_index, radius);
362
+ }
363
+ else {
364
+ return to_and_gate(cell_index);
365
+ }
366
+ }
367
+
368
+ // Sample gating state of cell at given offset
369
+ int sample_cell_gate(vec2 off, bvec2 mode, float radius) {
370
+ int cell_index = sample_cell_index(off);
371
+ return to_gate(cell_index, mode, radius);
372
+ }
373
+
374
+ // Sample gated cell index at given offset
375
+ int gate_cell_index(vec2 off, bvec2 mode, float radius) {
376
+ if (u_cell_range_shape.y < 1 && mode.x) {
377
+ return sample_cell_index(off);
378
+ }
379
+ int cell_gate = sample_cell_gate(off, mode, radius);
380
+ if (cell_gate == -1) {
381
+ return -1;
382
+ }
383
+ return sample_cell_index(off);
384
+ }
385
+
386
+ bool in_diff(usampler2D sam, vec2 pos, float one, bvec2 mode) {
387
+ int cell_idx = gate_cell_index(vec2(0., 0.), mode, -1.0);
388
+
389
+ int right = gate_cell_index(vec2(1. * one, 0.), mode, -1.0);
390
+ int left = gate_cell_index(vec2(-1. * one, 0.), mode, -1.0);
391
+ int down = gate_cell_index(vec2(0., -1. * one), mode, -1.0);
392
+ int top = gate_cell_index(vec2(0., 1. * one), mode, -1.0);
393
+
394
+ bool right_d = right != cell_idx;
395
+ bool left_d = left != cell_idx;
396
+ bool down_d = down != cell_idx;
397
+ bool top_d = top != cell_idx;
398
+
399
+ // Avoid double counting when zoomed in
400
+ if (one > 1.0 && cell_idx != -1) {
401
+ if (right == -1 || left == -1 || down == -1 || top == -1) {
402
+ return false;
403
+ }
404
+ }
405
+ // Count any border of gated cell
406
+ return (right_d || left_d || down_d || top_d);
407
+ }
408
+
409
+ bool equals4(uvec4 id1, uvec4 id2) {
410
+ return all(equal(id1, id2));
411
+ }
412
+
413
+ // Check if pixel is on a border
414
+ bool near_cell_edge(float one, bvec2 mode) {
415
+ uvec4 empty_val = uvec4(0., 0., 0., 0.);
416
+ int cell_index = sample_cell_index(vec2(0, 0));
417
+ uvec4 pixel = offset(u_tile, u_tile_shape, uv, vec2(0., 0.));
418
+ bool background = equals4(empty_val, pixel);
419
+
420
+ // Background not counted
421
+ if (one == 1.0 && background) {
422
+ return false;
423
+ }
424
+ // pixels are at different cells
425
+ return in_diff(u_tile, uv, one, mode);
426
+ }
427
+
428
+ // Colorize discrete u32 signal
429
+ vec4 u32_rgba_map(bvec2 mode) {
430
+ vec4 empty_pixel = vec4(0., 0., 0., 0.);
431
+ vec4 white_pixel = vec4(1., 1., 1., 1.);
432
+ bool edge_mode = mode.x;
433
+ bool or_mode = mode.y;
434
+ if (!edge_mode) {
435
+ return empty_pixel;
436
+ }
437
+
438
+ int cell_index = sample_cell_index(vec2(0., 0.));
439
+ if (cell_index < 0) {
440
+ return empty_pixel;
441
+ }
442
+
443
+ if (!or_mode) {
444
+ return white_pixel;
445
+ }
446
+
447
+ if (u_cell_range_shape.y < 1) {
448
+ return white_pixel;
449
+ }
450
+
451
+ int key = to_gate(cell_index, mode, -1.0);
452
+ if (key > -1) {
453
+ return vec4(sample_cell_range_color(float(key)), 1.0);
454
+ }
455
+ return empty_pixel;
456
+ }
457
+
458
+ // Clamp signal between given min/max
459
+ float range_clamp(float value) {
460
+ float min_ = u_tile_range[0];
461
+ float max_ = u_tile_range[1];
462
+ return clamp((value - min_) / (max_ - min_), 0.0, 1.0);
463
+ }
464
+
465
+ // Colorize continuous u16 signal
466
+ vec4 u16_rg_range(float alpha) {
467
+ uvec2 pixel = offset(u_tile, u_tile_shape, uv, vec2(0, 0)).rg;
468
+ float value = float(pixel.r * uint(255) + pixel.g) / 65535.;
469
+
470
+ // Threshhold pixel within range
471
+ float pixel_val = range_clamp(value);
472
+
473
+ // Color pixel value
474
+ vec3 pixel_color = u_tile_color * pixel_val;
475
+ return vec4(pixel_color, alpha);
476
+ }
477
+
478
+ // Colorize a quantized 8-bit signal (the fast/default WebP tile path).
479
+ // The server already quantized the original 16-bit value into [0, 255]
480
+ // (linear against the channel's true max); u_tile_range is expressed in
481
+ // that SAME [0, 255] domain in this mode (see viewerSidebar.js), so the
482
+ // quantized byte can feed range_clamp directly -- no reconstruction back
483
+ // into 16-bit units, and therefore no precision mismatch between the
484
+ // slider's domain and the encoded data's domain.
485
+ vec4 u8_r_range(float alpha) {
486
+ uint pixel = offset(u_tile, u_tile_shape, uv, vec2(0, 0)).r;
487
+ float quant = float(pixel) / 255.;
488
+
489
+ // Threshhold pixel within range
490
+ float pixel_val = range_clamp(quant);
491
+
492
+ // Color pixel value
493
+ vec3 pixel_color = u_tile_color * pixel_val;
494
+ return vec4(pixel_color, alpha);
495
+ }
496
+
497
+ // True while `screen` (quad-space uv) maps inside the tile's actual valid
498
+ // data region. u_x_bounds/u_y_bounds can legitimately extend outside
499
+ // [0, 1] -- e.g. an edge tile stretched past its real coverage while
500
+ // zoomed beyond native resolution (see toMagnifiedBounds in
501
+ // viewerManager.js) -- and outside that range GL's CLAMP_TO_EDGE wrap mode
502
+ // just keeps returning the boundary texel forever. Without this check
503
+ // those out-of-bounds fragments painted a solid smear of that edge
504
+ // texel/color across the rest of the quad instead of showing nothing.
505
+ bool in_tile_bounds(vec2 screen) {
506
+ float x = linear(u_x_bounds, 1., screen.x);
507
+ float y = linear(u_y_bounds, 1., screen.y);
508
+ return x >= 0.0 && x <= 1.0 && y >= 0.0 && y <= 1.0;
509
+ }
510
+
511
+ //
512
+ // Entrypoint
513
+ //
514
+
515
+ void main() {
516
+ if (u_tile_fmt == 32) {
517
+ color = u32_rgba_map(u_draw_mode);
518
+ }
519
+ else if (!in_tile_bounds(uv)) {
520
+ color = vec4(0.);
521
+ }
522
+ else if (u_tile_fmt == 8) {
523
+ color = u8_r_range(0.9);
524
+ }
525
+ else {
526
+ color = u16_rg_range(0.9);
527
+ }
528
+ }
@@ -0,0 +1,12 @@
1
+ #version 300 es
2
+ in vec2 a_uv;
3
+ out vec2 uv;
4
+
5
+ void main() {
6
+ // Texture coordinates
7
+ uv = a_uv;
8
+
9
+ // Clip coordinates
10
+ vec2 full_pos = 2. * a_uv - 1.;
11
+ gl_Position = vec4(full_pos, 0., 1.);
12
+ }