plexora 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plexora/__init__.py +129 -0
- plexora/__main__.py +15 -0
- plexora/_url.py +105 -0
- plexora/api/__init__.py +55 -0
- plexora/api/dataset.py +398 -0
- plexora/api/http.py +19 -0
- plexora/api/plugin.py +541 -0
- plexora/api/store.py +221 -0
- plexora/cli.py +777 -0
- plexora/client/dist/354_bundle.js +2 -0
- plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
- plexora/client/dist/418_bundle.js +2 -0
- plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
- plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
- plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
- plexora/client/dist/770_bundle.js +1 -0
- plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
- plexora/client/dist/vendor_bundle.js +2 -0
- plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
- plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
- plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
- plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
- plexora/client/src/css/import.css +474 -0
- plexora/client/src/css/main.css +903 -0
- plexora/client/src/css/openProject.css +363 -0
- plexora/client/src/css/quickView.css +166 -0
- plexora/client/src/css/tokens.css +51 -0
- plexora/client/src/css/viewer.css +1655 -0
- plexora/client/src/img/apple-touch-icon.png +0 -0
- plexora/client/src/img/favicon.ico +0 -0
- plexora/client/src/img/logo.ai +8892 -33
- plexora/client/src/img/logo.svg +1 -0
- plexora/client/src/img/logo_with_text.ai +9011 -34
- plexora/client/src/img/logo_with_text.svg +1 -0
- plexora/client/src/js/main.js +704 -0
- plexora/client/src/js/pluginRegistry.js +108 -0
- plexora/client/src/js/services/appStatus.js +286 -0
- plexora/client/src/js/services/browsePicker.js +73 -0
- plexora/client/src/js/services/dataLayer.js +400 -0
- plexora/client/src/js/services/datasetContext.js +143 -0
- plexora/client/src/js/services/glRenderer.js +143 -0
- plexora/client/src/js/services/importFormValidation.js +257 -0
- plexora/client/src/js/services/numericData.js +100 -0
- plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
- plexora/client/src/js/services/simpleEventHandler.js +25 -0
- plexora/client/src/js/vendor.js +29 -0
- plexora/client/src/js/views/channelList.js +663 -0
- plexora/client/src/js/views/colorSwatchPicker.js +202 -0
- plexora/client/src/js/views/columnClassifier.js +146 -0
- plexora/client/src/js/views/coordinateField.js +133 -0
- plexora/client/src/js/views/dataSourceField.js +276 -0
- plexora/client/src/js/views/imageViewer.js +3306 -0
- plexora/client/src/js/views/miniMap.js +787 -0
- plexora/client/src/js/views/navbarControls.js +178 -0
- plexora/client/src/js/views/openProjectPage.js +239 -0
- plexora/client/src/js/views/projectEdit.js +335 -0
- plexora/client/src/js/views/quickViewLanding.js +136 -0
- plexora/client/src/js/views/rainbow.js +326 -0
- plexora/client/src/js/views/requirementsModal.js +572 -0
- plexora/client/src/js/views/rgbImageViewer.js +115 -0
- plexora/client/src/js/views/searchableSelect.js +411 -0
- plexora/client/src/js/views/segmentationProgress.js +122 -0
- plexora/client/src/js/views/toolLoader.js +747 -0
- plexora/client/src/js/views/viewerControls.js +689 -0
- plexora/client/src/js/views/viewerManager.js +434 -0
- plexora/client/src/js/views/viewerSidebar.js +1117 -0
- plexora/client/src/js/workers/tileDecoder.js +130 -0
- plexora/client/src/shaders/frag.glsl +528 -0
- plexora/client/src/shaders/vert.glsl +12 -0
- plexora/client/templates/base.html +212 -0
- plexora/client/templates/index.html +238 -0
- plexora/client/templates/open_project.html +96 -0
- plexora/client/templates/project_columns.html +86 -0
- plexora/client/templates/project_edit.html +196 -0
- plexora/client/templates/upload.html +118 -0
- plexora/connect.py +528 -0
- plexora/datasource.py +782 -0
- plexora/jupyter.py +406 -0
- plexora/notebook_env.py +232 -0
- plexora/paths.py +486 -0
- plexora/plugins/__init__.py +12 -0
- plexora/plugins/cell_explorer/__init__.py +100 -0
- plexora/plugins/cell_explorer/server/__init__.py +11 -0
- plexora/plugins/cell_explorer/server/routes.py +167 -0
- plexora/plugins/cell_explorer/server/state.py +281 -0
- plexora/plugins/cell_explorer/server/values.py +145 -0
- plexora/plugins/cell_explorer/server/variables.py +367 -0
- plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
- plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
- plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
- plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
- plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
- plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
- plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
- plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
- plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
- plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
- plexora/plugins/figure_builder/__init__.py +125 -0
- plexora/plugins/figure_builder/server/__init__.py +7 -0
- plexora/plugins/figure_builder/server/compose.py +365 -0
- plexora/plugins/figure_builder/server/export.py +784 -0
- plexora/plugins/figure_builder/server/export_jobs.py +148 -0
- plexora/plugins/figure_builder/server/operations.py +556 -0
- plexora/plugins/figure_builder/server/pixels.py +142 -0
- plexora/plugins/figure_builder/server/provenance.py +174 -0
- plexora/plugins/figure_builder/server/render.py +278 -0
- plexora/plugins/figure_builder/server/repository.py +701 -0
- plexora/plugins/figure_builder/server/routes.py +594 -0
- plexora/plugins/figure_builder/server/schema.py +1023 -0
- plexora/plugins/figure_builder/server/sources.py +120 -0
- plexora/plugins/figure_builder/server/textmetrics.py +155 -0
- plexora/plugins/figure_builder/static/figureActions.js +363 -0
- plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
- plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
- plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
- plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
- plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
- plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
- plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
- plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
- plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
- plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
- plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
- plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
- plexora/plugins/figure_builder/static/figureRichText.js +634 -0
- plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
- plexora/plugins/figure_builder/static/figureSchema.js +218 -0
- plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
- plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
- plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
- plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
- plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
- plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
- plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
- plexora/plugins/gating/__init__.py +76 -0
- plexora/plugins/gating/server/__init__.py +0 -0
- plexora/plugins/gating/server/anndata_gates.py +367 -0
- plexora/plugins/gating/server/database.py +16 -0
- plexora/plugins/gating/server/model.py +320 -0
- plexora/plugins/gating/server/routes.py +235 -0
- plexora/plugins/gating/static/csvGatingList.js +947 -0
- plexora/plugins/gating/static/gating.css +289 -0
- plexora/plugins/gating/static/gatingApi.js +221 -0
- plexora/plugins/gating/static/gatingSidebarController.js +513 -0
- plexora/plugins/gating/templates/gating/legacy.html +26 -0
- plexora/plugins/gating/templates/gating/panel.html +69 -0
- plexora/plugins/roi/__init__.py +95 -0
- plexora/plugins/roi/server/__init__.py +8 -0
- plexora/plugins/roi/server/adapters.py +655 -0
- plexora/plugins/roi/server/geojson.py +267 -0
- plexora/plugins/roi/server/geometry.py +181 -0
- plexora/plugins/roi/server/mapping.py +169 -0
- plexora/plugins/roi/server/operations.py +293 -0
- plexora/plugins/roi/server/repository.py +215 -0
- plexora/plugins/roi/server/routes.py +409 -0
- plexora/plugins/roi/server/schema.py +323 -0
- plexora/plugins/roi/static/roi.css +583 -0
- plexora/plugins/roi/static/roiApi.js +177 -0
- plexora/plugins/roi/static/roiFigureBridge.js +107 -0
- plexora/plugins/roi/static/roiGeometry.js +350 -0
- plexora/plugins/roi/static/roiRenderer.js +278 -0
- plexora/plugins/roi/static/roiSidebarController.js +1038 -0
- plexora/plugins/roi/static/roiState.js +518 -0
- plexora/plugins/roi/static/roiTools.js +993 -0
- plexora/plugins/roi/templates/roi/panel.html +240 -0
- plexora/proxy.py +57 -0
- plexora/server/models/adapters/__init__.py +94 -0
- plexora/server/models/adapters/anndata_adapter.py +388 -0
- plexora/server/models/adapters/base.py +93 -0
- plexora/server/models/adapters/classify.py +192 -0
- plexora/server/models/adapters/csv_adapter.py +105 -0
- plexora/server/models/adapters/inspection.py +265 -0
- plexora/server/models/adapters/spatialdata_adapter.py +248 -0
- plexora/server/models/centroid_tiles.py +305 -0
- plexora/server/models/data_model.py +1659 -0
- plexora/server/models/database_model.py +169 -0
- plexora/server/models/project.py +1270 -0
- plexora/server/plugins.py +199 -0
- plexora/server/routes/browse_routes.py +41 -0
- plexora/server/routes/data_routes.py +294 -0
- plexora/server/routes/import_routes.py +603 -0
- plexora/server/routes/page_routes.py +139 -0
- plexora/server/routes/project_routes.py +438 -0
- plexora/server/routes/quick_view_routes.py +62 -0
- plexora/server/routes/system_routes.py +39 -0
- plexora/server/routes/tool_routes.py +418 -0
- plexora/server/utils/addHEColumns.py +11 -0
- plexora/server/utils/fast_png.py +97 -0
- plexora/server/utils/fullConversion.py +50 -0
- plexora/server/utils/native_dialog.py +168 -0
- plexora/server/utils/pre_normalization.py +47 -0
- plexora/server/utils/segmentation_pyramid.py +598 -0
- plexora/server/utils/smallestenclosingcircle.py +126 -0
- plexora/server/utils/tiffsurgeon.py +372 -0
- plexora/server_cli.py +48 -0
- plexora/test.py +0 -0
- plexora-0.0.1.dist-info/METADATA +341 -0
- plexora-0.0.1.dist-info/RECORD +248 -0
- plexora-0.0.1.dist-info/WHEEL +5 -0
- plexora-0.0.1.dist-info/entry_points.txt +12 -0
- plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
- plexora-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* importFormValidation.js -- the upload page's one form.
|
|
3
|
+
*
|
|
4
|
+
* The page asks for a name, an image, an optional mask and an optional data
|
|
5
|
+
* file. It does not ask what format the data is in: /inspect_data answers that
|
|
6
|
+
* from the file, and the two controls that cannot be answered from the file --
|
|
7
|
+
* which table inside a multi-table .zarr store, and which image inside a table
|
|
8
|
+
* that spans several -- are revealed only when the file forces the choice.
|
|
9
|
+
*
|
|
10
|
+
* Submission is a native POST navigation rather than fetch(). That is
|
|
11
|
+
* deliberate: this page's scripts share a realm with base.html's stack, and an
|
|
12
|
+
* AJAX submit here previously had to reconstruct a whole page's worth of state
|
|
13
|
+
* from the response. Letting the browser navigate keeps the server free to
|
|
14
|
+
* redirect wherever import decided the user should go next -- the viewer, or
|
|
15
|
+
* the column-classification screen.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
(function () {
|
|
19
|
+
'use strict'
|
|
20
|
+
|
|
21
|
+
// Bootstrap validation styling; the browser handles the actual submit.
|
|
22
|
+
Array.prototype.slice.call(document.querySelectorAll('.needs-validation'))
|
|
23
|
+
.forEach(function (form) {
|
|
24
|
+
form.addEventListener('submit', function (event) {
|
|
25
|
+
if (!form.checkValidity()) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
event.stopPropagation();
|
|
28
|
+
} else {
|
|
29
|
+
setSubmitting(form, true);
|
|
30
|
+
}
|
|
31
|
+
form.classList.add('was-validated');
|
|
32
|
+
}, true);
|
|
33
|
+
});
|
|
34
|
+
})();
|
|
35
|
+
|
|
36
|
+
function setSubmitting(form, busy) {
|
|
37
|
+
const button = form.querySelector('button[type="submit"]');
|
|
38
|
+
if (!button) return;
|
|
39
|
+
button.disabled = busy;
|
|
40
|
+
// Reading a large .h5ad to work out its structure takes a moment, and the
|
|
41
|
+
// page navigates rather than streaming progress -- so say something.
|
|
42
|
+
button.textContent = busy ? 'Importing…' : 'Create Project';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// --------------------------------------------------------------------------
|
|
46
|
+
// Project name, suggested from the image path
|
|
47
|
+
// --------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
let datasetNameManuallyEdited = false;
|
|
50
|
+
|
|
51
|
+
function markDatasetNameEdited() {
|
|
52
|
+
datasetNameManuallyEdited = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function deriveDatasetName(path) {
|
|
56
|
+
if (!path) return "";
|
|
57
|
+
const base = path.split(/[\\/]/).pop() || "";
|
|
58
|
+
return base.replace(/\.(ome\.tiff|ome\.tif|ome\.zarr|tiff|tif|svs|zarr|png|qptiff)$/i, "");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function suggestDatasetName(caller, targetFieldId) {
|
|
62
|
+
if (datasetNameManuallyEdited) return;
|
|
63
|
+
const nameField = document.getElementById(targetFieldId || "name");
|
|
64
|
+
if (!nameField) return;
|
|
65
|
+
const suggested = deriveDatasetName(caller && caller.value);
|
|
66
|
+
if (suggested) nameField.value = suggested;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
70
|
+
// Wire every "Browse..." button (see browsePicker.js) to fill its paired
|
|
71
|
+
// text field via the native OS file/folder dialog.
|
|
72
|
+
document.querySelectorAll('[data-browse-target]').forEach(function (button) {
|
|
73
|
+
const input = document.getElementById(button.dataset.browseTarget);
|
|
74
|
+
attachBrowseButton(button, input, {
|
|
75
|
+
mode: button.dataset.browseMode || 'file',
|
|
76
|
+
filter: button.dataset.browseFilter || 'any',
|
|
77
|
+
});
|
|
78
|
+
// A path arriving from the dialog has to go through the same
|
|
79
|
+
// inspection a typed one does, or the table/subset pickers never
|
|
80
|
+
// appear for anyone who used Browse.
|
|
81
|
+
if (input && input.id === 'data_file') {
|
|
82
|
+
button.addEventListener('click', () => setTimeout(() => inspectDataFile(input), 0));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const dataField = document.getElementById('data_file');
|
|
87
|
+
if (dataField && dataField.value) inspectDataFile(dataField);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// --------------------------------------------------------------------------
|
|
91
|
+
// Path checks
|
|
92
|
+
// --------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
function markValidity(input, valid) {
|
|
95
|
+
input.classList.remove('is-valid', 'is-invalid');
|
|
96
|
+
if (valid !== null) input.classList.add(valid ? 'is-valid' : 'is-invalid');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function checkFileExistence(caller) {
|
|
100
|
+
if (!caller.value) return markValidity(caller, null);
|
|
101
|
+
const response = await fetch(plexoraUrl('check_file_existence'), {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: { 'Content-Type': 'application/json' },
|
|
104
|
+
body: JSON.stringify({ path: caller.value }),
|
|
105
|
+
});
|
|
106
|
+
const { exists } = await response.json();
|
|
107
|
+
markValidity(caller, exists);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Blank is fine for an optional field -- an empty box is not an error. */
|
|
111
|
+
async function checkOptionalFileExistence(caller) {
|
|
112
|
+
if (!caller.value) return markValidity(caller, null);
|
|
113
|
+
return checkFileExistence(caller);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function checkDatasetExistence(caller) {
|
|
117
|
+
const response = await fetch(plexoraUrl('dataset_existence'), {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify({ datasetName: caller.value }),
|
|
121
|
+
});
|
|
122
|
+
const { exists } = await response.json();
|
|
123
|
+
markValidity(caller, !exists);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --------------------------------------------------------------------------
|
|
127
|
+
// Inspecting the data file
|
|
128
|
+
// --------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
const DATA_TYPE_LABELS = {
|
|
131
|
+
csv: 'CSV table',
|
|
132
|
+
anndata: 'AnnData',
|
|
133
|
+
spatialdata: 'SpatialData store',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Replies can arrive out of order when someone types quickly, and a stale one
|
|
137
|
+
// would re-show a picker for a file that is no longer in the box.
|
|
138
|
+
let inspectToken = 0;
|
|
139
|
+
let inspectTimer = null;
|
|
140
|
+
|
|
141
|
+
function inspectDataFile(caller) {
|
|
142
|
+
clearTimeout(inspectTimer);
|
|
143
|
+
// Every inspection opens the file, and an .h5ad is not cheap to open --
|
|
144
|
+
// so wait for a pause in typing rather than firing per keystroke.
|
|
145
|
+
inspectTimer = setTimeout(() => runInspection(caller), 250);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param caller the data path input
|
|
150
|
+
* @param table which table inside a .zarr store to look at, on the second
|
|
151
|
+
* pass. Everything below the table picker is a question about a table, so a
|
|
152
|
+
* multi-table store cannot answer any of it until one is named -- and the
|
|
153
|
+
* picker's own selection has to survive, which is why the table field is
|
|
154
|
+
* left alone when this is set.
|
|
155
|
+
*/
|
|
156
|
+
async function runInspection(caller, table) {
|
|
157
|
+
const token = ++inspectToken;
|
|
158
|
+
const hint = document.getElementById('data_file_hint');
|
|
159
|
+
const error = document.getElementById('data_file_error');
|
|
160
|
+
if (!table) hideField('data_table_field');
|
|
161
|
+
hideField('subset_field');
|
|
162
|
+
|
|
163
|
+
if (!caller.value) {
|
|
164
|
+
markValidity(caller, null);
|
|
165
|
+
caller.setCustomValidity('');
|
|
166
|
+
hint.textContent = 'CSV, AnnData (.h5ad) or SpatialData (.zarr).';
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let payload;
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch(plexoraUrl('inspect_data'), {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
body: JSON.stringify({ path: caller.value, table: table || null }),
|
|
176
|
+
});
|
|
177
|
+
payload = await response.json();
|
|
178
|
+
} catch (e) {
|
|
179
|
+
return; // transport failures are reported by PlexoraStatus
|
|
180
|
+
}
|
|
181
|
+
if (token !== inspectToken) return;
|
|
182
|
+
|
|
183
|
+
if (!payload.ok) {
|
|
184
|
+
markValidity(caller, false);
|
|
185
|
+
// setCustomValidity is what stops the form submitting -- the red
|
|
186
|
+
// border alone would let an unreadable file through to the server.
|
|
187
|
+
caller.setCustomValidity(payload.error || 'Unreadable');
|
|
188
|
+
error.textContent = payload.error || '';
|
|
189
|
+
hint.textContent = '';
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
markValidity(caller, true);
|
|
194
|
+
caller.setCustomValidity('');
|
|
195
|
+
error.textContent = '';
|
|
196
|
+
hint.textContent = DATA_TYPE_LABELS[payload.data_type] || payload.data_type;
|
|
197
|
+
|
|
198
|
+
if (!table && (payload.tables || []).length > 1) {
|
|
199
|
+
// Choosing re-runs this with the table, which is what puts the subset
|
|
200
|
+
// question. Returning here used to be the end of it, so a multi-table
|
|
201
|
+
// store could import loading every image at once.
|
|
202
|
+
//
|
|
203
|
+
// The re-run also matters for what this form does NOT ask: the chosen
|
|
204
|
+
// table's obs columns and layer names are recorded from this same
|
|
205
|
+
// inspection, and the requirements modal asks about them later.
|
|
206
|
+
showTablePicker(payload.tables, caller);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if ((payload.ambiguous || []).length) {
|
|
210
|
+
showSubsetPicker(payload.ambiguous[0]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function hideField(id) {
|
|
215
|
+
const field = document.getElementById(id);
|
|
216
|
+
if (!field) return;
|
|
217
|
+
field.hidden = true;
|
|
218
|
+
// A hidden required control blocks submission with no visible cause.
|
|
219
|
+
field.querySelectorAll('select, input').forEach((el) => {
|
|
220
|
+
el.required = false;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function showTablePicker(tables, dataInput) {
|
|
225
|
+
const field = document.getElementById('data_table_field');
|
|
226
|
+
const select = document.getElementById('data_table');
|
|
227
|
+
select.innerHTML = '';
|
|
228
|
+
select.append(new Option('Choose a table…', ''));
|
|
229
|
+
tables.forEach((table) => {
|
|
230
|
+
select.append(new Option(
|
|
231
|
+
`${table.name} — ${table.n_obs} cells × ${table.n_var} markers`,
|
|
232
|
+
table.name,
|
|
233
|
+
));
|
|
234
|
+
});
|
|
235
|
+
// Assigned rather than added: this runs again on every re-inspection, and
|
|
236
|
+
// addEventListener would stack a fresh handler each time.
|
|
237
|
+
select.onchange = () => {
|
|
238
|
+
hideField('subset_field');
|
|
239
|
+
if (select.value) runInspection(dataInput, select.value);
|
|
240
|
+
};
|
|
241
|
+
select.required = true;
|
|
242
|
+
field.hidden = false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function showSubsetPicker(ambiguous) {
|
|
246
|
+
const field = document.getElementById('subset_field');
|
|
247
|
+
const select = document.getElementById('subset_value');
|
|
248
|
+
document.getElementById('subset_column').value = ambiguous.column;
|
|
249
|
+
document.getElementById('subset_hint').textContent =
|
|
250
|
+
`This table covers several images (column "${ambiguous.column}"). ` +
|
|
251
|
+
'Choose the one this image shows.';
|
|
252
|
+
select.innerHTML = '';
|
|
253
|
+
select.append(new Option('Choose an image…', ''));
|
|
254
|
+
(ambiguous.values || []).forEach((value) => select.append(new Option(value, value)));
|
|
255
|
+
select.required = true;
|
|
256
|
+
field.hidden = false;
|
|
257
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
class NumericData {
|
|
2
|
+
/**
|
|
3
|
+
* Constructor for NumericDataLayer.
|
|
4
|
+
*
|
|
5
|
+
* @param config - the cinfiguration file (json)
|
|
6
|
+
* @param dataLayer - the data layer (stub) that executes server requests and holds client side data
|
|
7
|
+
*/
|
|
8
|
+
constructor(config, dataLayer) {
|
|
9
|
+
// Roles, not literal column names -- the same map plugins read.
|
|
10
|
+
this.schema = PlexoraDataset.resolveSchema(config);
|
|
11
|
+
this.dataLayer = dataLayer;
|
|
12
|
+
this.cellsPromise = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Load cell segmentation data
|
|
17
|
+
*/
|
|
18
|
+
async loadCells() {
|
|
19
|
+
if (this.cellsPromise) {
|
|
20
|
+
return this.cellsPromise;
|
|
21
|
+
}
|
|
22
|
+
this.cellsPromise = this.fetchCells();
|
|
23
|
+
return this.cellsPromise;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async fetchCells() {
|
|
27
|
+
const { cellId, x, y } = this.schema;
|
|
28
|
+
const fields = [ cellId, x, y ];
|
|
29
|
+
const idsCenters = await this.getAllUInt32Entries(fields);
|
|
30
|
+
// Deinterleave in one linear pass instead of two full-array .filter()
|
|
31
|
+
// passes (was O(2 * cellCount * fields.length)).
|
|
32
|
+
const count = idsCenters.length / fields.length;
|
|
33
|
+
const ids = new Uint32Array(count);
|
|
34
|
+
const centers = new Uint32Array(count * (fields.length - 1));
|
|
35
|
+
for (let i = 0, o = 0, c = 0; i < idsCenters.length; i += fields.length, o++) {
|
|
36
|
+
ids[o] = idsCenters[i];
|
|
37
|
+
for (let k = 1; k < fields.length; k++) {
|
|
38
|
+
centers[c++] = idsCenters[i + k];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ids, centers };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Access DataLayer bitrange as floating point.
|
|
46
|
+
*/
|
|
47
|
+
get floatRange() {
|
|
48
|
+
return this.dataLayer.getImageBitRange(true);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* Access DataLayer bitrange as an integer.
|
|
53
|
+
*/
|
|
54
|
+
get intRange() {
|
|
55
|
+
return this.dataLayer.getImageBitRange(false);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* @function getNearestCell - return nearest cell to point
|
|
60
|
+
*
|
|
61
|
+
* @param x - cell x position in image coordinates
|
|
62
|
+
* @param y - cell y position in image coordinates
|
|
63
|
+
*/
|
|
64
|
+
getNearestCell(x, y) {
|
|
65
|
+
return this.dataLayer.getNearestCell(x, y);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* @function getAllFloat32Ids - all integer entries
|
|
70
|
+
* @param keys - list of keys to access
|
|
71
|
+
*/
|
|
72
|
+
async getAllFloat32Entries(keys) {
|
|
73
|
+
return this.getAllEntries(keys, false);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* @function getAllUInt32Ids - all integer entries
|
|
78
|
+
* @param keys - list of keys to access
|
|
79
|
+
*/
|
|
80
|
+
async getAllUInt32Entries(keys) {
|
|
81
|
+
return this.getAllEntries(keys, true);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* @function getAllEntries - all cell entries by keys
|
|
86
|
+
* @param keys - list of keys to access
|
|
87
|
+
* @param useInt - whether requesting integers
|
|
88
|
+
*/
|
|
89
|
+
async getAllEntries(keys, useInt) {
|
|
90
|
+
if (!keys.length) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const { dataLayer } = this;
|
|
94
|
+
const arr = await dataLayer.getAllCells(keys, useInt);
|
|
95
|
+
if (useInt) {
|
|
96
|
+
return new Uint32Array(arr);
|
|
97
|
+
}
|
|
98
|
+
return new Float32Array(arr);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function passVariablesToFrontend(vars) {
|
|
2
|
+
return vars
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function plexoraBaseUrl() {
|
|
6
|
+
const base = window.PLEXORA_BASE_URL || "";
|
|
7
|
+
if (!base || base === "/") {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
return "/" + String(base).replace(/^\/+|\/+$/g, "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function plexoraUrl(path) {
|
|
14
|
+
const normalizedPath = String(path || "").replace(/^\/+/, "");
|
|
15
|
+
const base = plexoraBaseUrl();
|
|
16
|
+
if (!normalizedPath) {
|
|
17
|
+
return base || "/";
|
|
18
|
+
}
|
|
19
|
+
return (base ? base + "/" : "/") + normalizedPath;
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
class SimpleEventHandler {
|
|
3
|
+
|
|
4
|
+
constructor(subject) {
|
|
5
|
+
this.subject = subject;
|
|
6
|
+
this.eventListeners = [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
bind(eventNames, eventFunction) {
|
|
10
|
+
for (const eventName of eventNames.split(' ')) {
|
|
11
|
+
this.eventListeners.push({eventName, eventFunction});
|
|
12
|
+
const eventFunctionWrap = e => eventFunction(e.detail, e);
|
|
13
|
+
this.subject.addEventListener(eventName, eventFunctionWrap, false);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getListeners() {
|
|
18
|
+
return this.eventListeners;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
trigger(eventName, detail) {
|
|
22
|
+
this.subject.dispatchEvent(new CustomEvent(eventName, {detail}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Buffer} from 'buffer/';
|
|
2
|
+
import {PNG} from 'pngjs'
|
|
3
|
+
import UPNG from 'upng-js'
|
|
4
|
+
import 'bootstrap'
|
|
5
|
+
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
6
|
+
import 'pngjs'
|
|
7
|
+
import "regenerator-runtime";
|
|
8
|
+
import * as d3 from 'd3';
|
|
9
|
+
import {sliderBottom} from 'd3-simple-slider';
|
|
10
|
+
import 'lodash'
|
|
11
|
+
import '@fortawesome/fontawesome-free/js/all'
|
|
12
|
+
import Sortable from 'sortablejs';
|
|
13
|
+
import OpenSeadragon from 'openseadragon';
|
|
14
|
+
import {jsPDF} from 'jspdf';
|
|
15
|
+
import {ViewerManager} from './views/viewerManager';
|
|
16
|
+
import {GLRenderer} from './services/glRenderer';
|
|
17
|
+
import Dropzone from 'dropzone';
|
|
18
|
+
|
|
19
|
+
window.d3 = d3;
|
|
20
|
+
window.d3.sliderBottom = sliderBottom;
|
|
21
|
+
window.PNG = PNG;
|
|
22
|
+
window.UPNG = UPNG;
|
|
23
|
+
window.Buffer = Buffer;
|
|
24
|
+
window.Sortable = Sortable;
|
|
25
|
+
window.OpenSeadragon = OpenSeadragon;
|
|
26
|
+
window.jsPDF = jsPDF;
|
|
27
|
+
window.Dropzone = Dropzone;
|
|
28
|
+
window.ViewerManager = ViewerManager;
|
|
29
|
+
window.GLRenderer = GLRenderer;
|