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.
- plexora/__init__.py +129 -0
- plexora/__main__.py +15 -0
- plexora/_url.py +105 -0
- plexora/api/__init__.py +55 -0
- plexora/api/dataset.py +398 -0
- plexora/api/http.py +19 -0
- plexora/api/plugin.py +541 -0
- plexora/api/store.py +221 -0
- plexora/cli.py +777 -0
- plexora/client/dist/354_bundle.js +2 -0
- plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
- plexora/client/dist/418_bundle.js +2 -0
- plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
- plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
- plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
- plexora/client/dist/770_bundle.js +1 -0
- plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
- plexora/client/dist/vendor_bundle.js +2 -0
- plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
- plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
- plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
- plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
- plexora/client/src/css/import.css +474 -0
- plexora/client/src/css/main.css +903 -0
- plexora/client/src/css/openProject.css +363 -0
- plexora/client/src/css/quickView.css +166 -0
- plexora/client/src/css/tokens.css +51 -0
- plexora/client/src/css/viewer.css +1655 -0
- plexora/client/src/img/apple-touch-icon.png +0 -0
- plexora/client/src/img/favicon.ico +0 -0
- plexora/client/src/img/logo.ai +8892 -33
- plexora/client/src/img/logo.svg +1 -0
- plexora/client/src/img/logo_with_text.ai +9011 -34
- plexora/client/src/img/logo_with_text.svg +1 -0
- plexora/client/src/js/main.js +704 -0
- plexora/client/src/js/pluginRegistry.js +108 -0
- plexora/client/src/js/services/appStatus.js +286 -0
- plexora/client/src/js/services/browsePicker.js +73 -0
- plexora/client/src/js/services/dataLayer.js +400 -0
- plexora/client/src/js/services/datasetContext.js +143 -0
- plexora/client/src/js/services/glRenderer.js +143 -0
- plexora/client/src/js/services/importFormValidation.js +257 -0
- plexora/client/src/js/services/numericData.js +100 -0
- plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
- plexora/client/src/js/services/simpleEventHandler.js +25 -0
- plexora/client/src/js/vendor.js +29 -0
- plexora/client/src/js/views/channelList.js +663 -0
- plexora/client/src/js/views/colorSwatchPicker.js +202 -0
- plexora/client/src/js/views/columnClassifier.js +146 -0
- plexora/client/src/js/views/coordinateField.js +133 -0
- plexora/client/src/js/views/dataSourceField.js +276 -0
- plexora/client/src/js/views/imageViewer.js +3306 -0
- plexora/client/src/js/views/miniMap.js +787 -0
- plexora/client/src/js/views/navbarControls.js +178 -0
- plexora/client/src/js/views/openProjectPage.js +239 -0
- plexora/client/src/js/views/projectEdit.js +335 -0
- plexora/client/src/js/views/quickViewLanding.js +136 -0
- plexora/client/src/js/views/rainbow.js +326 -0
- plexora/client/src/js/views/requirementsModal.js +572 -0
- plexora/client/src/js/views/rgbImageViewer.js +115 -0
- plexora/client/src/js/views/searchableSelect.js +411 -0
- plexora/client/src/js/views/segmentationProgress.js +122 -0
- plexora/client/src/js/views/toolLoader.js +747 -0
- plexora/client/src/js/views/viewerControls.js +689 -0
- plexora/client/src/js/views/viewerManager.js +434 -0
- plexora/client/src/js/views/viewerSidebar.js +1117 -0
- plexora/client/src/js/workers/tileDecoder.js +130 -0
- plexora/client/src/shaders/frag.glsl +528 -0
- plexora/client/src/shaders/vert.glsl +12 -0
- plexora/client/templates/base.html +212 -0
- plexora/client/templates/index.html +238 -0
- plexora/client/templates/open_project.html +96 -0
- plexora/client/templates/project_columns.html +86 -0
- plexora/client/templates/project_edit.html +196 -0
- plexora/client/templates/upload.html +118 -0
- plexora/connect.py +528 -0
- plexora/datasource.py +782 -0
- plexora/jupyter.py +406 -0
- plexora/notebook_env.py +232 -0
- plexora/paths.py +486 -0
- plexora/plugins/__init__.py +12 -0
- plexora/plugins/cell_explorer/__init__.py +100 -0
- plexora/plugins/cell_explorer/server/__init__.py +11 -0
- plexora/plugins/cell_explorer/server/routes.py +167 -0
- plexora/plugins/cell_explorer/server/state.py +281 -0
- plexora/plugins/cell_explorer/server/values.py +145 -0
- plexora/plugins/cell_explorer/server/variables.py +367 -0
- plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
- plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
- plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
- plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
- plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
- plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
- plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
- plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
- plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
- plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
- plexora/plugins/figure_builder/__init__.py +125 -0
- plexora/plugins/figure_builder/server/__init__.py +7 -0
- plexora/plugins/figure_builder/server/compose.py +365 -0
- plexora/plugins/figure_builder/server/export.py +784 -0
- plexora/plugins/figure_builder/server/export_jobs.py +148 -0
- plexora/plugins/figure_builder/server/operations.py +556 -0
- plexora/plugins/figure_builder/server/pixels.py +142 -0
- plexora/plugins/figure_builder/server/provenance.py +174 -0
- plexora/plugins/figure_builder/server/render.py +278 -0
- plexora/plugins/figure_builder/server/repository.py +701 -0
- plexora/plugins/figure_builder/server/routes.py +594 -0
- plexora/plugins/figure_builder/server/schema.py +1023 -0
- plexora/plugins/figure_builder/server/sources.py +120 -0
- plexora/plugins/figure_builder/server/textmetrics.py +155 -0
- plexora/plugins/figure_builder/static/figureActions.js +363 -0
- plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
- plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
- plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
- plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
- plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
- plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
- plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
- plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
- plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
- plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
- plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
- plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
- plexora/plugins/figure_builder/static/figureRichText.js +634 -0
- plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
- plexora/plugins/figure_builder/static/figureSchema.js +218 -0
- plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
- plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
- plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
- plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
- plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
- plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
- plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
- plexora/plugins/gating/__init__.py +76 -0
- plexora/plugins/gating/server/__init__.py +0 -0
- plexora/plugins/gating/server/anndata_gates.py +367 -0
- plexora/plugins/gating/server/database.py +16 -0
- plexora/plugins/gating/server/model.py +320 -0
- plexora/plugins/gating/server/routes.py +235 -0
- plexora/plugins/gating/static/csvGatingList.js +947 -0
- plexora/plugins/gating/static/gating.css +289 -0
- plexora/plugins/gating/static/gatingApi.js +221 -0
- plexora/plugins/gating/static/gatingSidebarController.js +513 -0
- plexora/plugins/gating/templates/gating/legacy.html +26 -0
- plexora/plugins/gating/templates/gating/panel.html +69 -0
- plexora/plugins/roi/__init__.py +95 -0
- plexora/plugins/roi/server/__init__.py +8 -0
- plexora/plugins/roi/server/adapters.py +655 -0
- plexora/plugins/roi/server/geojson.py +267 -0
- plexora/plugins/roi/server/geometry.py +181 -0
- plexora/plugins/roi/server/mapping.py +169 -0
- plexora/plugins/roi/server/operations.py +293 -0
- plexora/plugins/roi/server/repository.py +215 -0
- plexora/plugins/roi/server/routes.py +409 -0
- plexora/plugins/roi/server/schema.py +323 -0
- plexora/plugins/roi/static/roi.css +583 -0
- plexora/plugins/roi/static/roiApi.js +177 -0
- plexora/plugins/roi/static/roiFigureBridge.js +107 -0
- plexora/plugins/roi/static/roiGeometry.js +350 -0
- plexora/plugins/roi/static/roiRenderer.js +278 -0
- plexora/plugins/roi/static/roiSidebarController.js +1038 -0
- plexora/plugins/roi/static/roiState.js +518 -0
- plexora/plugins/roi/static/roiTools.js +993 -0
- plexora/plugins/roi/templates/roi/panel.html +240 -0
- plexora/proxy.py +57 -0
- plexora/server/models/adapters/__init__.py +94 -0
- plexora/server/models/adapters/anndata_adapter.py +388 -0
- plexora/server/models/adapters/base.py +93 -0
- plexora/server/models/adapters/classify.py +192 -0
- plexora/server/models/adapters/csv_adapter.py +105 -0
- plexora/server/models/adapters/inspection.py +265 -0
- plexora/server/models/adapters/spatialdata_adapter.py +248 -0
- plexora/server/models/centroid_tiles.py +305 -0
- plexora/server/models/data_model.py +1659 -0
- plexora/server/models/database_model.py +169 -0
- plexora/server/models/project.py +1270 -0
- plexora/server/plugins.py +199 -0
- plexora/server/routes/browse_routes.py +41 -0
- plexora/server/routes/data_routes.py +294 -0
- plexora/server/routes/import_routes.py +603 -0
- plexora/server/routes/page_routes.py +139 -0
- plexora/server/routes/project_routes.py +438 -0
- plexora/server/routes/quick_view_routes.py +62 -0
- plexora/server/routes/system_routes.py +39 -0
- plexora/server/routes/tool_routes.py +418 -0
- plexora/server/utils/addHEColumns.py +11 -0
- plexora/server/utils/fast_png.py +97 -0
- plexora/server/utils/fullConversion.py +50 -0
- plexora/server/utils/native_dialog.py +168 -0
- plexora/server/utils/pre_normalization.py +47 -0
- plexora/server/utils/segmentation_pyramid.py +598 -0
- plexora/server/utils/smallestenclosingcircle.py +126 -0
- plexora/server/utils/tiffsurgeon.py +372 -0
- plexora/server_cli.py +48 -0
- plexora/test.py +0 -0
- plexora-0.0.1.dist-info/METADATA +341 -0
- plexora-0.0.1.dist-info/RECORD +248 -0
- plexora-0.0.1.dist-info/WHEEL +5 -0
- plexora-0.0.1.dist-info/entry_points.txt +12 -0
- plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
- 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}"
|
plexora/api/__init__.py
ADDED
|
@@ -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
|
+
]
|