plexora 0.0.2__tar.gz → 0.0.7__tar.gz
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-0.0.2/plexora.egg-info → plexora-0.0.7}/PKG-INFO +43 -9
- {plexora-0.0.2 → plexora-0.0.7}/README.md +40 -8
- {plexora-0.0.2 → plexora-0.0.7}/plexora/__init__.py +83 -8
- plexora-0.0.7/plexora/_resources.py +236 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/api/__init__.py +11 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/api/dataset.py +255 -14
- {plexora-0.0.2 → plexora-0.0.7}/plexora/api/plugin.py +120 -1
- plexora-0.0.7/plexora/askpass.py +157 -0
- plexora-0.0.7/plexora/cli.py +1650 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/vendor_bundle.js +1 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/import.css +55 -104
- plexora-0.0.7/plexora/client/src/css/main.css +3576 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/openProject.css +15 -3
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/quickView.css +46 -2
- plexora-0.0.7/plexora/client/src/css/settings.css +1030 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/tokens.css +13 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/viewer.css +134 -12
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/main.js +153 -3
- plexora-0.0.7/plexora/client/src/js/services/appRouter.js +438 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/appStatus.js +31 -2
- plexora-0.0.7/plexora/client/src/js/services/browsePicker.js +126 -0
- plexora-0.0.7/plexora/client/src/js/services/connectionModal.js +785 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/dataLayer.js +7 -13
- plexora-0.0.7/plexora/client/src/js/services/dataLocation.js +764 -0
- plexora-0.0.7/plexora/client/src/js/services/fileLocation.js +704 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/importFormValidation.js +107 -2
- plexora-0.0.7/plexora/client/src/js/services/keyboardShortcuts.js +222 -0
- plexora-0.0.7/plexora/client/src/js/services/logTerminal.js +126 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/numericData.js +28 -0
- plexora-0.0.7/plexora/client/src/js/services/pageBoot.js +103 -0
- plexora-0.0.7/plexora/client/src/js/services/pathPicker.js +886 -0
- plexora-0.0.7/plexora/client/src/js/services/placePicker.js +244 -0
- plexora-0.0.7/plexora/client/src/js/services/remoteGlobe.js +633 -0
- plexora-0.0.7/plexora/client/src/js/services/remoteState.js +533 -0
- plexora-0.0.7/plexora/client/src/js/services/resourceRouting.js +206 -0
- plexora-0.0.7/plexora/client/src/js/services/resourceStatus.js +418 -0
- plexora-0.0.7/plexora/client/src/js/services/sessionExpiry.js +338 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/channelList.js +69 -27
- plexora-0.0.7/plexora/client/src/js/views/channelNamesUpload.js +906 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/colorSwatchPicker.js +11 -7
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/dataSourceField.js +35 -5
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/imageViewer.js +11 -7
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/navbarControls.js +14 -32
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/openProjectPage.js +1 -9
- plexora-0.0.7/plexora/client/src/js/views/popoverPortal.js +89 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/projectEdit.js +227 -20
- plexora-0.0.7/plexora/client/src/js/views/quickViewLanding.js +202 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/requirementsModal.js +26 -7
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/searchableSelect.js +11 -8
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/segmentationProgress.js +5 -0
- plexora-0.0.7/plexora/client/src/js/views/segmentationWait.js +296 -0
- plexora-0.0.7/plexora/client/src/js/views/settingsPage.js +1233 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/toolLoader.js +231 -2
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/viewerControls.js +165 -27
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/viewerManager.js +11 -2
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/viewerSidebar.js +134 -17
- plexora-0.0.7/plexora/client/templates/_fragment.html +34 -0
- plexora-0.0.7/plexora/client/templates/base.html +379 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/index.html +69 -23
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/open_project.html +96 -96
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/project_columns.html +3 -3
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/project_edit.html +220 -196
- plexora-0.0.7/plexora/client/templates/settings.html +545 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/upload.html +47 -3
- plexora-0.0.7/plexora/connect.py +1890 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/datasource.py +22 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/jupyter.py +215 -1
- plexora-0.0.7/plexora/nodes.py +730 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/notebook_env.py +366 -366
- {plexora-0.0.2 → plexora-0.0.7}/plexora/paths.py +517 -486
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/__init__.py +105 -100
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/values.py +4 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +92 -133
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +13 -10
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/__init__.py +38 -2
- plexora-0.0.7/plexora/plugins/figure_builder/server/compose.py +825 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/export.py +280 -20
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/pixels.py +110 -6
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/render.py +58 -7
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/schema.py +377 -37
- plexora-0.0.7/plexora/plugins/figure_builder/server/shapegeom.py +189 -0
- plexora-0.0.7/plexora/plugins/figure_builder/server/strokegeom.py +337 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/textmetrics.py +2 -2
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureActions.js +659 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureBuilderApi.js +15 -3
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCanvas.js +1303 -174
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureChoiceField.js +386 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureColorField.js +294 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureContextBar.js +500 -426
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureContextMenu.js +19 -3
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureImagePanel.js +1558 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureLibrary.js +13 -9
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureLineDefs.js +142 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureLinePanel.js +445 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figurePanelCompositor.js +157 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figurePointEditor.js +575 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureQuickEdit.js +391 -99
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureRichText.js +46 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureSceneSnapshot.js +12 -7
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureSchema.js +466 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapeDefs.js +235 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapeDrawing.js +288 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapeGeometry.js +546 -0
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapePanel.js +404 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureSidebarController.js +52 -14
- plexora-0.0.7/plexora/plugins/figure_builder/static/figureStrokeGeometry.js +323 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureTextEditor.js +65 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureTextPanel.js +18 -3
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureViewOptions.js +15 -9
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureWorkspace.js +626 -192
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figure_builder.css +2367 -336
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/templates/figure_builder/library.html +10 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -27
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +132 -41
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/__init__.py +5 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/model.py +37 -39
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/routes.py +29 -29
- plexora-0.0.7/plexora/plugins/gating/server/tableops.py +138 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/csvGatingList.js +7 -5
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/gatingApi.js +67 -49
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/__init__.py +100 -95
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/mapping.py +5 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/routes.py +69 -62
- plexora-0.0.7/plexora/plugins/roi/server/tableops.py +138 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiApi.js +15 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiFigureBridge.js +8 -13
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/inspection.py +31 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/centroid_tiles.py +32 -3
- plexora-0.0.7/plexora/server/models/data_migration.py +307 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/data_model.py +2379 -1670
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/database_model.py +18 -0
- plexora-0.0.7/plexora/server/models/nodes.py +312 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/project.py +150 -1
- plexora-0.0.7/plexora/server/models/recipes.py +495 -0
- plexora-0.0.7/plexora/server/models/remote_sessions.py +707 -0
- plexora-0.0.7/plexora/server/models/remotes.py +267 -0
- plexora-0.0.7/plexora/server/models/secret_store.py +51 -0
- plexora-0.0.7/plexora/server/node/api.py +1138 -0
- plexora-0.0.7/plexora/server/node/app.py +508 -0
- plexora-0.0.7/plexora/server/node/resources.py +600 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/plugins.py +27 -0
- plexora-0.0.7/plexora/server/providers/__init__.py +185 -0
- plexora-0.0.7/plexora/server/providers/base.py +340 -0
- plexora-0.0.7/plexora/server/providers/http.py +453 -0
- plexora-0.0.7/plexora/server/providers/local.py +356 -0
- plexora-0.0.7/plexora/server/providers/node.py +542 -0
- plexora-0.0.7/plexora/server/providers/operations.py +139 -0
- plexora-0.0.7/plexora/server/providers/wire.py +131 -0
- plexora-0.0.7/plexora/server/routes/browse_routes.py +281 -0
- plexora-0.0.7/plexora/server/routes/data_routes.py +664 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/import_routes.py +393 -34
- plexora-0.0.7/plexora/server/routes/page_routes.py +280 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/project_routes.py +158 -5
- plexora-0.0.7/plexora/server/routes/quick_view_routes.py +127 -0
- plexora-0.0.7/plexora/server/routes/settings_routes.py +1059 -0
- plexora-0.0.7/plexora/server/routes/transfer_routes.py +198 -0
- plexora-0.0.7/plexora/server/utils/channel_file.py +272 -0
- plexora-0.0.7/plexora/server/utils/dir_listing.py +144 -0
- plexora-0.0.7/plexora/server/utils/file_transfer.py +192 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/native_dialog.py +33 -2
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/segmentation_pyramid.py +131 -6
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server_cli.py +6 -1
- {plexora-0.0.2 → plexora-0.0.7/plexora.egg-info}/PKG-INFO +43 -9
- {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/SOURCES.txt +99 -1
- {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/requires.txt +2 -0
- {plexora-0.0.2 → plexora-0.0.7}/pyproject.toml +15 -1
- plexora-0.0.7/tests/test_app_router.py +69 -0
- plexora-0.0.7/tests/test_app_shell.py +222 -0
- plexora-0.0.7/tests/test_browse_routes.py +430 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_cell_mode_control.py +50 -2
- plexora-0.0.7/tests/test_channel_names_modal.py +303 -0
- plexora-0.0.7/tests/test_channel_names_upload.py +362 -0
- plexora-0.0.7/tests/test_channel_rename_state.py +244 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_cli.py +951 -595
- plexora-0.0.7/tests/test_column_classifier_css.py +110 -0
- plexora-0.0.7/tests/test_connect.py +1335 -0
- plexora-0.0.7/tests/test_connection_modal.py +273 -0
- plexora-0.0.7/tests/test_data_location.py +416 -0
- plexora-0.0.7/tests/test_file_location.py +232 -0
- plexora-0.0.7/tests/test_file_transfer_node.py +267 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_hd_slider_domain.py +12 -3
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_jupyter_viewer.py +14 -1
- plexora-0.0.7/tests/test_mask_without_table.py +78 -0
- plexora-0.0.7/tests/test_nav_shortcuts.py +45 -0
- plexora-0.0.7/tests/test_node_direct_routing.py +210 -0
- plexora-0.0.7/tests/test_node_disconnect.py +218 -0
- plexora-0.0.7/tests/test_node_dynamic.py +581 -0
- plexora-0.0.7/tests/test_node_image.py +712 -0
- plexora-0.0.7/tests/test_node_routes.py +552 -0
- plexora-0.0.7/tests/test_node_table.py +414 -0
- plexora-0.0.7/tests/test_node_warm_and_cache.py +338 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_notebook_mode.py +10 -1
- plexora-0.0.7/tests/test_path_picker.py +252 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_plugin_boundary.py +2 -1
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_plugins.py +122 -2
- plexora-0.0.7/tests/test_popover_portal.py +141 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_quick_view_routes.py +73 -0
- plexora-0.0.7/tests/test_recipes.py +308 -0
- plexora-0.0.7/tests/test_reconnect.py +295 -0
- plexora-0.0.7/tests/test_remote_connect.py +874 -0
- plexora-0.0.7/tests/test_remote_globe.py +295 -0
- plexora-0.0.7/tests/test_remote_health.py +329 -0
- plexora-0.0.7/tests/test_remote_state.py +190 -0
- plexora-0.0.7/tests/test_resource_status_banner.py +94 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_segmentation_mapping.py +121 -4
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_segmentation_pyramid.py +197 -0
- plexora-0.0.7/tests/test_segmentation_wait.py +154 -0
- plexora-0.0.7/tests/test_session_expiry.py +169 -0
- plexora-0.0.7/tests/test_session_walltime.py +372 -0
- plexora-0.0.7/tests/test_settings_page.py +357 -0
- plexora-0.0.7/tests/test_settings_remotes_page.py +321 -0
- plexora-0.0.7/tests/test_tile_url.py +42 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_cards.py +52 -0
- plexora-0.0.7/tests/test_transfer_routes.py +179 -0
- plexora-0.0.7/tests/test_unreachable_node.py +393 -0
- plexora-0.0.7/tests/test_view_menu.py +155 -0
- plexora-0.0.2/plexora/cli.py +0 -876
- plexora-0.0.2/plexora/client/src/css/main.css +0 -903
- plexora-0.0.2/plexora/client/src/js/services/browsePicker.js +0 -73
- plexora-0.0.2/plexora/client/src/js/views/quickViewLanding.js +0 -136
- plexora-0.0.2/plexora/client/templates/base.html +0 -212
- plexora-0.0.2/plexora/connect.py +0 -528
- plexora-0.0.2/plexora/plugins/figure_builder/server/compose.py +0 -365
- plexora-0.0.2/plexora/plugins/figure_builder/static/figureActions.js +0 -363
- plexora-0.0.2/plexora/plugins/figure_builder/static/figureSchema.js +0 -218
- plexora-0.0.2/plexora/server/routes/browse_routes.py +0 -41
- plexora-0.0.2/plexora/server/routes/data_routes.py +0 -294
- plexora-0.0.2/plexora/server/routes/page_routes.py +0 -139
- plexora-0.0.2/plexora/server/routes/quick_view_routes.py +0 -62
- plexora-0.0.2/tests/test_browse_routes.py +0 -97
- plexora-0.0.2/tests/test_connect.py +0 -371
- {plexora-0.0.2 → plexora-0.0.7}/LICENSE +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/MANIFEST.in +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/__main__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/_url.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/api/http.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/api/store.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/354_bundle.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/354_bundle.js.LICENSE.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/418_bundle.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/418_bundle.js.LICENSE.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/770_bundle.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/vendor_bundle.js.LICENSE.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/apple-touch-icon.png +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/favicon.ico +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo.ai +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo.svg +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo_with_text.ai +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo_with_text.svg +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/pluginRegistry.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/datasetContext.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/glRenderer.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/passVariablesToFrontend.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/simpleEventHandler.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/vendor.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/columnClassifier.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/coordinateField.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/miniMap.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/rainbow.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/rgbImageViewer.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/workers/tileDecoder.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/shaders/frag.glsl +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/shaders/vert.glsl +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/__init__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/__init__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/state.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/variables.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerApi.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerColors.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerLegend.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerState.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cell_explorer.css +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/__init__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/export_jobs.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/operations.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/provenance.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/repository.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/sources.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCaptureBoxes.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCaptureDock.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCaptureTool.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureConfirm.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureDocumentState.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureExportUi.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/__init__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/anndata_gates.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/database.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/gating.css +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/gatingSidebarController.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/templates/gating/legacy.html +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/templates/gating/panel.html +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/__init__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/adapters.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/geojson.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/geometry.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/operations.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/repository.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/schema.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roi.css +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiGeometry.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiRenderer.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiSidebarController.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiState.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiTools.js +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/templates/roi/panel.html +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/proxy.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/__init__.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/anndata_adapter.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/base.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/classify.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/csv_adapter.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/spatialdata_adapter.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/system_routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/tool_routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/addHEColumns.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/fast_png.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/fullConversion.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/pre_normalization.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/smallestenclosingcircle.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/tiffsurgeon.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora/test.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/dependency_links.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/entry_points.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/top_level.txt +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/setup.cfg +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_anndata_adapter.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_api_dataset.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_api_store.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_auth_token.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_cell_color_layer.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_centroid_tiles.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_channel_overview.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_classify_columns.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_config_store.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_csv_adapter.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_database_model.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_datalayer_requests.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_derive_anndata_channel_names.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_detect_data_type.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_icon_names.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_label_outlines.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_label_tile_lifecycle.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_marker_split.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_metadata_columns.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_mini_map.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_notebook_env.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_optional_segmentation.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_page_assets.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_page_routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_paths.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_plugin_css_boundary.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_project_edit_routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_project_record.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_refresh_dataset.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_register_anndata_datasource.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_register_image_datasource.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_rename_channels.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_requirements_routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_segmentation_mode_routes.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_shared_roots.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_spatialdata_adapter.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_assets.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_coexistence.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_switching.py +0 -0
- {plexora-0.0.2 → plexora-0.0.7}/tests/test_url_helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plexora
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: OpenSeadragon-based cellular image viewing and analysis tool.
|
|
5
5
|
License-Expression: LicenseRef-Plexora-Academic-1.0
|
|
6
6
|
Requires-Python: <3.14,>=3.12
|
|
@@ -14,6 +14,7 @@ Requires-Dist: itsdangerous>=2.2
|
|
|
14
14
|
Requires-Dist: Jinja2>=3.1
|
|
15
15
|
Requires-Dist: numpy>=2.0
|
|
16
16
|
Requires-Dist: ome-types>=0.6.3
|
|
17
|
+
Requires-Dist: openpyxl>=3.1
|
|
17
18
|
Requires-Dist: orjson>=3.11
|
|
18
19
|
Requires-Dist: pandas>=3.0.5
|
|
19
20
|
Requires-Dist: Pillow>=10.4
|
|
@@ -28,6 +29,7 @@ Requires-Dist: shapely>=2.1.2
|
|
|
28
29
|
Requires-Dist: spatialdata>=0.8.0
|
|
29
30
|
Requires-Dist: threadpoolctl>=3.6.0
|
|
30
31
|
Requires-Dist: tifffile>=2026.7.31
|
|
32
|
+
Requires-Dist: urllib3>=2.0
|
|
31
33
|
Requires-Dist: waitress
|
|
32
34
|
Requires-Dist: Werkzeug>=3.1
|
|
33
35
|
Requires-Dist: zarr>=3
|
|
@@ -72,12 +74,19 @@ There are four ways to run Plexora, all of which end in the same viewer:
|
|
|
72
74
|
| --- | --- |
|
|
73
75
|
| Your own machine, a terminal | `plexora` |
|
|
74
76
|
| Your own machine, a notebook | `plexora.view("my_dataset")` |
|
|
75
|
-
| A remote machine you can ssh into |
|
|
76
|
-
| A hosted notebook
|
|
77
|
+
| A remote machine you can ssh into | **Settings → Remote servers → Connect** — or `plexora connect user@host` |
|
|
78
|
+
| A hosted notebook or an HPC terminal | `plexora` — it works out where it is running and prints a URL that works |
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
Save a remote server once and reconnecting is a button: Plexora handles the SSH
|
|
81
|
+
connection, the ports, the tunnel and the URL, and relays a password or 2FA
|
|
82
|
+
prompt to the page if the server asks for one. It never stores the password.
|
|
83
|
+
|
|
84
|
+
**[docs/SETUP_GUIDE.md](docs/SETUP_GUIDE.md)** is the step-by-step version for
|
|
85
|
+
people who would rather not learn about tunnels — organised by where your data
|
|
86
|
+
is, with a compatibility matrix and a screenshot for every screen.
|
|
87
|
+
**[DEPLOYMENT.md](DEPLOYMENT.md)** is the same ground for a technical reader,
|
|
88
|
+
from a fresh conda environment to HPC job submission, with the real output of
|
|
89
|
+
each command. The rest of this README is the short version.
|
|
81
90
|
|
|
82
91
|
### Where your data lives
|
|
83
92
|
|
|
@@ -147,9 +156,11 @@ If the remote `plexora` is not on a non-interactive `PATH` — which is common
|
|
|
147
156
|
with conda — name it explicitly:
|
|
148
157
|
|
|
149
158
|
```bash
|
|
150
|
-
plexora connect user@server.lab.edu --remote-command
|
|
159
|
+
plexora connect user@server.lab.edu --remote-command /home/you/miniconda3/envs/imaging
|
|
151
160
|
```
|
|
152
161
|
|
|
162
|
+
The environment path is enough — `bin/plexora` is filled in for you.
|
|
163
|
+
|
|
153
164
|
**Or do it by hand.** On the remote machine:
|
|
154
165
|
|
|
155
166
|
```bash
|
|
@@ -166,13 +177,13 @@ Plexora belongs in a job. `--srun` submits one and tunnels to whichever node
|
|
|
166
177
|
the scheduler grants — the target is the *login* node:
|
|
167
178
|
|
|
168
179
|
```bash
|
|
169
|
-
plexora connect user@o2.hms.harvard.edu --srun "-p interactive -t 4:00:00 --mem
|
|
180
|
+
plexora connect user@o2.hms.harvard.edu --srun "-p interactive -t 4:00:00 -c 16 --mem 128G"
|
|
170
181
|
```
|
|
171
182
|
|
|
172
183
|
Allocation may queue; it says so while it waits. Ctrl+C ends the job.
|
|
173
184
|
|
|
174
185
|
By hand, the same thing is two steps: start an interactive job
|
|
175
|
-
(`srun --pty -p interactive -t 4:00:00 --mem
|
|
186
|
+
(`srun --pty -p interactive -t 4:00:00 -c 16 --mem 128G bash`), then run
|
|
176
187
|
`plexora --remote` inside it. It detects the job, works out which compute node
|
|
177
188
|
it is on and which login node you came through, and prints the two-hop
|
|
178
189
|
`ssh -J` command for it.
|
|
@@ -210,6 +221,29 @@ The image also sets `PLEXORA_HOST=0.0.0.0` — published ports would never reach
|
|
|
210
221
|
it otherwise — and `PLEXORA_DOCKER=1`, which switches the import page to
|
|
211
222
|
container-shaped path hints.
|
|
212
223
|
|
|
224
|
+
## Data on more than one machine
|
|
225
|
+
|
|
226
|
+
Sometimes the image and the cell table are not on the same computer — the slide
|
|
227
|
+
is on cluster scratch and the `.h5ad` came back to your laptop. Start a **data
|
|
228
|
+
node** where the data is:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
plexora node serve --serve image:tumor=/scratch/me/tumor.ome.tif
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
It prints a token. Register the node in the viewer under **Settings → Data
|
|
235
|
+
nodes**, then point a project at it from that project's **Edit** page, under
|
|
236
|
+
*Where the data lives*.
|
|
237
|
+
|
|
238
|
+
A node is a Plexora with the viewer switched off: it holds files and answers
|
|
239
|
+
questions about them. Your projects, ROIs, gates and figures all stay on the
|
|
240
|
+
machine you are looking at, so a node can restart or disappear without any of
|
|
241
|
+
your work being at risk — the project still opens, and whatever came from that
|
|
242
|
+
node is absent and says so.
|
|
243
|
+
|
|
244
|
+
See [DEPLOYMENT.md](DEPLOYMENT.md#7-data-on-more-than-one-machine) for the
|
|
245
|
+
tunnel recipes, what actually crosses the network, and the limits.
|
|
246
|
+
|
|
213
247
|
## Clone and Run Codebase (for Developers)
|
|
214
248
|
|
|
215
249
|
```bash
|
|
@@ -31,12 +31,19 @@ There are four ways to run Plexora, all of which end in the same viewer:
|
|
|
31
31
|
| --- | --- |
|
|
32
32
|
| Your own machine, a terminal | `plexora` |
|
|
33
33
|
| Your own machine, a notebook | `plexora.view("my_dataset")` |
|
|
34
|
-
| A remote machine you can ssh into |
|
|
35
|
-
| A hosted notebook
|
|
34
|
+
| A remote machine you can ssh into | **Settings → Remote servers → Connect** — or `plexora connect user@host` |
|
|
35
|
+
| A hosted notebook or an HPC terminal | `plexora` — it works out where it is running and prints a URL that works |
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
Save a remote server once and reconnecting is a button: Plexora handles the SSH
|
|
38
|
+
connection, the ports, the tunnel and the URL, and relays a password or 2FA
|
|
39
|
+
prompt to the page if the server asks for one. It never stores the password.
|
|
40
|
+
|
|
41
|
+
**[docs/SETUP_GUIDE.md](docs/SETUP_GUIDE.md)** is the step-by-step version for
|
|
42
|
+
people who would rather not learn about tunnels — organised by where your data
|
|
43
|
+
is, with a compatibility matrix and a screenshot for every screen.
|
|
44
|
+
**[DEPLOYMENT.md](DEPLOYMENT.md)** is the same ground for a technical reader,
|
|
45
|
+
from a fresh conda environment to HPC job submission, with the real output of
|
|
46
|
+
each command. The rest of this README is the short version.
|
|
40
47
|
|
|
41
48
|
### Where your data lives
|
|
42
49
|
|
|
@@ -106,9 +113,11 @@ If the remote `plexora` is not on a non-interactive `PATH` — which is common
|
|
|
106
113
|
with conda — name it explicitly:
|
|
107
114
|
|
|
108
115
|
```bash
|
|
109
|
-
plexora connect user@server.lab.edu --remote-command
|
|
116
|
+
plexora connect user@server.lab.edu --remote-command /home/you/miniconda3/envs/imaging
|
|
110
117
|
```
|
|
111
118
|
|
|
119
|
+
The environment path is enough — `bin/plexora` is filled in for you.
|
|
120
|
+
|
|
112
121
|
**Or do it by hand.** On the remote machine:
|
|
113
122
|
|
|
114
123
|
```bash
|
|
@@ -125,13 +134,13 @@ Plexora belongs in a job. `--srun` submits one and tunnels to whichever node
|
|
|
125
134
|
the scheduler grants — the target is the *login* node:
|
|
126
135
|
|
|
127
136
|
```bash
|
|
128
|
-
plexora connect user@o2.hms.harvard.edu --srun "-p interactive -t 4:00:00 --mem
|
|
137
|
+
plexora connect user@o2.hms.harvard.edu --srun "-p interactive -t 4:00:00 -c 16 --mem 128G"
|
|
129
138
|
```
|
|
130
139
|
|
|
131
140
|
Allocation may queue; it says so while it waits. Ctrl+C ends the job.
|
|
132
141
|
|
|
133
142
|
By hand, the same thing is two steps: start an interactive job
|
|
134
|
-
(`srun --pty -p interactive -t 4:00:00 --mem
|
|
143
|
+
(`srun --pty -p interactive -t 4:00:00 -c 16 --mem 128G bash`), then run
|
|
135
144
|
`plexora --remote` inside it. It detects the job, works out which compute node
|
|
136
145
|
it is on and which login node you came through, and prints the two-hop
|
|
137
146
|
`ssh -J` command for it.
|
|
@@ -169,6 +178,29 @@ The image also sets `PLEXORA_HOST=0.0.0.0` — published ports would never reach
|
|
|
169
178
|
it otherwise — and `PLEXORA_DOCKER=1`, which switches the import page to
|
|
170
179
|
container-shaped path hints.
|
|
171
180
|
|
|
181
|
+
## Data on more than one machine
|
|
182
|
+
|
|
183
|
+
Sometimes the image and the cell table are not on the same computer — the slide
|
|
184
|
+
is on cluster scratch and the `.h5ad` came back to your laptop. Start a **data
|
|
185
|
+
node** where the data is:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
plexora node serve --serve image:tumor=/scratch/me/tumor.ome.tif
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
It prints a token. Register the node in the viewer under **Settings → Data
|
|
192
|
+
nodes**, then point a project at it from that project's **Edit** page, under
|
|
193
|
+
*Where the data lives*.
|
|
194
|
+
|
|
195
|
+
A node is a Plexora with the viewer switched off: it holds files and answers
|
|
196
|
+
questions about them. Your projects, ROIs, gates and figures all stay on the
|
|
197
|
+
machine you are looking at, so a node can restart or disappear without any of
|
|
198
|
+
your work being at risk — the project still opens, and whatever came from that
|
|
199
|
+
node is absent and says so.
|
|
200
|
+
|
|
201
|
+
See [DEPLOYMENT.md](DEPLOYMENT.md#7-data-on-more-than-one-machine) for the
|
|
202
|
+
tunnel recipes, what actually crosses the network, and the limits.
|
|
203
|
+
|
|
172
204
|
## Clone and Run Codebase (for Developers)
|
|
173
205
|
|
|
174
206
|
```bash
|
|
@@ -2,17 +2,30 @@ import multiprocessing
|
|
|
2
2
|
|
|
3
3
|
multiprocessing.freeze_support()
|
|
4
4
|
|
|
5
|
-
from flask import Flask, request
|
|
5
|
+
from flask import Flask, jsonify, request
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
import hmac
|
|
9
9
|
import os
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
# Cap every numeric thread pool at this process's REAL CPU allocation, and do
|
|
13
|
+
# it here -- before anything numeric is imported -- so the environment
|
|
14
|
+
# variables OpenBLAS/MKL/OpenMP read at load time are already in place.
|
|
15
|
+
#
|
|
16
|
+
# This replaces a bare `threadpool_limits()`, which capped nothing: threadpoolctl
|
|
17
|
+
# only applies a limit when one is passed, so that call's only effect was to
|
|
18
|
+
# force the BLAS libraries to load. That effect mattered (it is what stops two
|
|
19
|
+
# request threads racing to load them and deadlocking) and is preserved --
|
|
20
|
+
# configure_thread_pools() calls threadpool_limits with a real limit, which
|
|
21
|
+
# loads them just the same.
|
|
22
|
+
#
|
|
23
|
+
# See plexora._resources for why os.cpu_count() is the wrong question here: on
|
|
24
|
+
# a 2-core SLURM allocation carved out of a 64-core node it reports 64, and the
|
|
25
|
+
# resulting oversubscription is what buried the server on HMS O2.
|
|
26
|
+
from plexora._resources import configure_thread_pools
|
|
27
|
+
|
|
28
|
+
configure_thread_pools()
|
|
16
29
|
|
|
17
30
|
# Where data lives is `plexora.paths`' business, and it is resolved on demand
|
|
18
31
|
# rather than here. This module used to compute `data_path` at import, whose
|
|
@@ -29,6 +42,43 @@ from plexora._url import clean_prefix as _clean_base_url
|
|
|
29
42
|
#: Where the entry URL's `?token=` is remembered for the rest of the session.
|
|
30
43
|
AUTH_COOKIE = "plexora_auth"
|
|
31
44
|
|
|
45
|
+
#: What the unavailable-resource handler has already said, and the load it said
|
|
46
|
+
#: it for. One screenful of the viewer is dozens of tile requests and every one
|
|
47
|
+
#: of them fails the same way against the same absent machine, so the sentence
|
|
48
|
+
#: is printed once and the rest are quiet.
|
|
49
|
+
_said_unavailable = set()
|
|
50
|
+
_said_for_generation = None
|
|
51
|
+
_said_lock = threading.Lock()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _say_unavailable_once(message, context=""):
|
|
55
|
+
"""Print `message` unless this load of a project has already printed it.
|
|
56
|
+
|
|
57
|
+
Keyed on `data_model.load_generation` rather than cleared on a timer:
|
|
58
|
+
reloading is the only thing that re-reads a project's bindings, so it is
|
|
59
|
+
exactly when "is that node here" can have a different answer -- which is
|
|
60
|
+
the same reason the tile cache keys on it.
|
|
61
|
+
|
|
62
|
+
`context` rides along on the line without joining the key, so the line
|
|
63
|
+
names one of the requests that hit this rather than standing for none of
|
|
64
|
+
them.
|
|
65
|
+
"""
|
|
66
|
+
global _said_for_generation
|
|
67
|
+
|
|
68
|
+
# Local, like every other import of the server package in this module: it
|
|
69
|
+
# imports this one back, and this runs long after both are built anyway.
|
|
70
|
+
from plexora.server.models import data_model
|
|
71
|
+
|
|
72
|
+
with _said_lock:
|
|
73
|
+
if data_model.load_generation != _said_for_generation:
|
|
74
|
+
_said_for_generation = data_model.load_generation
|
|
75
|
+
_said_unavailable.clear()
|
|
76
|
+
if message in _said_unavailable:
|
|
77
|
+
return
|
|
78
|
+
_said_unavailable.add(message)
|
|
79
|
+
print(f"{context} -- {message}" if context else message)
|
|
80
|
+
|
|
81
|
+
|
|
32
82
|
app = None
|
|
33
83
|
|
|
34
84
|
|
|
@@ -134,10 +184,35 @@ def create_app(plugins=None):
|
|
|
134
184
|
# cannot read out of an HttpOnly cookie.
|
|
135
185
|
return response
|
|
136
186
|
|
|
187
|
+
# The one failure the provider layer is built to survive, answered as one.
|
|
188
|
+
#
|
|
189
|
+
# `ResourceUnavailable` means a machine did not answer -- a laptop asleep, a
|
|
190
|
+
# tunnel dropped, a compute job ended, a node disconnected -- and it arrives
|
|
191
|
+
# already carrying the sentence its owner should read. Unhandled, Flask
|
|
192
|
+
# called that a 500 and logged the whole traceback, and since the viewer
|
|
193
|
+
# asks for one tile at a time, a single screenful of a project on a vanished
|
|
194
|
+
# node buried the terminal in dozens of identical stacks. None of it was
|
|
195
|
+
# news: the node is named in the first line, and `/resource_status` has
|
|
196
|
+
# already put the same sentence on screen with the button that fixes it.
|
|
197
|
+
#
|
|
198
|
+
# 503 rather than 500 because the condition is temporary by definition, and
|
|
199
|
+
# rather than 404 because the resource has not gone -- the road to it has.
|
|
200
|
+
# The answer is JSON because only routes that read data can arrive here;
|
|
201
|
+
# rendering a page touches no resource.
|
|
202
|
+
from plexora.server.providers.base import ResourceUnavailable
|
|
203
|
+
|
|
204
|
+
@app.errorhandler(ResourceUnavailable)
|
|
205
|
+
def resource_unavailable(exc):
|
|
206
|
+
# The path goes in the line but not in the key: one request stands for
|
|
207
|
+
# the screenful, and keying on it would print every one of them.
|
|
208
|
+
_say_unavailable_once(str(exc), f"{request.method} {request.path}")
|
|
209
|
+
return jsonify(success=False, error=str(exc), node=exc.node,
|
|
210
|
+
unavailable=True), 503
|
|
211
|
+
|
|
137
212
|
# Imported here (not at module top) purely for their route-registration
|
|
138
213
|
# side effects -- see the docstring above for why `app` must already be
|
|
139
214
|
# assigned by this point.
|
|
140
|
-
from plexora.server.routes import page_routes, data_routes, import_routes, quick_view_routes, browse_routes, tool_routes, system_routes, project_routes
|
|
215
|
+
from plexora.server.routes import page_routes, data_routes, import_routes, quick_view_routes, browse_routes, transfer_routes, tool_routes, system_routes, project_routes, settings_routes
|
|
141
216
|
from plexora.server.models import data_model, database_model
|
|
142
217
|
from plexora.server import plugins as plugin_registry
|
|
143
218
|
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""How much CPU this process actually has, and everything sized from it.
|
|
2
|
+
|
|
3
|
+
One number, computed once, feeding three decisions that used to be made
|
|
4
|
+
independently and wrongly:
|
|
5
|
+
|
|
6
|
+
- how many threads the numeric stack (BLAS/OpenMP, blosc) may start,
|
|
7
|
+
- how many request workers waitress runs,
|
|
8
|
+
- how many requests the page may have in flight while restoring channels.
|
|
9
|
+
|
|
10
|
+
The reason this module exists is that **`os.cpu_count()` is the wrong question
|
|
11
|
+
on a cluster**. It reports the machine's cores, not the allocation's. A SLURM
|
|
12
|
+
job asking for `--cpus-per-task=2` on a 64-core node still sees 64 there, so
|
|
13
|
+
OpenBLAS starts 64 threads, numcodecs starts 8 decompression threads, and all
|
|
14
|
+
of them contend for two cores' worth of quota. Measured on HMS O2, that
|
|
15
|
+
oversubscription is what turned "opening a project is slow" into a task queue
|
|
16
|
+
that never drained and a reverse proxy returning 502.
|
|
17
|
+
|
|
18
|
+
There is no single reliable signal, so this takes the minimum of the ones that
|
|
19
|
+
exist, each of which is authoritative when present:
|
|
20
|
+
|
|
21
|
+
- `sched_getaffinity` -- right when the site pins jobs with a cpuset. Silent
|
|
22
|
+
(reports every core) when the site uses CPU *quota* instead.
|
|
23
|
+
- the cgroup CPU quota -- right in exactly the case affinity is silent.
|
|
24
|
+
- `SLURM_CPUS_PER_TASK` -- what was actually requested, and the only signal
|
|
25
|
+
that survives a site configuring neither of the above.
|
|
26
|
+
|
|
27
|
+
Deliberately a leaf module: stdlib only, imports nothing from `plexora`, so
|
|
28
|
+
`plexora/__init__.py` can call it before anything numeric is imported.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
|
|
35
|
+
#: Never fewer than this many request workers, whatever the allocation. A
|
|
36
|
+
#: single-core allocation still needs enough workers that a blocked tile read
|
|
37
|
+
#: cannot starve /health -- the liveness probe does no work at all, and a page
|
|
38
|
+
#: that cannot reach it reports the server as dead while it is merely busy.
|
|
39
|
+
MIN_WORKER_THREADS = 8
|
|
40
|
+
|
|
41
|
+
#: Above this, more workers only add concurrent full-resolution reads to hold
|
|
42
|
+
#: in memory. All image I/O is serialized downstream anyway (see data_model's
|
|
43
|
+
#: note on the zarr io thread and tifffile's per-file read lock), so the extra
|
|
44
|
+
#: workers would queue on that lock rather than do useful work.
|
|
45
|
+
MAX_WORKER_THREADS = 32
|
|
46
|
+
|
|
47
|
+
#: The page never fans out wider than this even on a large node. Past roughly
|
|
48
|
+
#: this point the restore burst is bounded by the serialized reader rather than
|
|
49
|
+
#: by the client, and a wider burst just moves the queue from the browser into
|
|
50
|
+
#: waitress where it is harder to see.
|
|
51
|
+
MAX_CLIENT_CONCURRENCY = 8
|
|
52
|
+
|
|
53
|
+
#: Floor for the same, so a one- or two-core allocation still overlaps a little
|
|
54
|
+
#: rather than restoring strictly one channel at a time.
|
|
55
|
+
MIN_CLIENT_CONCURRENCY = 2
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _affinity_cpus():
|
|
59
|
+
"""Cores this process may be scheduled on, or None where unsupported.
|
|
60
|
+
|
|
61
|
+
Absent on macOS and Windows, which is why every caller has to tolerate None
|
|
62
|
+
rather than treating it as the answer.
|
|
63
|
+
"""
|
|
64
|
+
getaffinity = getattr(os, "sched_getaffinity", None)
|
|
65
|
+
if getaffinity is None:
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
count = len(getaffinity(0))
|
|
69
|
+
except OSError:
|
|
70
|
+
return None
|
|
71
|
+
return count or None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _cgroup_cpus():
|
|
75
|
+
"""Cores implied by the cgroup CPU quota, or None when unlimited/absent.
|
|
76
|
+
|
|
77
|
+
This is the signal that catches a site using `cpu.max` quota without a
|
|
78
|
+
cpuset: affinity reports the whole node, and only the quota knows the job
|
|
79
|
+
was given two cores. Both cgroup versions are read because clusters run
|
|
80
|
+
both, and a wrong answer here is worse than no answer -- so anything
|
|
81
|
+
unparseable returns None and lets another signal decide.
|
|
82
|
+
"""
|
|
83
|
+
# cgroup v2: "<quota> <period>", or "max <period>" when unlimited.
|
|
84
|
+
try:
|
|
85
|
+
quota_text, period_text = _read_text("/sys/fs/cgroup/cpu.max").split()
|
|
86
|
+
if quota_text != "max":
|
|
87
|
+
return _quota_to_cpus(int(quota_text), int(period_text))
|
|
88
|
+
except (OSError, ValueError):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# cgroup v1: the two halves live in separate files, and a quota of -1
|
|
92
|
+
# means unlimited.
|
|
93
|
+
try:
|
|
94
|
+
quota = int(_read_text("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"))
|
|
95
|
+
period = int(_read_text("/sys/fs/cgroup/cpu/cpu.cfs_period_us"))
|
|
96
|
+
if quota > 0:
|
|
97
|
+
return _quota_to_cpus(quota, period)
|
|
98
|
+
except (OSError, ValueError):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _read_text(path):
|
|
105
|
+
with open(path, "r", encoding="utf-8") as handle:
|
|
106
|
+
return handle.read().strip()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _quota_to_cpus(quota, period):
|
|
110
|
+
"""Whole cores a quota/period pair is worth, rounded up, minimum 1.
|
|
111
|
+
|
|
112
|
+
Rounded UP rather than down because a fractional allocation (say 1.5 cores)
|
|
113
|
+
still benefits from two threads: one of them is usually blocked on I/O, and
|
|
114
|
+
rounding down to 1 would serialize work the quota can genuinely overlap.
|
|
115
|
+
"""
|
|
116
|
+
if period <= 0:
|
|
117
|
+
return None
|
|
118
|
+
return max(1, -(-quota // period))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _scheduler_cpus():
|
|
122
|
+
"""What the batch scheduler was asked for, or None outside a job.
|
|
123
|
+
|
|
124
|
+
Last of the three signals and the crudest, but it is the only one that
|
|
125
|
+
survives a site configuring neither cpuset nor quota -- which does happen,
|
|
126
|
+
and is precisely the configuration where the other two lie by reporting the
|
|
127
|
+
whole node.
|
|
128
|
+
|
|
129
|
+
Scheduler variables only. `OMP_NUM_THREADS` looks like it belongs here and
|
|
130
|
+
does not: it states a thread-pool preference rather than an allocation, and
|
|
131
|
+
`configure_thread_pools` sets it -- so reading it back would make this
|
|
132
|
+
function's own output one of its own inputs.
|
|
133
|
+
"""
|
|
134
|
+
for name in ("SLURM_CPUS_PER_TASK", "SLURM_CPUS_ON_NODE",
|
|
135
|
+
"PBS_NP", "NSLOTS"):
|
|
136
|
+
raw = os.environ.get(name)
|
|
137
|
+
if not raw:
|
|
138
|
+
continue
|
|
139
|
+
try:
|
|
140
|
+
value = int(str(raw).strip())
|
|
141
|
+
except ValueError:
|
|
142
|
+
continue
|
|
143
|
+
if value > 0:
|
|
144
|
+
return value
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def allocated_cpus():
|
|
149
|
+
"""Cores this process can actually use. Never zero.
|
|
150
|
+
|
|
151
|
+
The minimum of every signal that answered, because each one is right in a
|
|
152
|
+
situation where the others are silent, and a signal that is silent reports
|
|
153
|
+
the whole machine rather than admitting it does not know. Taking the
|
|
154
|
+
minimum is therefore the only combination that cannot over-subscribe.
|
|
155
|
+
"""
|
|
156
|
+
candidates = [value for value in (_affinity_cpus(), _cgroup_cpus(),
|
|
157
|
+
_scheduler_cpus(), os.cpu_count())
|
|
158
|
+
if value]
|
|
159
|
+
return max(1, min(candidates)) if candidates else 1
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def worker_threads():
|
|
163
|
+
"""How many request workers waitress should run.
|
|
164
|
+
|
|
165
|
+
Deliberately NOT equal to the core count. Most workers spend their time
|
|
166
|
+
blocked on the serialized image reader rather than computing, so more of
|
|
167
|
+
them than cores is both harmless and necessary -- it is what keeps a worker
|
|
168
|
+
free to answer /health while the rest wait.
|
|
169
|
+
"""
|
|
170
|
+
return max(MIN_WORKER_THREADS, min(allocated_cpus() * 2, MAX_WORKER_THREADS))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def client_concurrency():
|
|
174
|
+
"""How many requests the page may have in flight restoring channels.
|
|
175
|
+
|
|
176
|
+
Sized from cores rather than from workers because each of these requests
|
|
177
|
+
does real per-channel work at the far end, and one worker short of the pool
|
|
178
|
+
is reserved so the liveness probe is always answerable.
|
|
179
|
+
"""
|
|
180
|
+
ceiling = min(allocated_cpus(), worker_threads() - 1, MAX_CLIENT_CONCURRENCY)
|
|
181
|
+
return max(MIN_CLIENT_CONCURRENCY, ceiling)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def configure_thread_pools(cpus=None):
|
|
185
|
+
"""Cap every numeric thread pool at the allocation. Returns the cap used.
|
|
186
|
+
|
|
187
|
+
Belt AND braces, because the two mechanisms cover different cases and
|
|
188
|
+
neither covers both:
|
|
189
|
+
|
|
190
|
+
- The environment variables are read by OpenBLAS/MKL/OpenMP when they are
|
|
191
|
+
first loaded, so they work only if nothing numeric has been imported
|
|
192
|
+
yet. That is the sidecar's situation (`plexora.server_cli` imports this
|
|
193
|
+
package before anything else) but not the notebook's, where the user may
|
|
194
|
+
well have imported numpy in an earlier cell.
|
|
195
|
+
- `threadpool_limits` reaches into already-loaded libraries and is the only
|
|
196
|
+
thing that helps in the notebook case. It cannot help with blosc, which
|
|
197
|
+
is not a BLAS-like pool and has to be set through numcodecs.
|
|
198
|
+
|
|
199
|
+
Every step is individually guarded: a capped pool is an optimisation, and
|
|
200
|
+
failing to apply one must never stop the server starting.
|
|
201
|
+
"""
|
|
202
|
+
cpus = allocated_cpus() if cpus is None else max(1, int(cpus))
|
|
203
|
+
value = str(cpus)
|
|
204
|
+
|
|
205
|
+
# setdefault, not assignment: an operator who exported OMP_NUM_THREADS
|
|
206
|
+
# meant it, and silently overriding them would be the same class of bug
|
|
207
|
+
# this module exists to fix.
|
|
208
|
+
for name in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS",
|
|
209
|
+
"NUMEXPR_NUM_THREADS", "VECLIB_MAXIMUM_THREADS",
|
|
210
|
+
"BLIS_NUM_THREADS"):
|
|
211
|
+
os.environ.setdefault(name, value)
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
from threadpoolctl import threadpool_limits
|
|
215
|
+
|
|
216
|
+
# Called for its side effect and the result deliberately dropped:
|
|
217
|
+
# `threadpool_limits` restores the previous limits on __exit__ or an
|
|
218
|
+
# explicit restore, never on garbage collection, so a bare call sets
|
|
219
|
+
# the cap for the life of the process. This is also the fix for the
|
|
220
|
+
# call this replaced, which passed no limits at all and therefore
|
|
221
|
+
# capped nothing -- it only ever forced the BLAS libraries to load.
|
|
222
|
+
threadpool_limits(limits=cpus)
|
|
223
|
+
except Exception:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# zarr's decompression pool, and the one signal that is definitely
|
|
228
|
+
# wrong by default: numcodecs sizes it from the machine's core count,
|
|
229
|
+
# so a 2-core allocation still gets 8 decompression threads.
|
|
230
|
+
import numcodecs.blosc
|
|
231
|
+
|
|
232
|
+
numcodecs.blosc.set_nthreads(cpus)
|
|
233
|
+
except Exception:
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
return cpus
|
|
@@ -32,6 +32,12 @@ from plexora.api.dataset import (
|
|
|
32
32
|
from plexora.api.http import json_response
|
|
33
33
|
from plexora.api.store import PluginStore, store
|
|
34
34
|
from plexora.server.models.adapters.anndata_adapter import _deduplicate_names
|
|
35
|
+
from plexora.server.providers.base import (
|
|
36
|
+
ResourceLocator,
|
|
37
|
+
ResourceNotLocal,
|
|
38
|
+
ResourceUnavailable,
|
|
39
|
+
)
|
|
40
|
+
from plexora.server.providers.operations import table_operation, table_stream
|
|
35
41
|
|
|
36
42
|
#: Multiplexed panels routinely re-stain the same marker across cycles, so
|
|
37
43
|
#: duplicate names are ordinary rather than exceptional. Suffixes them the way
|
|
@@ -45,6 +51,9 @@ __all__ = [
|
|
|
45
51
|
"ImageSource",
|
|
46
52
|
"MetadataColumn",
|
|
47
53
|
"PluginStore",
|
|
54
|
+
"ResourceLocator",
|
|
55
|
+
"ResourceNotLocal",
|
|
56
|
+
"ResourceUnavailable",
|
|
48
57
|
"SegHandle",
|
|
49
58
|
"TableHandle",
|
|
50
59
|
"TableSource",
|
|
@@ -52,4 +61,6 @@ __all__ = [
|
|
|
52
61
|
"deduplicate_names",
|
|
53
62
|
"json_response",
|
|
54
63
|
"store",
|
|
64
|
+
"table_operation",
|
|
65
|
+
"table_stream",
|
|
55
66
|
]
|