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.
Files changed (420) hide show
  1. {plexora-0.0.2/plexora.egg-info → plexora-0.0.7}/PKG-INFO +43 -9
  2. {plexora-0.0.2 → plexora-0.0.7}/README.md +40 -8
  3. {plexora-0.0.2 → plexora-0.0.7}/plexora/__init__.py +83 -8
  4. plexora-0.0.7/plexora/_resources.py +236 -0
  5. {plexora-0.0.2 → plexora-0.0.7}/plexora/api/__init__.py +11 -0
  6. {plexora-0.0.2 → plexora-0.0.7}/plexora/api/dataset.py +255 -14
  7. {plexora-0.0.2 → plexora-0.0.7}/plexora/api/plugin.py +120 -1
  8. plexora-0.0.7/plexora/askpass.py +157 -0
  9. plexora-0.0.7/plexora/cli.py +1650 -0
  10. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/vendor_bundle.js +1 -1
  11. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/import.css +55 -104
  12. plexora-0.0.7/plexora/client/src/css/main.css +3576 -0
  13. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/openProject.css +15 -3
  14. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/quickView.css +46 -2
  15. plexora-0.0.7/plexora/client/src/css/settings.css +1030 -0
  16. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/tokens.css +13 -0
  17. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/css/viewer.css +134 -12
  18. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/main.js +153 -3
  19. plexora-0.0.7/plexora/client/src/js/services/appRouter.js +438 -0
  20. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/appStatus.js +31 -2
  21. plexora-0.0.7/plexora/client/src/js/services/browsePicker.js +126 -0
  22. plexora-0.0.7/plexora/client/src/js/services/connectionModal.js +785 -0
  23. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/dataLayer.js +7 -13
  24. plexora-0.0.7/plexora/client/src/js/services/dataLocation.js +764 -0
  25. plexora-0.0.7/plexora/client/src/js/services/fileLocation.js +704 -0
  26. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/importFormValidation.js +107 -2
  27. plexora-0.0.7/plexora/client/src/js/services/keyboardShortcuts.js +222 -0
  28. plexora-0.0.7/plexora/client/src/js/services/logTerminal.js +126 -0
  29. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/numericData.js +28 -0
  30. plexora-0.0.7/plexora/client/src/js/services/pageBoot.js +103 -0
  31. plexora-0.0.7/plexora/client/src/js/services/pathPicker.js +886 -0
  32. plexora-0.0.7/plexora/client/src/js/services/placePicker.js +244 -0
  33. plexora-0.0.7/plexora/client/src/js/services/remoteGlobe.js +633 -0
  34. plexora-0.0.7/plexora/client/src/js/services/remoteState.js +533 -0
  35. plexora-0.0.7/plexora/client/src/js/services/resourceRouting.js +206 -0
  36. plexora-0.0.7/plexora/client/src/js/services/resourceStatus.js +418 -0
  37. plexora-0.0.7/plexora/client/src/js/services/sessionExpiry.js +338 -0
  38. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/channelList.js +69 -27
  39. plexora-0.0.7/plexora/client/src/js/views/channelNamesUpload.js +906 -0
  40. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/colorSwatchPicker.js +11 -7
  41. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/dataSourceField.js +35 -5
  42. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/imageViewer.js +11 -7
  43. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/navbarControls.js +14 -32
  44. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/openProjectPage.js +1 -9
  45. plexora-0.0.7/plexora/client/src/js/views/popoverPortal.js +89 -0
  46. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/projectEdit.js +227 -20
  47. plexora-0.0.7/plexora/client/src/js/views/quickViewLanding.js +202 -0
  48. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/requirementsModal.js +26 -7
  49. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/searchableSelect.js +11 -8
  50. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/segmentationProgress.js +5 -0
  51. plexora-0.0.7/plexora/client/src/js/views/segmentationWait.js +296 -0
  52. plexora-0.0.7/plexora/client/src/js/views/settingsPage.js +1233 -0
  53. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/toolLoader.js +231 -2
  54. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/viewerControls.js +165 -27
  55. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/viewerManager.js +11 -2
  56. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/viewerSidebar.js +134 -17
  57. plexora-0.0.7/plexora/client/templates/_fragment.html +34 -0
  58. plexora-0.0.7/plexora/client/templates/base.html +379 -0
  59. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/index.html +69 -23
  60. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/open_project.html +96 -96
  61. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/project_columns.html +3 -3
  62. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/project_edit.html +220 -196
  63. plexora-0.0.7/plexora/client/templates/settings.html +545 -0
  64. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/templates/upload.html +47 -3
  65. plexora-0.0.7/plexora/connect.py +1890 -0
  66. {plexora-0.0.2 → plexora-0.0.7}/plexora/datasource.py +22 -0
  67. {plexora-0.0.2 → plexora-0.0.7}/plexora/jupyter.py +215 -1
  68. plexora-0.0.7/plexora/nodes.py +730 -0
  69. {plexora-0.0.2 → plexora-0.0.7}/plexora/notebook_env.py +366 -366
  70. {plexora-0.0.2 → plexora-0.0.7}/plexora/paths.py +517 -486
  71. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/__init__.py +105 -100
  72. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/values.py +4 -1
  73. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +92 -133
  74. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +13 -10
  75. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/__init__.py +38 -2
  76. plexora-0.0.7/plexora/plugins/figure_builder/server/compose.py +825 -0
  77. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/export.py +280 -20
  78. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/pixels.py +110 -6
  79. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/render.py +58 -7
  80. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/schema.py +377 -37
  81. plexora-0.0.7/plexora/plugins/figure_builder/server/shapegeom.py +189 -0
  82. plexora-0.0.7/plexora/plugins/figure_builder/server/strokegeom.py +337 -0
  83. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/textmetrics.py +2 -2
  84. plexora-0.0.7/plexora/plugins/figure_builder/static/figureActions.js +659 -0
  85. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureBuilderApi.js +15 -3
  86. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCanvas.js +1303 -174
  87. plexora-0.0.7/plexora/plugins/figure_builder/static/figureChoiceField.js +386 -0
  88. plexora-0.0.7/plexora/plugins/figure_builder/static/figureColorField.js +294 -0
  89. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureContextBar.js +500 -426
  90. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureContextMenu.js +19 -3
  91. plexora-0.0.7/plexora/plugins/figure_builder/static/figureImagePanel.js +1558 -0
  92. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureLibrary.js +13 -9
  93. plexora-0.0.7/plexora/plugins/figure_builder/static/figureLineDefs.js +142 -0
  94. plexora-0.0.7/plexora/plugins/figure_builder/static/figureLinePanel.js +445 -0
  95. plexora-0.0.7/plexora/plugins/figure_builder/static/figurePanelCompositor.js +157 -0
  96. plexora-0.0.7/plexora/plugins/figure_builder/static/figurePointEditor.js +575 -0
  97. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureQuickEdit.js +391 -99
  98. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureRichText.js +46 -1
  99. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureSceneSnapshot.js +12 -7
  100. plexora-0.0.7/plexora/plugins/figure_builder/static/figureSchema.js +466 -0
  101. plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapeDefs.js +235 -0
  102. plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapeDrawing.js +288 -0
  103. plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapeGeometry.js +546 -0
  104. plexora-0.0.7/plexora/plugins/figure_builder/static/figureShapePanel.js +404 -0
  105. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureSidebarController.js +52 -14
  106. plexora-0.0.7/plexora/plugins/figure_builder/static/figureStrokeGeometry.js +323 -0
  107. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureTextEditor.js +65 -1
  108. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureTextPanel.js +18 -3
  109. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureViewOptions.js +15 -9
  110. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureWorkspace.js +626 -192
  111. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figure_builder.css +2367 -336
  112. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/templates/figure_builder/library.html +10 -1
  113. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -27
  114. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +132 -41
  115. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/__init__.py +5 -1
  116. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/model.py +37 -39
  117. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/routes.py +29 -29
  118. plexora-0.0.7/plexora/plugins/gating/server/tableops.py +138 -0
  119. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/csvGatingList.js +7 -5
  120. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/gatingApi.js +67 -49
  121. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/__init__.py +100 -95
  122. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/mapping.py +5 -1
  123. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/routes.py +69 -62
  124. plexora-0.0.7/plexora/plugins/roi/server/tableops.py +138 -0
  125. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiApi.js +15 -0
  126. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiFigureBridge.js +8 -13
  127. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/inspection.py +31 -0
  128. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/centroid_tiles.py +32 -3
  129. plexora-0.0.7/plexora/server/models/data_migration.py +307 -0
  130. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/data_model.py +2379 -1670
  131. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/database_model.py +18 -0
  132. plexora-0.0.7/plexora/server/models/nodes.py +312 -0
  133. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/project.py +150 -1
  134. plexora-0.0.7/plexora/server/models/recipes.py +495 -0
  135. plexora-0.0.7/plexora/server/models/remote_sessions.py +707 -0
  136. plexora-0.0.7/plexora/server/models/remotes.py +267 -0
  137. plexora-0.0.7/plexora/server/models/secret_store.py +51 -0
  138. plexora-0.0.7/plexora/server/node/api.py +1138 -0
  139. plexora-0.0.7/plexora/server/node/app.py +508 -0
  140. plexora-0.0.7/plexora/server/node/resources.py +600 -0
  141. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/plugins.py +27 -0
  142. plexora-0.0.7/plexora/server/providers/__init__.py +185 -0
  143. plexora-0.0.7/plexora/server/providers/base.py +340 -0
  144. plexora-0.0.7/plexora/server/providers/http.py +453 -0
  145. plexora-0.0.7/plexora/server/providers/local.py +356 -0
  146. plexora-0.0.7/plexora/server/providers/node.py +542 -0
  147. plexora-0.0.7/plexora/server/providers/operations.py +139 -0
  148. plexora-0.0.7/plexora/server/providers/wire.py +131 -0
  149. plexora-0.0.7/plexora/server/routes/browse_routes.py +281 -0
  150. plexora-0.0.7/plexora/server/routes/data_routes.py +664 -0
  151. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/import_routes.py +393 -34
  152. plexora-0.0.7/plexora/server/routes/page_routes.py +280 -0
  153. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/project_routes.py +158 -5
  154. plexora-0.0.7/plexora/server/routes/quick_view_routes.py +127 -0
  155. plexora-0.0.7/plexora/server/routes/settings_routes.py +1059 -0
  156. plexora-0.0.7/plexora/server/routes/transfer_routes.py +198 -0
  157. plexora-0.0.7/plexora/server/utils/channel_file.py +272 -0
  158. plexora-0.0.7/plexora/server/utils/dir_listing.py +144 -0
  159. plexora-0.0.7/plexora/server/utils/file_transfer.py +192 -0
  160. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/native_dialog.py +33 -2
  161. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/segmentation_pyramid.py +131 -6
  162. {plexora-0.0.2 → plexora-0.0.7}/plexora/server_cli.py +6 -1
  163. {plexora-0.0.2 → plexora-0.0.7/plexora.egg-info}/PKG-INFO +43 -9
  164. {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/SOURCES.txt +99 -1
  165. {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/requires.txt +2 -0
  166. {plexora-0.0.2 → plexora-0.0.7}/pyproject.toml +15 -1
  167. plexora-0.0.7/tests/test_app_router.py +69 -0
  168. plexora-0.0.7/tests/test_app_shell.py +222 -0
  169. plexora-0.0.7/tests/test_browse_routes.py +430 -0
  170. {plexora-0.0.2 → plexora-0.0.7}/tests/test_cell_mode_control.py +50 -2
  171. plexora-0.0.7/tests/test_channel_names_modal.py +303 -0
  172. plexora-0.0.7/tests/test_channel_names_upload.py +362 -0
  173. plexora-0.0.7/tests/test_channel_rename_state.py +244 -0
  174. {plexora-0.0.2 → plexora-0.0.7}/tests/test_cli.py +951 -595
  175. plexora-0.0.7/tests/test_column_classifier_css.py +110 -0
  176. plexora-0.0.7/tests/test_connect.py +1335 -0
  177. plexora-0.0.7/tests/test_connection_modal.py +273 -0
  178. plexora-0.0.7/tests/test_data_location.py +416 -0
  179. plexora-0.0.7/tests/test_file_location.py +232 -0
  180. plexora-0.0.7/tests/test_file_transfer_node.py +267 -0
  181. {plexora-0.0.2 → plexora-0.0.7}/tests/test_hd_slider_domain.py +12 -3
  182. {plexora-0.0.2 → plexora-0.0.7}/tests/test_jupyter_viewer.py +14 -1
  183. plexora-0.0.7/tests/test_mask_without_table.py +78 -0
  184. plexora-0.0.7/tests/test_nav_shortcuts.py +45 -0
  185. plexora-0.0.7/tests/test_node_direct_routing.py +210 -0
  186. plexora-0.0.7/tests/test_node_disconnect.py +218 -0
  187. plexora-0.0.7/tests/test_node_dynamic.py +581 -0
  188. plexora-0.0.7/tests/test_node_image.py +712 -0
  189. plexora-0.0.7/tests/test_node_routes.py +552 -0
  190. plexora-0.0.7/tests/test_node_table.py +414 -0
  191. plexora-0.0.7/tests/test_node_warm_and_cache.py +338 -0
  192. {plexora-0.0.2 → plexora-0.0.7}/tests/test_notebook_mode.py +10 -1
  193. plexora-0.0.7/tests/test_path_picker.py +252 -0
  194. {plexora-0.0.2 → plexora-0.0.7}/tests/test_plugin_boundary.py +2 -1
  195. {plexora-0.0.2 → plexora-0.0.7}/tests/test_plugins.py +122 -2
  196. plexora-0.0.7/tests/test_popover_portal.py +141 -0
  197. {plexora-0.0.2 → plexora-0.0.7}/tests/test_quick_view_routes.py +73 -0
  198. plexora-0.0.7/tests/test_recipes.py +308 -0
  199. plexora-0.0.7/tests/test_reconnect.py +295 -0
  200. plexora-0.0.7/tests/test_remote_connect.py +874 -0
  201. plexora-0.0.7/tests/test_remote_globe.py +295 -0
  202. plexora-0.0.7/tests/test_remote_health.py +329 -0
  203. plexora-0.0.7/tests/test_remote_state.py +190 -0
  204. plexora-0.0.7/tests/test_resource_status_banner.py +94 -0
  205. {plexora-0.0.2 → plexora-0.0.7}/tests/test_segmentation_mapping.py +121 -4
  206. {plexora-0.0.2 → plexora-0.0.7}/tests/test_segmentation_pyramid.py +197 -0
  207. plexora-0.0.7/tests/test_segmentation_wait.py +154 -0
  208. plexora-0.0.7/tests/test_session_expiry.py +169 -0
  209. plexora-0.0.7/tests/test_session_walltime.py +372 -0
  210. plexora-0.0.7/tests/test_settings_page.py +357 -0
  211. plexora-0.0.7/tests/test_settings_remotes_page.py +321 -0
  212. plexora-0.0.7/tests/test_tile_url.py +42 -0
  213. {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_cards.py +52 -0
  214. plexora-0.0.7/tests/test_transfer_routes.py +179 -0
  215. plexora-0.0.7/tests/test_unreachable_node.py +393 -0
  216. plexora-0.0.7/tests/test_view_menu.py +155 -0
  217. plexora-0.0.2/plexora/cli.py +0 -876
  218. plexora-0.0.2/plexora/client/src/css/main.css +0 -903
  219. plexora-0.0.2/plexora/client/src/js/services/browsePicker.js +0 -73
  220. plexora-0.0.2/plexora/client/src/js/views/quickViewLanding.js +0 -136
  221. plexora-0.0.2/plexora/client/templates/base.html +0 -212
  222. plexora-0.0.2/plexora/connect.py +0 -528
  223. plexora-0.0.2/plexora/plugins/figure_builder/server/compose.py +0 -365
  224. plexora-0.0.2/plexora/plugins/figure_builder/static/figureActions.js +0 -363
  225. plexora-0.0.2/plexora/plugins/figure_builder/static/figureSchema.js +0 -218
  226. plexora-0.0.2/plexora/server/routes/browse_routes.py +0 -41
  227. plexora-0.0.2/plexora/server/routes/data_routes.py +0 -294
  228. plexora-0.0.2/plexora/server/routes/page_routes.py +0 -139
  229. plexora-0.0.2/plexora/server/routes/quick_view_routes.py +0 -62
  230. plexora-0.0.2/tests/test_browse_routes.py +0 -97
  231. plexora-0.0.2/tests/test_connect.py +0 -371
  232. {plexora-0.0.2 → plexora-0.0.7}/LICENSE +0 -0
  233. {plexora-0.0.2 → plexora-0.0.7}/MANIFEST.in +0 -0
  234. {plexora-0.0.2 → plexora-0.0.7}/plexora/__main__.py +0 -0
  235. {plexora-0.0.2 → plexora-0.0.7}/plexora/_url.py +0 -0
  236. {plexora-0.0.2 → plexora-0.0.7}/plexora/api/http.py +0 -0
  237. {plexora-0.0.2 → plexora-0.0.7}/plexora/api/store.py +0 -0
  238. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/354_bundle.js +0 -0
  239. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/354_bundle.js.LICENSE.txt +0 -0
  240. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/418_bundle.js +0 -0
  241. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/418_bundle.js.LICENSE.txt +0 -0
  242. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +0 -0
  243. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +0 -0
  244. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/770_bundle.js +0 -0
  245. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +0 -0
  246. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/dist/vendor_bundle.js.LICENSE.txt +0 -0
  247. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +0 -0
  248. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +0 -0
  249. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +0 -0
  250. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +0 -0
  251. {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
  252. {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
  253. {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
  254. {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
  255. {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
  256. {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
  257. {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
  258. {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
  259. {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
  260. {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
  261. {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
  262. {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
  263. {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
  264. {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
  265. {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
  266. {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
  267. {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
  268. {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
  269. {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
  270. {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
  271. {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
  272. {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
  273. {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
  274. {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
  275. {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
  276. {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
  277. {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
  278. {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
  279. {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
  280. {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
  281. {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
  282. {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
  283. {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
  284. {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
  285. {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
  286. {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
  287. {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
  288. {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
  289. {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
  290. {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
  291. {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
  292. {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
  293. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +0 -0
  294. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/apple-touch-icon.png +0 -0
  295. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/favicon.ico +0 -0
  296. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo.ai +0 -0
  297. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo.svg +0 -0
  298. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo_with_text.ai +0 -0
  299. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/img/logo_with_text.svg +0 -0
  300. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/pluginRegistry.js +0 -0
  301. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/datasetContext.js +0 -0
  302. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/glRenderer.js +0 -0
  303. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/passVariablesToFrontend.js +0 -0
  304. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/services/simpleEventHandler.js +0 -0
  305. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/vendor.js +0 -0
  306. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/columnClassifier.js +0 -0
  307. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/coordinateField.js +0 -0
  308. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/miniMap.js +0 -0
  309. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/rainbow.js +0 -0
  310. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/views/rgbImageViewer.js +0 -0
  311. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/js/workers/tileDecoder.js +0 -0
  312. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/shaders/frag.glsl +0 -0
  313. {plexora-0.0.2 → plexora-0.0.7}/plexora/client/src/shaders/vert.glsl +0 -0
  314. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/__init__.py +0 -0
  315. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/__init__.py +0 -0
  316. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/routes.py +0 -0
  317. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/state.py +0 -0
  318. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/server/variables.py +0 -0
  319. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerApi.js +0 -0
  320. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerColors.js +0 -0
  321. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +0 -0
  322. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerLegend.js +0 -0
  323. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +0 -0
  324. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cellExplorerState.js +0 -0
  325. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/static/cell_explorer.css +0 -0
  326. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +0 -0
  327. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/__init__.py +0 -0
  328. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/export_jobs.py +0 -0
  329. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/operations.py +0 -0
  330. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/provenance.py +0 -0
  331. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/repository.py +0 -0
  332. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/routes.py +0 -0
  333. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/server/sources.py +0 -0
  334. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCaptureBoxes.js +0 -0
  335. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCaptureDock.js +0 -0
  336. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureCaptureTool.js +0 -0
  337. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureConfirm.js +0 -0
  338. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureDocumentState.js +0 -0
  339. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/figure_builder/static/figureExportUi.js +0 -0
  340. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/__init__.py +0 -0
  341. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/anndata_gates.py +0 -0
  342. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/server/database.py +0 -0
  343. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/gating.css +0 -0
  344. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/static/gatingSidebarController.js +0 -0
  345. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/templates/gating/legacy.html +0 -0
  346. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/gating/templates/gating/panel.html +0 -0
  347. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/__init__.py +0 -0
  348. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/adapters.py +0 -0
  349. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/geojson.py +0 -0
  350. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/geometry.py +0 -0
  351. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/operations.py +0 -0
  352. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/repository.py +0 -0
  353. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/server/schema.py +0 -0
  354. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roi.css +0 -0
  355. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiGeometry.js +0 -0
  356. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiRenderer.js +0 -0
  357. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiSidebarController.js +0 -0
  358. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiState.js +0 -0
  359. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/static/roiTools.js +0 -0
  360. {plexora-0.0.2 → plexora-0.0.7}/plexora/plugins/roi/templates/roi/panel.html +0 -0
  361. {plexora-0.0.2 → plexora-0.0.7}/plexora/proxy.py +0 -0
  362. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/__init__.py +0 -0
  363. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/anndata_adapter.py +0 -0
  364. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/base.py +0 -0
  365. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/classify.py +0 -0
  366. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/csv_adapter.py +0 -0
  367. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/models/adapters/spatialdata_adapter.py +0 -0
  368. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/system_routes.py +0 -0
  369. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/routes/tool_routes.py +0 -0
  370. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/addHEColumns.py +0 -0
  371. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/fast_png.py +0 -0
  372. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/fullConversion.py +0 -0
  373. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/pre_normalization.py +0 -0
  374. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/smallestenclosingcircle.py +0 -0
  375. {plexora-0.0.2 → plexora-0.0.7}/plexora/server/utils/tiffsurgeon.py +0 -0
  376. {plexora-0.0.2 → plexora-0.0.7}/plexora/test.py +0 -0
  377. {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/dependency_links.txt +0 -0
  378. {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/entry_points.txt +0 -0
  379. {plexora-0.0.2 → plexora-0.0.7}/plexora.egg-info/top_level.txt +0 -0
  380. {plexora-0.0.2 → plexora-0.0.7}/setup.cfg +0 -0
  381. {plexora-0.0.2 → plexora-0.0.7}/tests/test_anndata_adapter.py +0 -0
  382. {plexora-0.0.2 → plexora-0.0.7}/tests/test_api_dataset.py +0 -0
  383. {plexora-0.0.2 → plexora-0.0.7}/tests/test_api_store.py +0 -0
  384. {plexora-0.0.2 → plexora-0.0.7}/tests/test_auth_token.py +0 -0
  385. {plexora-0.0.2 → plexora-0.0.7}/tests/test_cell_color_layer.py +0 -0
  386. {plexora-0.0.2 → plexora-0.0.7}/tests/test_centroid_tiles.py +0 -0
  387. {plexora-0.0.2 → plexora-0.0.7}/tests/test_channel_overview.py +0 -0
  388. {plexora-0.0.2 → plexora-0.0.7}/tests/test_classify_columns.py +0 -0
  389. {plexora-0.0.2 → plexora-0.0.7}/tests/test_config_store.py +0 -0
  390. {plexora-0.0.2 → plexora-0.0.7}/tests/test_csv_adapter.py +0 -0
  391. {plexora-0.0.2 → plexora-0.0.7}/tests/test_database_model.py +0 -0
  392. {plexora-0.0.2 → plexora-0.0.7}/tests/test_datalayer_requests.py +0 -0
  393. {plexora-0.0.2 → plexora-0.0.7}/tests/test_derive_anndata_channel_names.py +0 -0
  394. {plexora-0.0.2 → plexora-0.0.7}/tests/test_detect_data_type.py +0 -0
  395. {plexora-0.0.2 → plexora-0.0.7}/tests/test_icon_names.py +0 -0
  396. {plexora-0.0.2 → plexora-0.0.7}/tests/test_label_outlines.py +0 -0
  397. {plexora-0.0.2 → plexora-0.0.7}/tests/test_label_tile_lifecycle.py +0 -0
  398. {plexora-0.0.2 → plexora-0.0.7}/tests/test_marker_split.py +0 -0
  399. {plexora-0.0.2 → plexora-0.0.7}/tests/test_metadata_columns.py +0 -0
  400. {plexora-0.0.2 → plexora-0.0.7}/tests/test_mini_map.py +0 -0
  401. {plexora-0.0.2 → plexora-0.0.7}/tests/test_notebook_env.py +0 -0
  402. {plexora-0.0.2 → plexora-0.0.7}/tests/test_optional_segmentation.py +0 -0
  403. {plexora-0.0.2 → plexora-0.0.7}/tests/test_page_assets.py +0 -0
  404. {plexora-0.0.2 → plexora-0.0.7}/tests/test_page_routes.py +0 -0
  405. {plexora-0.0.2 → plexora-0.0.7}/tests/test_paths.py +0 -0
  406. {plexora-0.0.2 → plexora-0.0.7}/tests/test_plugin_css_boundary.py +0 -0
  407. {plexora-0.0.2 → plexora-0.0.7}/tests/test_project_edit_routes.py +0 -0
  408. {plexora-0.0.2 → plexora-0.0.7}/tests/test_project_record.py +0 -0
  409. {plexora-0.0.2 → plexora-0.0.7}/tests/test_refresh_dataset.py +0 -0
  410. {plexora-0.0.2 → plexora-0.0.7}/tests/test_register_anndata_datasource.py +0 -0
  411. {plexora-0.0.2 → plexora-0.0.7}/tests/test_register_image_datasource.py +0 -0
  412. {plexora-0.0.2 → plexora-0.0.7}/tests/test_rename_channels.py +0 -0
  413. {plexora-0.0.2 → plexora-0.0.7}/tests/test_requirements_routes.py +0 -0
  414. {plexora-0.0.2 → plexora-0.0.7}/tests/test_segmentation_mode_routes.py +0 -0
  415. {plexora-0.0.2 → plexora-0.0.7}/tests/test_shared_roots.py +0 -0
  416. {plexora-0.0.2 → plexora-0.0.7}/tests/test_spatialdata_adapter.py +0 -0
  417. {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_assets.py +0 -0
  418. {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_coexistence.py +0 -0
  419. {plexora-0.0.2 → plexora-0.0.7}/tests/test_tool_switching.py +0 -0
  420. {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.2
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 | `plexora connect user@host` — or `plexora --remote` on the host |
76
- | A hosted notebook (JupyterHub, Open OnDemand, Colab) | `plexora.view("my_dataset")` — the proxy is detected for you |
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
- **[DEPLOYMENT.md](DEPLOYMENT.md) walks through all of them in detail**, from a
79
- fresh conda environment to HPC job submission, with the real output of each
80
- command. The rest of this README is the short version.
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 "conda run -n imaging plexora"
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 16G"
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 16G bash`), then run
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 | `plexora connect user@host` — or `plexora --remote` on the host |
35
- | A hosted notebook (JupyterHub, Open OnDemand, Colab) | `plexora.view("my_dataset")` — the proxy is detected for you |
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
- **[DEPLOYMENT.md](DEPLOYMENT.md) walks through all of them in detail**, from a
38
- fresh conda environment to HPC job submission, with the real output of each
39
- command. The rest of this README is the short version.
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 "conda run -n imaging plexora"
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 16G"
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 16G bash`), then run
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
- # Initialize sklearn global threadpool controller to avoid deadlock in threaded
12
- # contexts.
13
- from threadpoolctl import threadpool_limits
14
-
15
- threadpool_limits()
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
  ]