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,240 @@
1
+ <section class="sidebar-section" id="roi_panel_section">
2
+ <div class="section-heading">
3
+ <h2><span class="fas fa-draw-polygon section-heading-icon"></span>Regions</h2>
4
+ <div class="section-actions">
5
+ <button id="roi_import_button" class="icon-button" title="Import ROIs from GeoJSON" type="button">
6
+ <span class="fas fa-file-import"></span>
7
+ </button>
8
+ <button id="roi_export_button" class="icon-button" title="Export ROIs as GeoJSON" type="button">
9
+ <span class="fas fa-file-export"></span>
10
+ </button>
11
+ <button id="roi_panel_close" class="icon-button" title="Close Regions" type="button">
12
+ <span class="fas fa-xmark"></span>
13
+ </button>
14
+ </div>
15
+ </div>
16
+
17
+ {# Only ever populated from a real file the user picked. #}
18
+ <input id="roi_import_file" class="roi-hidden-file" type="file" accept=".geojson,.json,application/geo+json,application/json">
19
+
20
+ <div id="roi_blocked_banner" class="roi-banner roi-banner-danger" hidden>
21
+ <span class="fas fa-triangle-exclamation"></span>
22
+ <div>
23
+ <strong>These ROIs belong to a different image.</strong>
24
+ <div id="roi_blocked_detail" class="roi-banner-detail"></div>
25
+ <div class="roi-banner-detail">Nothing is shown, and editing is off, so regions cannot be attached to the wrong image. Export them to keep them.</div>
26
+ </div>
27
+ </div>
28
+
29
+ <div id="roi_conflict_banner" class="roi-banner roi-banner-warning" hidden>
30
+ <span class="fas fa-code-branch"></span>
31
+ <div>
32
+ <strong>Changed in another session.</strong>
33
+ <div class="roi-banner-detail">Someone -- probably another tab -- saved since this one loaded.</div>
34
+ <div class="roi-banner-actions">
35
+ <button id="roi_conflict_reload" class="sidebar-action secondary" type="button">Use theirs</button>
36
+ <button id="roi_conflict_keep" class="sidebar-action" type="button">Export mine, then reload</button>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ {# The three that MAKE a shape start disabled, because until the store has
42
+ loaded this panel does not yet know whether there is a category to put
43
+ one in -- and offering a live pen that refuses every stroke is worse
44
+ than one that visibly waits. The first render decides. #}
45
+ <label class="control-label">Tools</label>
46
+ <div id="roi_toolbar" class="roi-toolbar">
47
+ <button id="roi_tool_select" class="roi-tool" data-tool="select" type="button" title="Select (V)">
48
+ <span class="fas fa-arrow-pointer"></span><span class="roi-tool-key">V</span>
49
+ </button>
50
+ <button id="roi_tool_polygon" class="roi-tool" data-tool="polygon" type="button" disabled title="Polygon (P) -- click to place points, Enter or double-click to finish">
51
+ <span class="fas fa-draw-polygon"></span><span class="roi-tool-key">P</span>
52
+ </button>
53
+ <button id="roi_tool_freehand" class="roi-tool" data-tool="freehand" type="button" disabled title="Freehand (F) -- press and drag">
54
+ <span class="fas fa-pen-nib"></span><span class="roi-tool-key">F</span>
55
+ </button>
56
+ <button id="roi_tool_rectangle" class="roi-tool" data-tool="rectangle" type="button" disabled title="Rectangle (R) -- press and drag">
57
+ <span class="far fa-square"></span><span class="roi-tool-key">R</span>
58
+ </button>
59
+ </div>
60
+ <div class="roi-hint">Hold <kbd>Space</kbd> to pan. The wheel always zooms.</div>
61
+
62
+ <label class="control-label">Categories</label>
63
+ <div id="roi_category_list" class="roi-category-list"></div>
64
+ {# A project starts with none: the names are the user's to choose, so the
65
+ panel asks rather than shipping an "Uncategorized" nobody picked. #}
66
+ <div id="roi_category_empty" class="roi-hint roi-empty" hidden>
67
+ Name what you are about to outline &mdash; Tumor, Stroma, Necrosis &mdash; then draw.
68
+ </div>
69
+ {# Deliberately not a <form>. This markup is server-rendered and on screen
70
+ before the plugin's scripts have finished loading, so for a moment there
71
+ is nobody to call preventDefault -- and an Enter in that window submits
72
+ natively and NAVIGATES THE PAGE, taking the panel and anything unsaved
73
+ with it. No form, no default action to suppress. #}
74
+ <div id="roi_category_add" class="roi-category-add">
75
+ <input id="roi_category_name" class="roi-input" type="text" autocomplete="off" spellcheck="false"
76
+ maxlength="200" placeholder="New category&hellip;">
77
+ <button id="roi_category_add_button" class="icon-button" title="Add category" type="button">
78
+ <span class="fas fa-plus"></span>
79
+ </button>
80
+ </div>
81
+
82
+ {# Compact on purpose. This block appears and disappears as regions are
83
+ clicked, right under the category list the user is also working in, so
84
+ every row it costs is a row the list loses. The two things worth reading
85
+ -- what it is called and what it is -- sit on one line; the two things
86
+ worth doing are icons on the heading. #}
87
+ <div id="roi_selection_panel" hidden>
88
+ <div class="roi-selection-head">
89
+ <label class="control-label">Selected</label>
90
+ <div class="roi-selection-tools">
91
+ {# Both padlocks ship, and CSS picks which one shows. Swapping
92
+ the class in JS does not work here: FontAwesome's SVG mode
93
+ has already replaced these spans with <svg> elements by the
94
+ time anything is selected, so there is no span left to
95
+ rewrite -- and rewriting the svg's class strips what FA put
96
+ there. The `fa-*` class survives onto the svg, which is what
97
+ the rule in roi.css hangs on. #}
98
+ <button id="roi_selection_locked" class="icon-button roi-selection-action"
99
+ type="button" aria-pressed="false" title="Lock this region">
100
+ <span class="fas fa-lock-open"></span>
101
+ <span class="fas fa-lock"></span>
102
+ </button>
103
+ <button id="roi_selection_delete" class="icon-button roi-selection-action roi-danger-action"
104
+ type="button" title="Delete this region">
105
+ <span class="fas fa-trash"></span>
106
+ </button>
107
+ </div>
108
+ </div>
109
+ <div class="roi-selection-row">
110
+ <input id="roi_selection_name" class="roi-input roi-selection-name" type="text"
111
+ autocomplete="off" spellcheck="false" maxlength="200"
112
+ placeholder="Name" title="Name of this region">
113
+ {# The swatch is a sibling sitting over the select's left padding:
114
+ a native <select> cannot colour its own options portably, and
115
+ the only colour worth showing is the current one. #}
116
+ <div class="roi-select-wrap">
117
+ <span id="roi_selection_swatch" class="roi-selection-swatch" aria-hidden="true"></span>
118
+ <select id="roi_selection_category" class="roi-input" title="Category"></select>
119
+ </div>
120
+ </div>
121
+ <div id="roi_selection_note" class="roi-hint roi-selection-note" hidden></div>
122
+ </div>
123
+
124
+ {# One of these two, never both. A project with a native destination gets
125
+ the file write; a CSV or image-only project gets the download in the same
126
+ place and at the same size, because "how do I keep this?" is the same
127
+ question either way.
128
+
129
+ The name field is a step in the save, not part of the resting panel: the
130
+ first click on the button opens it with the name already filled in, and
131
+ the second click writes. Most saves are the same name as last time, so
132
+ standing the field up permanently would spend three rows of a 320px
133
+ column on a decision almost nobody is making. Enter writes, Escape and
134
+ the x close it. #}
135
+ <div id="roi_destination" class="roi-destination" hidden>
136
+ <div class="roi-destination-head">
137
+ <label class="control-label" for="roi_destination_name">Save as</label>
138
+ <button id="roi_destination_close" class="icon-button roi-destination-close"
139
+ type="button" title="Close">
140
+ <span class="fas fa-xmark"></span>
141
+ </button>
142
+ </div>
143
+ <input id="roi_destination_name" class="roi-input" type="text" maxlength="200"
144
+ autocomplete="off" spellcheck="false">
145
+ <div id="roi_destination_hint" class="roi-hint roi-destination-hint"></div>
146
+ </div>
147
+ {# The two ways out of this panel, side by side, because they are the same
148
+ size of decision: one keeps the shapes, the other puts them on the rows.
149
+ Stacked they read as a sequence -- save, THEN map -- which is not true;
150
+ either can be done without the other, in either order.
151
+
152
+ Exactly one save button is ever shown, so the row is two buttons wide
153
+ whatever the project is. Both say "Save": which file it lands in is what
154
+ the hint above and the tooltip are for, and a button label is not the
155
+ place to name a storage format. #}
156
+ <div class="roi-actions">
157
+ <button id="roi_save_to_source" class="sidebar-action-fancy" type="button" hidden
158
+ aria-expanded="false">
159
+ <span class="fas fa-file-export"></span>
160
+ <span id="roi_save_to_source_label">Save</span>
161
+ </button>
162
+ <button id="roi_export_download" class="sidebar-action-fancy" type="button" hidden
163
+ title="Download these regions as a GeoJSON file">
164
+ <span class="fas fa-download"></span>
165
+ <span>Save GeoJSON</span>
166
+ </button>
167
+
168
+ {# Only for a project that has cells. The save buttons write the
169
+ polygons somewhere; this writes onto the rows, which is a different
170
+ thing to want and is meaningless without a table -- so it is absent
171
+ rather than disabled for a project that is nothing but an image. #}
172
+ <button id="roi_map_to_cells" class="sidebar-action-fancy" type="button" hidden>
173
+ <span class="fas fa-table-cells"></span>
174
+ <span>Map to cells</span>
175
+ </button>
176
+
177
+ {# The one control on this panel whose result a user cannot see before
178
+ pressing it -- it writes onto their own rows, and the rules it writes
179
+ by (overlaps, image scope, what a blank means) do not fit in a line
180
+ of hint text under a button. They used to be attempted in one, which
181
+ said only what the columns were called. #}
182
+ <button id="roi_map_info" class="icon-button roi-map-info" type="button" hidden
183
+ title="What does Map to cells do?" aria-haspopup="dialog">
184
+ <span class="fas fa-question"></span>
185
+ </button>
186
+ </div>
187
+
188
+ {# Opened by the ? above. Plain <dialog>: it is modal, Escape closes it and
189
+ focus is handled by the browser, none of which is worth reimplementing.
190
+ The prose is static -- it is the same for every project -- except for the
191
+ one line naming the columns, which follows the save name. #}
192
+ <dialog id="roi_map_info_dialog" class="roi-modal">
193
+ <div class="roi-modal-head">
194
+ <h3 class="roi-modal-title">Map to cells</h3>
195
+ <button id="roi_map_info_close" class="icon-button roi-modal-close"
196
+ type="button" title="Close">
197
+ <span class="fas fa-xmark"></span>
198
+ </button>
199
+ </div>
200
+ <div class="roi-modal-body">
201
+ <p>Assigns each cell to the ROI(s) that contain it and writes the
202
+ annotations directly to the data table.</p>
203
+
204
+ <p>For the current save name, Plexora creates:</p>
205
+
206
+ {# The only two values in here that are about THIS project. Written
207
+ by openMapInfo() from the save name, not hard-coded: a fixed pair
208
+ of names would be wrong for every user who saved under their own,
209
+ and this dialog exists to tell them what they are about to get. #}
210
+ <ul id="roi_map_info_columns" class="roi-modal-note">
211
+ <li><code id="roi_map_info_category">rois_category</code>
212
+ &mdash; ROI category, e.g. Tumour</li>
213
+ <li><code id="roi_map_info_name">rois_name</code>
214
+ &mdash; specific ROI name, e.g. Tumour 1</li>
215
+ </ul>
216
+
217
+ <p>If a cell falls within overlapping ROIs, values are joined with
218
+ <code>_</code>. Duplicate categories are collapsed, while distinct
219
+ ROI names are retained.</p>
220
+
221
+ <p>Cells outside all ROIs are left blank. Only cells belonging to the
222
+ currently displayed image are evaluated; cells from other images
223
+ are not modified.</p>
224
+
225
+ <p>Existing columns are not overwritten automatically. If they
226
+ already exist, Plexora prompts you to replace them or use a new
227
+ save name.</p>
228
+
229
+ <p class="roi-modal-aside"><strong>Note:</strong> Map to cells saves
230
+ ROI assignments to individual cells. The separate Save function
231
+ saves the ROI shapes themselves.</p>
232
+ </div>
233
+ </dialog>
234
+
235
+ <div id="roi_status" class="roi-status">
236
+ <span id="roi_status_dot" class="roi-status-dot"></span>
237
+ <span id="roi_status_text">Saved</span>
238
+ </div>
239
+ <div id="roi_message" class="roi-message" hidden></div>
240
+ </section>
plexora/proxy.py ADDED
@@ -0,0 +1,57 @@
1
+ import os
2
+ import sys
3
+
4
+
5
+ def setup_plexora():
6
+ data_dir = os.environ.get("PLEXORA_DATA_PATH", "")
7
+ plugins = os.environ.get("PLEXORA_PLUGINS")
8
+ base_url_template = "{base_url}plexora"
9
+ command = [
10
+ sys.executable,
11
+ "-m",
12
+ "plexora.server_cli",
13
+ "--host",
14
+ "127.0.0.1",
15
+ "--port",
16
+ "{port}",
17
+ "--base-url",
18
+ base_url_template,
19
+ "--notebook-mode",
20
+ ]
21
+ # Omitted entirely when unset, exactly as the `environment` dict below
22
+ # does. Passing `--plugins ""` here instead used to be a silent
23
+ # catastrophe: server_cli.py writes the flag straight into PLEXORA_PLUGINS,
24
+ # and "" is not "unset" -- it is the deliberate core-only build. Every
25
+ # launch from the JupyterHub launcher tile came up with all plugins
26
+ # disabled, and looked to the user like Plexora simply had no tools.
27
+ if plugins is not None:
28
+ command.extend(["--plugins", plugins])
29
+ if data_dir:
30
+ command.extend(["--data-dir", data_dir])
31
+
32
+ # jupyter_server_proxy sets these as real OS env vars before the child
33
+ # process starts (handlers.py: ensure_process's server_env.update(get_env())),
34
+ # which is what actually reaches plexora/__init__.py's import-time
35
+ # env snapshot -- the --base-url/--data-dir/--plugins CLI flags
36
+ # above are consumed too late relative to that import. Keep the two in
37
+ # sync if you change one.
38
+ environment = {
39
+ "PLEXORA_BASE_URL": base_url_template,
40
+ "PLEXORA_NOTEBOOK_MODE": "1",
41
+ **({"PLEXORA_PLUGINS": plugins} if plugins is not None else {}),
42
+ }
43
+ if data_dir:
44
+ environment["PLEXORA_DATA_PATH"] = data_dir
45
+
46
+ return {
47
+ "command": command,
48
+ "environment": environment,
49
+ "absolute_url": False,
50
+ "new_browser_tab": False,
51
+ "timeout": 30,
52
+ "launcher_entry": {
53
+ "enabled": True,
54
+ "title": "Plexora",
55
+ "path_info": "",
56
+ },
57
+ }
@@ -0,0 +1,94 @@
1
+ from pathlib import Path
2
+
3
+ from .base import DatasourceAdapter, MetadataColumn, NormalizedDatasource
4
+ from .classify import classify_columns, classify_from_inspection
5
+ from .csv_adapter import CsvAdapter
6
+ from .anndata_adapter import AnnDataAdapter
7
+ from .spatialdata_adapter import SpatialDataAdapter
8
+
9
+ _ADAPTERS = {
10
+ "csv": CsvAdapter,
11
+ "anndata": AnnDataAdapter,
12
+ "spatialdata": SpatialDataAdapter,
13
+ }
14
+
15
+ #: Extensions the single "Data" input on the upload page accepts, mapped to the
16
+ #: adapter that reads them. A directory is decided by inspecting it (a .zarr
17
+ #: store holding a `tables/` group is SpatialData, otherwise it is a
18
+ #: zarr-backed AnnData), since the extension alone cannot tell them apart.
19
+ _SUFFIX_TYPES = {
20
+ ".csv": "csv",
21
+ ".tsv": "csv",
22
+ ".txt": "csv",
23
+ ".h5ad": "anndata",
24
+ }
25
+
26
+ #: What to tell the user when a path is none of the above. Kept here rather
27
+ #: than in the route so the accepted list cannot drift from the table above.
28
+ SUPPORTED_DATA_DESCRIPTION = "a .csv, .h5ad or .zarr file"
29
+
30
+
31
+ def get_adapter(data_type: str):
32
+ """Look up the adapter class registered for a project's data type."""
33
+ try:
34
+ return _ADAPTERS[data_type]
35
+ except KeyError:
36
+ raise ValueError(f"Unknown datasource data_type: {data_type!r}") from None
37
+
38
+
39
+ def detect_data_type(path) -> str:
40
+ """Which adapter reads the file at `path`.
41
+
42
+ The upload page has one Data input rather than a tab per format, so this
43
+ is what decides where a dropped path goes. It reads the filesystem -- a
44
+ .zarr store is a directory, and only looking inside distinguishes a
45
+ SpatialData store from a plain zarr-backed AnnData -- so it is a detection
46
+ step, not a string parse.
47
+
48
+ Raises ValueError naming the accepted formats, which is what the upload
49
+ form shows: an unrecognized path is ordinary user error, not a bug.
50
+ """
51
+ path = Path(path).expanduser()
52
+ if not path.exists():
53
+ raise ValueError(f"No such file: {path}")
54
+
55
+ suffix = path.suffix.lower()
56
+ if path.is_dir():
57
+ if suffix != ".zarr":
58
+ raise ValueError(
59
+ f"{path.name} is a directory but not a .zarr store. Provide "
60
+ f"{SUPPORTED_DATA_DESCRIPTION}."
61
+ )
62
+ return "spatialdata" if _has_spatialdata_tables(path) else "anndata"
63
+
64
+ data_type = _SUFFIX_TYPES.get(suffix)
65
+ if data_type is None:
66
+ raise ValueError(
67
+ f"Cannot read {path.name}: expected {SUPPORTED_DATA_DESCRIPTION}."
68
+ )
69
+ return data_type
70
+
71
+
72
+ def _has_spatialdata_tables(store) -> bool:
73
+ """Whether a .zarr directory is a SpatialData store rather than a bare
74
+ AnnData written to zarr. Structural, and cheap: SpatialData keeps its
75
+ tables under a `tables/` group, and a plain AnnData has no such thing.
76
+ Nothing is opened -- a store with thousands of chunks costs one stat."""
77
+ from .spatialdata_adapter import TABLES_GROUP
78
+
79
+ return (Path(store) / TABLES_GROUP).is_dir()
80
+
81
+
82
+ __all__ = [
83
+ "DatasourceAdapter",
84
+ "MetadataColumn",
85
+ "NormalizedDatasource",
86
+ "CsvAdapter",
87
+ "AnnDataAdapter",
88
+ "SpatialDataAdapter",
89
+ "SUPPORTED_DATA_DESCRIPTION",
90
+ "classify_columns",
91
+ "classify_from_inspection",
92
+ "detect_data_type",
93
+ "get_adapter",
94
+ ]