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
plexora/__init__.py ADDED
@@ -0,0 +1,129 @@
1
+ import multiprocessing
2
+
3
+ multiprocessing.freeze_support()
4
+
5
+ from flask import Flask
6
+ from pathlib import Path
7
+
8
+ import os
9
+
10
+ # Initialize sklearn global threadpool controller to avoid deadlock in threaded
11
+ # contexts.
12
+ from threadpoolctl import threadpool_limits
13
+
14
+ threadpool_limits()
15
+
16
+ # Where data lives is `plexora.paths`' business, and it is resolved on demand
17
+ # rather than here. This module used to compute `data_path` at import, whose
18
+ # last fallback was relative to the current working directory -- see that
19
+ # module's docstring for why that had to go, and why nothing may snapshot the
20
+ # answer into a module constant again.
21
+
22
+
23
+ # Re-exported under its historical name because server_cli.py imports it from
24
+ # here. `plexora._url` is a leaf module -- it imports nothing from this package
25
+ # -- so pulling it in mid-initialisation is safe.
26
+ from plexora._url import clean_prefix as _clean_base_url
27
+
28
+ app = None
29
+
30
+
31
+ def create_app(plugins=None):
32
+ """Build the Flask app, then register the core routes and whichever
33
+ plugins this process activates.
34
+
35
+ `global app` is assigned immediately after constructing Flask(...) --
36
+ before the route-module imports below -- because those modules do
37
+ `from plexora import app` themselves; if the module-level
38
+ `app` attribute didn't exist yet at that point (e.g. only assigned via
39
+ `app = create_app()` after this function returns), that import would
40
+ fail on a partially-initialized package.
41
+
42
+ In practice this is called exactly once, at import time, at the bottom
43
+ of this file -- it's a factory (rather than a bare module-level
44
+ Flask()) only so plugins can be chosen before route registration
45
+ happens. Calling it a second time in one interpreter does NOT rebuild
46
+ the app: the route modules below are already in sys.modules, so their
47
+ @app.route decorators never run again and the second app comes back
48
+ with no core routes at all.
49
+ """
50
+ global app
51
+ # static_folder is None on purpose. It used to be "data", which Flask
52
+ # resolves against the PACKAGE directory -- so it pointed at
53
+ # site-packages/plexora/data whatever the data root actually was, and in
54
+ # the old dev layout (where those happened to be the same directory) it
55
+ # served the entire data tree from `/data/<path:filename>`. Nothing ever
56
+ # used it: no template calls url_for('static'), no client code requests
57
+ # /data/, and tiles go through /generated/data/... instead.
58
+ app = Flask(__name__, template_folder=Path("client/templates"), static_folder=None)
59
+ app.config["TEMPLATES_AUTO_RELOAD"] = True
60
+ app.config["CLIENT_PATH"] = app.root_path + "/client/"
61
+ # Read from the environment as well as being settable afterwards. It used
62
+ # to be set only by run.py, from its second positional argv -- so the flag
63
+ # (which drives the import page's path hints) was silently False for every
64
+ # other way of starting the server, including the console script the
65
+ # container could otherwise use.
66
+ app.config["IS_DOCKER"] = os.environ.get("PLEXORA_DOCKER", "").lower() in ("1", "true", "yes")
67
+ app.config["PLEXORA_BASE_URL"] = _clean_base_url(os.environ.get("PLEXORA_BASE_URL", ""))
68
+ app.config["PLEXORA_NOTEBOOK_MODE"] = os.environ.get("PLEXORA_NOTEBOOK_MODE", "").lower() in ("1", "true", "yes")
69
+
70
+ @app.after_request
71
+ def add_notebook_headers(response):
72
+ # X-Frame-Options: SAMEORIGIN would block the direct (non-proxy) notebook
73
+ # iframe flow, since the sidecar server (127.0.0.1:<port>) is always a
74
+ # different origin than the Jupyter page embedding it. The sidecar only
75
+ # binds to 127.0.0.1, so omitting the header does not expose it to the
76
+ # network.
77
+ return response
78
+
79
+ # Imported here (not at module top) purely for their route-registration
80
+ # side effects -- see the docstring above for why `app` must already be
81
+ # assigned by this point.
82
+ from plexora.server.routes import page_routes, data_routes, import_routes, quick_view_routes, browse_routes, tool_routes, system_routes, project_routes
83
+ from plexora.server.models import data_model, database_model
84
+ from plexora.server import plugins as plugin_registry
85
+
86
+ # `plugins is None` means "not passed, consult PLEXORA_PLUGINS", which in
87
+ # turn distinguishes unset (activate everything installed) from "" (a
88
+ # deliberate core-only build). A truthy check here would collapse those.
89
+ plugin_registry.install(app, plugins)
90
+
91
+ return app
92
+
93
+
94
+ def get_config():
95
+ """Every project this user can open, merged across the roots.
96
+
97
+ Thin wrapper over the project registry, kept because a great deal of the
98
+ route layer already calls it. The merge -- and the rule that the user's own
99
+ root wins a name collision with a shared one -- lives in project.py, which
100
+ is the module that knows how to read a config.json safely.
101
+ """
102
+ # Imported here rather than at module scope: project.py is part of the
103
+ # server package, which imports this module back.
104
+ from plexora.server.models.project import Project
105
+
106
+ return Project.load_all()
107
+
108
+
109
+ def get_config_names():
110
+ data = get_config()
111
+ try:
112
+ return [key for key in data.keys()]
113
+ except AttributeError:
114
+ return []
115
+
116
+
117
+ def view(datasource, **kwargs):
118
+ """Return a notebook-displayable Plexora viewer for `datasource`.
119
+
120
+ This is a small public convenience wrapper around PlexoraViewer. In a
121
+ Jupyter cell, making it the last expression starts the sidecar server and
122
+ displays the viewer iframe.
123
+ """
124
+ from plexora.jupyter import PlexoraViewer
125
+
126
+ return PlexoraViewer(datasource=datasource, **kwargs)
127
+
128
+
129
+ app = create_app()
plexora/__main__.py ADDED
@@ -0,0 +1,15 @@
1
+ """`python -m plexora` -- the same command as `plexora`, for anyone whose
2
+ PATH does not have the console script on it (a common state on Windows, and
3
+ inside conda environments that were activated after the shell started).
4
+
5
+ A pass-through. It carries no special handling of its own: the one thing `-m`
6
+ makes awkward -- that the package is imported before any argument is read, so
7
+ `--plugins` would arrive after Blueprint registration -- is equally true of the
8
+ console script, so the fix lives in `cli.main` where both reach it. See
9
+ `plexora.cli.maybe_reexec_for_plugins`.
10
+ """
11
+
12
+ from plexora.cli import main
13
+
14
+ if __name__ == "__main__":
15
+ raise SystemExit(main())
plexora/_url.py ADDED
@@ -0,0 +1,105 @@
1
+ """Base-URL arithmetic, in one place.
2
+
3
+ Three modules used to carry their own `_clean_base_url`, and two of them meant
4
+ different things by it: `plexora/__init__.py` and `plexora/cli.py` returned a
5
+ prefix with NO trailing slash (`/user/me`), while `plexora/jupyter.py` returned
6
+ one WITH (`/user/me/`) because it immediately concatenated `proxy/<port>` onto
7
+ the end. Same name, same-looking body, opposite contracts -- which is exactly
8
+ the shape of bug that survives review. The two forms both have a legitimate
9
+ caller, so they are kept, but under names that say which is which.
10
+
11
+ The third function here is the reason this module exists at all.
12
+ `clean_prefix` REFUSES a full origin. Everywhere Plexora used the old helper it
13
+ was handling a mount path -- "/proxy/8000" -- and prefixing it with "/" is
14
+ correct for that. Hosted notebooks introduce a base that is a whole origin
15
+ instead: Colab's `google.colab.kernel.proxyPort()` hands back
16
+ `https://xxxx.googleusercontent.com`. Feeding that to the old helper produced
17
+ `/https:/xxxx.googleusercontent.com` -- a silently corrupt path that fails far
18
+ from where it was made. Raising here turns that class of mistake into a message
19
+ naming the value.
20
+
21
+ Deliberately a leaf module: stdlib only, imports nothing from `plexora`, so
22
+ `plexora/__init__.py` can use it while it is still initialising.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from urllib.parse import quote
28
+
29
+
30
+ def is_full_origin(value) -> bool:
31
+ """Whether `value` is an absolute http(s) URL rather than a mount path."""
32
+ if not value:
33
+ return False
34
+ return str(value).strip().lower().startswith(("http://", "https://"))
35
+
36
+
37
+ def clean_prefix(base_url) -> str:
38
+ """A mount path with a leading slash and no trailing one; "" for none.
39
+
40
+ This is what `PLEXORA_BASE_URL` and `app.config['PLEXORA_BASE_URL']` hold,
41
+ and what templates concatenate request paths onto. "" and "/" both mean
42
+ "mounted at the root", and both must produce "" rather than "/" so that
43
+ `f"{prefix}/tile/..."` does not come out with a doubled slash.
44
+ """
45
+ if not base_url:
46
+ return ""
47
+ base_url = str(base_url).strip()
48
+ if is_full_origin(base_url):
49
+ raise ValueError(
50
+ f"Base URL must be a mount path such as '/proxy/8000', not a full "
51
+ f"URL: {base_url!r}. A full origin belongs in the DISPLAY url (see "
52
+ f"plexora._url.join_display), never in PLEXORA_BASE_URL -- the "
53
+ f"server mounts routes under a path, and prefixing a scheme with "
54
+ f"'/' would corrupt every link on the page."
55
+ )
56
+ if base_url == "/":
57
+ return ""
58
+ return "/" + base_url.strip("/")
59
+
60
+
61
+ def prefix_with_slash(base_url) -> str:
62
+ """A mount path with slashes on BOTH ends; "/" for none.
63
+
64
+ The form callers want when they are about to append a further path segment
65
+ by string concatenation, as the notebook sidecar does with
66
+ `f"{prefix}proxy/{port}"`. Kept separate from `clean_prefix` rather than
67
+ making one of them call `rstrip` at each call site, because the two answers
68
+ for "no prefix" genuinely differ: "" and "/".
69
+ """
70
+ if not base_url or str(base_url).strip() == "/":
71
+ return "/"
72
+ if is_full_origin(base_url):
73
+ raise ValueError(
74
+ f"Base URL must be a mount path, not a full URL: {base_url!r}."
75
+ )
76
+ return "/" + str(base_url).strip().strip("/") + "/"
77
+
78
+
79
+ def join_display(base, *segments) -> str:
80
+ """Build a URL a human or an <iframe> can follow.
81
+
82
+ Unlike the two above, `base` here may legitimately be any of three things,
83
+ because a hosted notebook picks a different one in each case:
84
+
85
+ - "" -- nothing known; the result is a site-root-relative path.
86
+ - a mount path (`/user/me/proxy/8123`) -- the JupyterHub and Open OnDemand
87
+ case. Stays path-only ON PURPOSE: an iframe resolves it against the
88
+ notebook page's own origin, which is the only origin that carries the
89
+ user's auth cookie, and hardcoding a host would break the moment the hub
90
+ moved or the user came in through a different name.
91
+ - a full origin (`https://xxxx.googleusercontent.com`) -- the Colab case,
92
+ where the proxy is a whole separate subdomain.
93
+
94
+ Segments are percent-quoted, so a project named "Tonsil 2 (repeat)" gives a
95
+ URL that survives being pasted into a browser.
96
+ """
97
+ base = "" if base is None else str(base).rstrip("/")
98
+ tail = "/".join(
99
+ quote(str(segment).strip("/"), safe="")
100
+ for segment in segments
101
+ if segment not in (None, "")
102
+ )
103
+ if not tail:
104
+ return f"{base}/" if base else "/"
105
+ return f"{base}/{tail}"
@@ -0,0 +1,55 @@
1
+ """Plexora's plugin API -- the only surface a plugin may import.
2
+
3
+ from plexora import api
4
+
5
+ ds = api.dataset("my_project")
6
+ ds.image.channel_names
7
+ ds.table.markers
8
+ ds.schema.x
9
+
10
+ Anything not re-exported here is an implementation detail and will change
11
+ without notice. In particular `plexora.server.models.data_model` is off limits:
12
+ it carries mutable module-level globals under a load lock and two adjacent
13
+ loaders whose names differ by one underscore. The handles in `dataset` call the
14
+ correct one on a plugin's behalf.
15
+
16
+ First-party plugins (gating today) import from here and nowhere else. That is
17
+ deliberate -- it is what keeps this file honest, since a gap in the API becomes
18
+ a gap in the shipped product rather than something only outside authors hit.
19
+ """
20
+
21
+ from plexora.api.dataset import (
22
+ Dataset,
23
+ DatasetSchema,
24
+ ImageHandle,
25
+ ImageSource,
26
+ MetadataColumn,
27
+ SegHandle,
28
+ TableHandle,
29
+ TableSource,
30
+ dataset,
31
+ )
32
+ from plexora.api.http import json_response
33
+ from plexora.api.store import PluginStore, store
34
+ from plexora.server.models.adapters.anndata_adapter import _deduplicate_names
35
+
36
+ #: Multiplexed panels routinely re-stain the same marker across cycles, so
37
+ #: duplicate names are ordinary rather than exceptional. Suffixes them the way
38
+ #: anndata's own var_names_make_unique() does.
39
+ deduplicate_names = _deduplicate_names
40
+
41
+ __all__ = [
42
+ "Dataset",
43
+ "DatasetSchema",
44
+ "ImageHandle",
45
+ "ImageSource",
46
+ "MetadataColumn",
47
+ "PluginStore",
48
+ "SegHandle",
49
+ "TableHandle",
50
+ "TableSource",
51
+ "dataset",
52
+ "deduplicate_names",
53
+ "json_response",
54
+ "store",
55
+ ]