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,400 @@
1
+ //todo add crossfilter stuff here... build some lensingFilters and sorters for individual and combined dimensions
2
+
3
+ class DataLayer {
4
+
5
+ constructor(config, imageChannels) {
6
+ var that = this;
7
+ //vars and consts
8
+ this.config = config;
9
+ //all image channels
10
+ this.imageChannels = imageChannels;
11
+
12
+ this.imageBitRange = [0, 65536];
13
+ //selections
14
+ this.currentSelection = new Map();
15
+ // Roles, not literal column names -- see services/datasetContext.js.
16
+ // Null for an image-only project, and also for one whose coordinate
17
+ // columns nobody has identified yet; nothing needing real coordinates
18
+ // should be reachable in either case (callers check `schema` first).
19
+ this.schema = PlexoraDataset.resolveSchema(this.config);
20
+ this.x = this.schema?.x;
21
+ this.y = this.schema?.y;
22
+ this.phenotypes = [];
23
+ }
24
+
25
+ async init() {
26
+ try {
27
+ await fetch(plexoraUrl('init_database') + '?' + new URLSearchParams({
28
+ datasource: datasource
29
+ }))
30
+
31
+ } catch (e) {
32
+ console.log("Error Initializing Dataset", e);
33
+ }
34
+ }
35
+
36
+ async getRow(row) {
37
+ try {
38
+ let response = await fetch(plexoraUrl('get_database_row') + '?' + new URLSearchParams({
39
+ row: row,
40
+ datasource: datasource
41
+ }))
42
+ let response_data = await response.json();
43
+ return response_data;
44
+ } catch (e) {
45
+ console.log("Error Getting Row", e);
46
+ }
47
+ }
48
+
49
+ async getSavedChannelList() {
50
+ try {
51
+ let response = await fetch(plexoraUrl('get_saved_channel_list') + '?' + new URLSearchParams({
52
+ datasource: datasource
53
+ }))
54
+ let response_data = await response.json();
55
+ return response_data;
56
+ } catch (e) {
57
+ console.log("Error Getting Saved Channel List", e);
58
+ }
59
+ }
60
+
61
+ async saveChannelList(map_channels, active_channels, list_colors, list_ranges, list_channels) {
62
+ const self = this;
63
+ try {
64
+ let response = await fetch(plexoraUrl('save_channel_list'), {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Accept': 'application/json',
68
+ 'Content-Type': 'application/json'
69
+ },
70
+ body: JSON.stringify(
71
+ {
72
+ datasource: datasource,
73
+ map_channels: map_channels,
74
+ active_channels: active_channels,
75
+ list_colors: list_colors,
76
+ list_ranges: list_ranges,
77
+ list_channels: list_channels
78
+ }
79
+ )
80
+ });
81
+ let response_data = await response.json();
82
+ return response_data;
83
+ } catch (e) {
84
+ console.log("Error Saving Channel List", e);
85
+ }
86
+ }
87
+
88
+ async getColumnDistributions(columns) {
89
+ try {
90
+ let response = await fetch(plexoraUrl('get_column_distributions') + '?' + new URLSearchParams({
91
+ columns: columns,
92
+ datasource: datasource
93
+ }))
94
+ let distributions = await response.json();
95
+ return distributions;
96
+ } catch (e) {
97
+ console.log("Error Getting Nearest Cell", e);
98
+ }
99
+ }
100
+
101
+ async submitChannelUpload(formData) {
102
+ try {
103
+ formData.append('datasource', datasource);
104
+ let response = await fetch(plexoraUrl('upload_channels'), {
105
+ method: "POST",
106
+ body: formData
107
+ })
108
+ let cell = await response.json();
109
+ return cell;
110
+ } catch (e) {
111
+ console.log("Error Getting Submitting Form Upload", e);
112
+ }
113
+ }
114
+
115
+ async getAllCells(start_keys, use_integer) {
116
+ const dtype = use_integer ? 'integer' : 'float'
117
+ const base_url = plexoraUrl(`get_all_cells/${dtype}/`) + '?'
118
+ try {
119
+ const headers = new Headers();
120
+ headers.append("Content-Type","application/octet-stream");
121
+ headers.append("Content-Encoding","gzip");
122
+ const response = await fetch(base_url + new URLSearchParams({
123
+ start_keys: start_keys,
124
+ datasource: datasource
125
+ }), {
126
+ headers: headers
127
+ })
128
+ return response.arrayBuffer();
129
+ } catch (e) {
130
+ console.log("Error Getting Gated Cell Ids", e);
131
+ }
132
+ }
133
+
134
+ async getCentroidManifest() {
135
+ try {
136
+ let response = await fetch(plexoraUrl('get_centroid_manifest') + '?' + new URLSearchParams({
137
+ datasource: datasource
138
+ }))
139
+ if (!response.ok) {
140
+ throw new Error(`Centroid manifest request failed: ${response.status}`);
141
+ }
142
+ const text = await response.text();
143
+ if (!text.trim().startsWith("{")) {
144
+ throw new Error("Centroid manifest response was not JSON");
145
+ }
146
+ return JSON.parse(text);
147
+ } catch (e) {
148
+ console.log("Error Getting Centroid Manifest", e);
149
+ return null;
150
+ }
151
+ }
152
+
153
+ async getCentroidTiles(level, tiles, filter = {}, maxPoints = 50000) {
154
+ try {
155
+ let response = await fetch(plexoraUrl('get_centroid_tiles'), {
156
+ method: 'POST',
157
+ headers: {
158
+ 'Accept': 'application/octet-stream',
159
+ 'Content-Type': 'application/json'
160
+ },
161
+ body: JSON.stringify(
162
+ {
163
+ datasource: datasource,
164
+ level: level,
165
+ tiles: tiles,
166
+ filter: filter || {},
167
+ max_points: maxPoints
168
+ }
169
+ )
170
+ });
171
+ if (!response.ok) {
172
+ throw new Error(`Centroid tile request failed: ${response.status}`);
173
+ }
174
+ const contentType = response.headers.get("Content-Type") || "";
175
+ if (!contentType.includes("application/octet-stream")) {
176
+ throw new Error("Centroid tile response was not binary");
177
+ }
178
+ return response.arrayBuffer();
179
+ } catch (e) {
180
+ console.log("Error Getting Centroid Tiles", e);
181
+ return null;
182
+ }
183
+ }
184
+
185
+ async getDatabaseDescription() {
186
+ try {
187
+ let response = await fetch(plexoraUrl('get_database_description') + '?' + new URLSearchParams({
188
+ datasource: datasource
189
+ }))
190
+ let description = await response.json();
191
+ return description;
192
+ } catch (e) {
193
+ console.log("Error Getting DB Description", e);
194
+ }
195
+ }
196
+
197
+ async getSegmentationStatus() {
198
+ try {
199
+ let response = await fetch(plexoraUrl('get_segmentation_status') + '?' + new URLSearchParams({
200
+ datasource: datasource
201
+ }))
202
+ return await response.json();
203
+ } catch (e) {
204
+ console.log("Error Getting Segmentation Status", e);
205
+ }
206
+ }
207
+
208
+ async getChannelGMM(channel) {
209
+ try {
210
+ let response = await fetch(plexoraUrl('get_channel_gmm') + '?' + new URLSearchParams({
211
+ channel: channel,
212
+ datasource: datasource
213
+ }))
214
+ let packet_gmm = await response.json();
215
+ return packet_gmm;
216
+ } catch (e) {
217
+ console.log("Error Getting Channel GMM", e);
218
+ }
219
+ }
220
+
221
+ async getImageChannelStats(channel) {
222
+ try {
223
+ let response = await fetch(plexoraUrl('get_image_channel_stats') + '?' + new URLSearchParams({
224
+ channel: channel,
225
+ datasource: datasource
226
+ }))
227
+ return await response.json();
228
+ } catch (e) {
229
+ console.log("Error Getting Image Channel Stats", e);
230
+ }
231
+ }
232
+
233
+ async getChannelCellIds(sels) {
234
+ try {
235
+ let response = await fetch(plexoraUrl('get_channel_cell_ids') + '?' + new URLSearchParams({
236
+ filter: JSON.stringify(sels),
237
+ datasource: datasource
238
+ }))
239
+ let cellIds = await response.json();
240
+ return cellIds;
241
+ } catch (e) {
242
+ console.log("Error Getting Channel Cell Ids", e);
243
+ }
244
+ }
245
+
246
+ async getChannelNames(shortNames = true) {
247
+ try {
248
+ let response = await fetch(plexoraUrl('get_channel_names') + '?' + new URLSearchParams({
249
+ datasource: datasource,
250
+ shortNames: shortNames
251
+ }))
252
+ let response_data = await response.json();
253
+ return response_data;
254
+ } catch (e) {
255
+ console.log("Error Getting Sample Row", e);
256
+ }
257
+ }
258
+
259
+ async getNearestCell(point_x, point_y) {
260
+ try {
261
+ let response = await fetch(plexoraUrl('get_nearest_cell') + '?' + new URLSearchParams({
262
+ point_x: point_x,
263
+ point_y: point_y,
264
+ datasource: datasource
265
+ }))
266
+ let cell = await response.json();
267
+ return cell;
268
+ } catch (e) {
269
+ console.log("Error Getting Nearest Cell", e);
270
+ }
271
+ }
272
+
273
+
274
+ getCurrentSelection() {
275
+ return this.currentSelection;
276
+ }
277
+
278
+ clearCurrentSelection() {
279
+ this.currentSelection.clear();
280
+ }
281
+
282
+ getImageBitRange(float = false) {
283
+ const self = this;
284
+ if (!float) {
285
+ return self.imageBitRange;
286
+ } else {
287
+ return [0.0, 1.0];
288
+ }
289
+ }
290
+
291
+ addToCurrentSelection(item, allowDelete, clearPriors) {
292
+ // No real per-cell id field for a quick-view (no feature data)
293
+ // datasource -- nothing to key a selection by.
294
+ if (!this.schema) return;
295
+
296
+ // delete item on second click
297
+ if (allowDelete && this.currentSelection.has(item)) {
298
+ this.currentSelection.delete(item);
299
+ if (clearPriors) {
300
+ this.currentSelection.clear();
301
+ }
302
+
303
+ // console.log('current selection size:', this.currentSelection.size);
304
+ if (this.currentSelection.size > 0) {
305
+ // console.log('id: ', this.currentSelection.values().next().value.id);
306
+ }
307
+ return;
308
+ }
309
+
310
+ // clear previous items
311
+ if (clearPriors) {
312
+ this.currentSelection.clear();
313
+ }
314
+
315
+ // add new item
316
+ this.currentSelection.set(item[this.schema.cellId], item);
317
+
318
+ // console.log('current selection size:', this.currentSelection.size);
319
+ if (this.currentSelection.size > 0) {
320
+ // console.log('id: ', this.currentSelection.values().next().value.id);
321
+ }
322
+ }
323
+
324
+
325
+ addAllToCurrentSelection(items, allowDelete, clearPriors) {
326
+ // console.log("update current selection")
327
+ // No real per-cell id field for a quick-view (no feature data)
328
+ // datasource -- nothing to key a selection by.
329
+ if (!this.schema) return;
330
+ var that = this;
331
+ let idField = this.schema.cellId
332
+ that.currentSelection = new Map(items.map(i => [(i[idField]), i]));
333
+ // console.log("update current selection done")
334
+ }
335
+
336
+ isImageFeature(key) {
337
+ if (this.imageChannels.hasOwnProperty(key)
338
+ && key != 'CellId' && key != 'id' && key != 'CellID' && key != 'ID' && key != 'Area') {
339
+ return true;
340
+ }
341
+ return false;
342
+ }
343
+
344
+ getShortChannelName(fullname) {
345
+ var shortname = fullname;
346
+ this.config["imageData"].forEach(function (channel) {
347
+ if (channel.fullname == fullname) {
348
+ shortname = channel.name;
349
+ }
350
+ });
351
+ return shortname;
352
+ }
353
+
354
+ getFullChannelName(shortname) {
355
+ var fullname = shortname;
356
+ this.config["imageData"].forEach(function (channel) {
357
+ if (channel.name == shortname) {
358
+ fullname = channel.fullname;
359
+ }
360
+ });
361
+ return fullname;
362
+ }
363
+
364
+ async getMetadata() {
365
+ try {
366
+ let response = await fetch(plexoraUrl('get_ome_metadata') + '?' + new URLSearchParams({
367
+ datasource: datasource
368
+ }))
369
+ let response_data = await response.json();
370
+ return response_data;
371
+ } catch (e) {
372
+ console.log("Error Getting Metadata", e);
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Decimal-place precision for gate slider rounding, derived from the
378
+ * channel's own observed [min, max] span rather than a stored config
379
+ * flag -- aims for ~200 distinguishable steps across the range. Wide
380
+ * integer-scale channels (e.g. raw 0-65535 pixel intensities) land on 0
381
+ * decimals (whole-number gates); narrow float-scale channels (e.g.
382
+ * already log/arcsinh-transformed markers spanning ~1.7-2.2) get enough
383
+ * decimal places to stay meaningful. Replaces the old
384
+ * dataset.isTransformed flag, which had to be set correctly
385
+ * (and consistently across datasources built from the same source data)
386
+ * at import time and silently broke gating whenever it didn't match the
387
+ * data's actual scale -- this is instead computed fresh from the same
388
+ * min/max every request already returns, so it can't drift out of sync.
389
+ * @param {number[]} range - [min, max] of the channel being gated
390
+ * @returns {number}
391
+ */
392
+ gateDecimals(range) {
393
+ const span = Math.abs(((range && range[1]) || 0) - ((range && range[0]) || 0));
394
+ if (!Number.isFinite(span) || span <= 0) return 0;
395
+ const decimals = Math.ceil(Math.log10(200 / span));
396
+ return Math.max(0, Math.min(6, decimals));
397
+ }
398
+
399
+
400
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * datasetContext.js - the dataset a plugin is handed, client-side.
3
+ *
4
+ * Mirrors plexora/api/dataset.py so a plugin sees the same shape on both
5
+ * sides: image data always, segmentation and a feature table when the project
6
+ * has them, and a role->column map read from the project record.
7
+ *
8
+ * The point is that plugins read ROLES, not column names. `schema.x` resolves
9
+ * to whatever the project recorded for the x role; a plugin that hardcodes
10
+ * "X_centroid" breaks on the next dataset. Roles this version has no field for
11
+ * survive in `extra`, so adding one later needs no plugin change.
12
+ *
13
+ * It also replaces the bare globals plugins used to reach for. csvGatingList
14
+ * read `imageChannels`, `datasource` and `__plexora` straight off window,
15
+ * which meant core could not rename or scope any of them, and a plugin loaded
16
+ * out of order got undefined rather than an error.
17
+ */
18
+ window.PlexoraDataset = (function () {
19
+ // Role name on the wire -> the camelCase name plugins read. The record
20
+ // stores roles under one explicit key (server/models/project.py's
21
+ // ROLE_NAMES), so unlike the old shape there is no alias list and no
22
+ // denylist -- a role can no longer be confused with a file path or a
23
+ // processing flag, because they are not in the same dict any more.
24
+ const ROLE_KEYS = {
25
+ cell_id: "cellId",
26
+ x: "x",
27
+ y: "y",
28
+ celltype: "celltype",
29
+ image_id: "imageId",
30
+ };
31
+
32
+ function resolveSchema(config) {
33
+ const spec = config.dataset;
34
+ if (!spec) return null;
35
+ const roles = spec.roles || {};
36
+ const schema = { extra: {} };
37
+ for (const [wire, name] of Object.entries(ROLE_KEYS)) {
38
+ schema[name] = roles[wire] || null;
39
+ }
40
+ // A role the record has learned but this version has no field for
41
+ // still reaches plugins, so adding one needs no client change.
42
+ for (const [wire, value] of Object.entries(roles)) {
43
+ if (!ROLE_KEYS[wire] && typeof value === "string") schema.extra[wire] = value;
44
+ }
45
+ return schema;
46
+ }
47
+
48
+ /**
49
+ * @param config - this datasource's /config entry
50
+ * @param imageChannels - fullname -> index lookup built by main.js
51
+ * @param databaseDescription - per-column stats, or {} before it arrives
52
+ */
53
+ function build(config, imageChannels, databaseDescription = {}) {
54
+ const schema = resolveSchema(config);
55
+
56
+ // Reserved columns are numeric but are not markers. Kept in step with
57
+ // the server's TableHandle.markers, which likewise reports only
58
+ // columns a histogram could be built for.
59
+ const reserved = new Set(["id"]);
60
+ [schema?.cellId, schema?.x, schema?.y].forEach((c) => c && reserved.add(c));
61
+
62
+ return {
63
+ name: config.name || window.datasource,
64
+ image: {
65
+ channels: (config.imageData || []).filter((c) => c.fullname !== "Area"),
66
+ get channelNames() {
67
+ return this.channels.map((c) => c.fullname);
68
+ },
69
+ kind: config.image_kind || null,
70
+ index: imageChannels || {},
71
+ /** Whether a marker name is also a real image channel. */
72
+ has(fullName) {
73
+ return (imageChannels || {})[fullName] !== undefined;
74
+ },
75
+ },
76
+ // Getters, not values: a mask whose pyramid was still building when
77
+ // the page opened is adopted in place when the job lands (main.js's
78
+ // adoptSegmentation), and a plugin holding this object from before
79
+ // that would otherwise go on being told there is no mask.
80
+ segmentation: {
81
+ get available() {
82
+ return Boolean(config.segmentation);
83
+ },
84
+ get pending() {
85
+ return config.segmentation_status === "pending";
86
+ },
87
+ },
88
+ table: {
89
+ // No dataset block IS the image-only state; there is no
90
+ // separate flag that could disagree with it.
91
+ available: Boolean(config.dataset),
92
+ sourceKind: config.dataset?.type || "csv",
93
+ describe: databaseDescription,
94
+ /**
95
+ * Columns a plugin can threshold or plot -- the classification
96
+ * the project recorded at import, so every plugin sees the same
97
+ * answer. Deliberately not image.channelNames: a structural
98
+ * channel like DNA is a real image channel with no feature
99
+ * column, and conflating the two has caused bugs here.
100
+ *
101
+ * Narrowed to columns the server could describe, because that
102
+ * is what "can be thresholded" actually means: a gate needs a
103
+ * range and a histogram to draw, and a recorded marker the
104
+ * loaded table no longer holds has neither.
105
+ *
106
+ * The derivation is the fallback, not the answer. It cannot
107
+ * tell a stain from a measurement -- Area and Eccentricity are
108
+ * as numeric as CD3 is, and in a CSV they sit in the same
109
+ * header, which is the entire reason the import step asks. It
110
+ * covers a project whose columns were never classified; better
111
+ * a usable guess than an empty panel.
112
+ */
113
+ get markers() {
114
+ const describable = (column) => Boolean(databaseDescription[column]?.histogram);
115
+ const recorded = (config.dataset?.columns?.markers || []).filter(describable);
116
+ if (recorded.length) return recorded;
117
+ return Object.keys(databaseDescription).filter(
118
+ (column) => !reserved.has(column) && describable(column)
119
+ );
120
+ },
121
+ get metadataColumns() {
122
+ return [...(config.dataset?.columns?.metadata || [])];
123
+ },
124
+ },
125
+ schema,
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Whether this project has per-cell positions to draw.
131
+ *
132
+ * Not the same question as "has a feature table": a table whose coordinate
133
+ * columns nobody has identified yet has no positions either, and the
134
+ * server's centroid manifest reports `missing` for both cases. Callers use
135
+ * it to skip a centroid round trip that was never going to return points.
136
+ */
137
+ function hasCentroids(config) {
138
+ const roles = config?.dataset?.roles;
139
+ return Boolean(roles && roles.x && roles.y);
140
+ }
141
+
142
+ return { build, resolveSchema, hasCentroids };
143
+ })();
@@ -0,0 +1,143 @@
1
+ /**
2
+ * WebGL2 tile-colorization engine.
3
+ *
4
+ * Deliberately OpenSeadragon-independent: this module is only the WebGL
5
+ * rendering engine, so the app can run against upstream OpenSeadragon rather
6
+ * than a fork pinned to a patched commit. The OpenSeadragon integration
7
+ * (tile-loaded/tile-drawing wiring) lives in imageViewer.js rather than a
8
+ * separate glue class, since imageViewer.js already overrides most of that
9
+ * layer's behavior.
10
+ */
11
+ export class GLRenderer {
12
+ constructor(incoming) {
13
+ // Custom hook points, callable via `this['gl-drawing']`/`this['gl-loaded']`
14
+ this['gl-drawing'] = function (e) { return e; };
15
+ this['gl-loaded'] = function (e) { return e; };
16
+
17
+ // Vertex input buffer describing a full-canvas quad
18
+ this.one_point_size = 2 * Float32Array.BYTES_PER_ELEMENT;
19
+ this.points_list_size = 4 * this.one_point_size;
20
+ this.points_buffer = new Float32Array([
21
+ 0, 1, 0, 0, 1, 1, 1, 0,
22
+ ]);
23
+
24
+ // Offscreen WebGL2 context and texture/buffer handles
25
+ this.gl = document.createElement('canvas').getContext('webgl2');
26
+ this.texture = this.gl.createTexture();
27
+ this.buffer = this.gl.createBuffer();
28
+
29
+ // Placeholder defaults, overwritten by the caller before init()
30
+ this.vShader = 'vShader.glsl';
31
+ this.fShader = 'fShader.glsl';
32
+ this.height = 0;
33
+ this.width = 0;
34
+
35
+ for (const key in incoming) {
36
+ this[key] = incoming[key];
37
+ }
38
+ }
39
+
40
+ init() {
41
+ const step = [[this.vShader, this.fShader].map(this.getter)];
42
+ step.push(this.toProgram.bind(this), this.toBuffers.bind(this));
43
+ return Promise.all(step[0]).then(step[1]).then(step[2]);
44
+ }
45
+
46
+ updateShape(width, height) {
47
+ this.width = width;
48
+ this.height = height;
49
+ this.gl.canvas.width = width;
50
+ this.gl.canvas.height = height;
51
+ this.gl.viewport(0, 0, width, height);
52
+ }
53
+
54
+ // Fetch a shader source file (or pass through a non-.glsl value unchanged)
55
+ getter(where) {
56
+ return new Promise((done) => {
57
+ if (where.slice(-4) != 'glsl') {
58
+ return done(where);
59
+ }
60
+ const bid = new XMLHttpRequest();
61
+ const win = function () {
62
+ if (bid.status == 200) {
63
+ return done(bid.response);
64
+ }
65
+ return done(where);
66
+ };
67
+ bid.open('GET', where, true);
68
+ bid.setRequestHeader('Cache-Control', 'no-cache, no-store, max-age=0');
69
+ bid.onerror = bid.onload = win;
70
+ bid.send();
71
+ });
72
+ }
73
+
74
+ // Compile and link the vertex/fragment shader pair
75
+ toProgram(files) {
76
+ const gl = this.gl;
77
+ const program = gl.createProgram();
78
+ const ok = function (kind, status, value, sh) {
79
+ if (!gl['get' + kind + 'Parameter'](value, gl[status + '_STATUS'])) {
80
+ console.log((sh || 'LINK') + ':\n' + gl['get' + kind + 'InfoLog'](value));
81
+ }
82
+ return value;
83
+ };
84
+ files.map((given, i) => {
85
+ const sh = ['VERTEX_SHADER', 'FRAGMENT_SHADER'][i];
86
+ const shader = gl.createShader(gl[sh]);
87
+ gl.shaderSource(shader, given);
88
+ gl.compileShader(shader);
89
+ gl.attachShader(program, shader);
90
+ ok('Shader', 'COMPILE', shader, sh);
91
+ });
92
+ gl.linkProgram(program);
93
+ return ok('Program', 'LINK', program);
94
+ }
95
+
96
+ // Bind the quad buffer, texture parameters, and uniforms
97
+ toBuffers(program) {
98
+ const gl = this.gl;
99
+ gl.useProgram(program);
100
+ this['gl-loaded'].call(this, program);
101
+
102
+ const u_tile = gl.getUniformLocation(program, 'u_tile');
103
+ const a_uv = gl.getAttribLocation(program, 'a_uv');
104
+ const u8 = gl.getUniformLocation(program, 'u8');
105
+
106
+ gl.uniform1ui(u8, 255);
107
+ gl.uniform1i(u_tile, 0);
108
+
109
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
110
+ gl.bufferData(gl.ARRAY_BUFFER, this.points_buffer, gl.STATIC_DRAW);
111
+
112
+ gl.enableVertexAttribArray(a_uv);
113
+ gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, 0, this.one_point_size, 0 * this.points_list_size);
114
+
115
+ gl.activeTexture(gl.TEXTURE0);
116
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
117
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
118
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
119
+
120
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
121
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
122
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
123
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
124
+ }
125
+
126
+ // Default single-texture draw path. imageViewer.js overrides this with
127
+ // its own multi-texture-cache implementation; kept here for fidelity/reuse.
128
+ loadArray(width, height, pixels, format = 'u16') {
129
+ this['gl-drawing'].call(this);
130
+ const gl = this.gl;
131
+
132
+ if (format == 'u16') {
133
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG8UI, width, height, 0, gl.RG_INTEGER, gl.UNSIGNED_BYTE, pixels);
134
+ } else if (format == 'u32') {
135
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, width, height, 0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, pixels);
136
+ } else if (format == 'u8') {
137
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8UI, width, height, 0, gl.RED_INTEGER, gl.UNSIGNED_BYTE, pixels);
138
+ }
139
+
140
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
141
+ return this.gl.canvas;
142
+ }
143
+ }