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,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cellExplorerColors.js - palettes, and the lookup table core draws from.
|
|
3
|
+
*
|
|
4
|
+
* `buildLUT` is the whole point of the plugin's client half. Everything the
|
|
5
|
+
* user can change without touching the data -- palette, category colour,
|
|
6
|
+
* category visibility, continuous range -- is a rebuild of this table and a
|
|
7
|
+
* redraw, with no request and no geometry rebuilt. The values themselves are
|
|
8
|
+
* fetched once per column and cached, so those interactions cost microseconds
|
|
9
|
+
* rather than a round trip.
|
|
10
|
+
*
|
|
11
|
+
* Two shapes, because a dense table over cell ids is the fast lookup and cell
|
|
12
|
+
* ids are not guaranteed to be dense. Below the threshold it is a flat
|
|
13
|
+
* Uint8Array indexed by id; above it, a Map. Core reads either (see
|
|
14
|
+
* ImageViewer.setCellColorLUT).
|
|
15
|
+
*
|
|
16
|
+
* Alpha 0 is the single "do not draw this cell" channel. Hidden category, no
|
|
17
|
+
* value, a value that is NaN, a cell the table has no row for -- all of them
|
|
18
|
+
* arrive as alpha 0, so the renderer needs exactly one test.
|
|
19
|
+
*/
|
|
20
|
+
class CellExplorerColors {
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Categorical palette. Paul Tol's qualitative sets, which are built to stay
|
|
24
|
+
* distinguishable under the common colour-vision deficiencies -- the usual
|
|
25
|
+
* default (a rainbow, or matplotlib's tab20) puts a red and a green next to
|
|
26
|
+
* each other and asks the user to tell two cell populations apart by them.
|
|
27
|
+
*
|
|
28
|
+
* Ordered so the first handful are maximally separated: most variables have
|
|
29
|
+
* fewer than eight categories, and those are the ones that must not be
|
|
30
|
+
* confusable. Past 24 the palette cycles, which is honest -- there is no
|
|
31
|
+
* set of 60 colours anybody can tell apart, and the legend's search and
|
|
32
|
+
* isolate controls are the real answer at that size.
|
|
33
|
+
*/
|
|
34
|
+
static CATEGORICAL = [
|
|
35
|
+
"#4477aa", "#ee6677", "#228833", "#ccbb44", "#66ccee", "#aa3377",
|
|
36
|
+
"#ee7733", "#0077bb", "#009988", "#cc3311", "#33bbee", "#ee3377",
|
|
37
|
+
"#88ccee", "#44aa99", "#117733", "#999933", "#ddcc77", "#cc6677",
|
|
38
|
+
"#882255", "#aa4499", "#6699cc", "#994455", "#997700", "#66aa55",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The quick picks in a category's colour popover, in the shape core's
|
|
43
|
+
* ColorSwatchPicker takes.
|
|
44
|
+
*
|
|
45
|
+
* Drawn from CATEGORICAL rather than invented, so the ten one-click choices
|
|
46
|
+
* are the same ten colours the legend already assigned by itself: swapping
|
|
47
|
+
* two categories over is picking one of the colours on screen, not matching
|
|
48
|
+
* a new one to them by eye. The neutral last is UNASSIGNED, which is the
|
|
49
|
+
* one deliberate "this means nothing" colour in the set.
|
|
50
|
+
*/
|
|
51
|
+
static SWATCH_PRESETS = [
|
|
52
|
+
{ label: "Blue", hex: "#4477aa" },
|
|
53
|
+
{ label: "Red", hex: "#ee6677" },
|
|
54
|
+
{ label: "Green", hex: "#228833" },
|
|
55
|
+
{ label: "Yellow", hex: "#ccbb44" },
|
|
56
|
+
{ label: "Cyan", hex: "#66ccee" },
|
|
57
|
+
{ label: "Purple", hex: "#aa3377" },
|
|
58
|
+
{ label: "Orange", hex: "#ee7733" },
|
|
59
|
+
{ label: "Teal", hex: "#009988" },
|
|
60
|
+
{ label: "Magenta", hex: "#ee3377" },
|
|
61
|
+
{ label: "Neutral grey", hex: "#5b6675" },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/** Missing values. Neutral and desaturated, so "we do not know" never reads
|
|
65
|
+
* as a category with a finding attached to it. */
|
|
66
|
+
static UNASSIGNED = "#5b6675";
|
|
67
|
+
|
|
68
|
+
/** The label the panel and the LUT both use for missing values. */
|
|
69
|
+
static UNASSIGNED_LABEL = "Unassigned";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Continuous ramps, as anchor colours interpolated to 256 stops at use.
|
|
73
|
+
*
|
|
74
|
+
* Four, deliberately: one perceptually uniform default, one warm, one
|
|
75
|
+
* colour-vision-safe, one diverging, plus a custom two-colour option. Forty
|
|
76
|
+
* matplotlib colormaps is a menu, not a choice, and most of them are
|
|
77
|
+
* perceptually non-uniform in ways that invent structure in the data.
|
|
78
|
+
*/
|
|
79
|
+
static RAMPS = {
|
|
80
|
+
viridis: ["#440154", "#472d7b", "#3b528b", "#2c728e", "#21918c",
|
|
81
|
+
"#28ae80", "#5ec962", "#addc30", "#fde725"],
|
|
82
|
+
magma: ["#000004", "#1c1044", "#4f127b", "#812581", "#b5367a",
|
|
83
|
+
"#e55964", "#fb8761", "#fec287", "#fcfdbf"],
|
|
84
|
+
cividis: ["#00224e", "#123570", "#3b496c", "#575d6d", "#707173",
|
|
85
|
+
"#8a8678", "#a59c74", "#c3b369", "#fee838"],
|
|
86
|
+
coolwarm: ["#3b4cc0", "#6788ee", "#9abbff", "#c9d7f0", "#edd1c2",
|
|
87
|
+
"#f7a889", "#e26952", "#b40426"],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
static PALETTE_LABELS = {
|
|
91
|
+
viridis: "Viridis",
|
|
92
|
+
magma: "Magma",
|
|
93
|
+
cividis: "Cividis (colour-vision safe)",
|
|
94
|
+
coolwarm: "Cool-warm (diverging)",
|
|
95
|
+
custom: "Custom",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Fallback ends for the custom ramp, before the user picks anything. */
|
|
99
|
+
static CUSTOM_LOW = "#1b2a4a";
|
|
100
|
+
static CUSTOM_HIGH = "#f7c948";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Above this, a dense table stops being worth allocating: 8M ids is 32 MB
|
|
104
|
+
* of Uint8Array, and a table whose ids run that high is sparse by
|
|
105
|
+
* definition (they are usually source label values, not row numbers).
|
|
106
|
+
*/
|
|
107
|
+
static DENSE_MAX_ID = 8_000_000;
|
|
108
|
+
|
|
109
|
+
static RAMP_STOPS = 256;
|
|
110
|
+
|
|
111
|
+
// -- colour utilities ---------------------------------------------------
|
|
112
|
+
|
|
113
|
+
static parseHex(hex) {
|
|
114
|
+
const text = String(hex || "").trim();
|
|
115
|
+
if (!/^#[0-9a-f]{6}$/i.test(text)) return null;
|
|
116
|
+
return [
|
|
117
|
+
parseInt(text.slice(1, 3), 16),
|
|
118
|
+
parseInt(text.slice(3, 5), 16),
|
|
119
|
+
parseInt(text.slice(5, 7), 16),
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static toHex([r, g, b]) {
|
|
124
|
+
const part = (v) => Math.max(0, Math.min(255, Math.round(v)))
|
|
125
|
+
.toString(16).padStart(2, "0");
|
|
126
|
+
return `#${part(r)}${part(g)}${part(b)}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The colour a category gets when nobody has chosen one.
|
|
131
|
+
*
|
|
132
|
+
* From its POSITION in the legend, not a hash of its name. Two things
|
|
133
|
+
* follow, and both are what a user expects: the same project opened twice
|
|
134
|
+
* gives the same colours, and `cluster = "1"` and `grade = "1"` are not
|
|
135
|
+
* forced to share one just because they share a label.
|
|
136
|
+
*/
|
|
137
|
+
static defaultCategoryColor(index) {
|
|
138
|
+
return CellExplorerColors.CATEGORICAL[index % CellExplorerColors.CATEGORICAL.length];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 256 RGB stops for a palette, built by interpolating its anchors.
|
|
143
|
+
*
|
|
144
|
+
* Anchors rather than 256 literal entries per ramp: four ramps at 256
|
|
145
|
+
* colours each is 3 kB of source that nobody can read or check, and the
|
|
146
|
+
* interpolation is exact at every anchor.
|
|
147
|
+
*/
|
|
148
|
+
static ramp(palette, custom) {
|
|
149
|
+
const anchors = palette === "custom"
|
|
150
|
+
? [custom?.low || CellExplorerColors.CUSTOM_LOW,
|
|
151
|
+
custom?.high || CellExplorerColors.CUSTOM_HIGH]
|
|
152
|
+
: (CellExplorerColors.RAMPS[palette] || CellExplorerColors.RAMPS.viridis);
|
|
153
|
+
|
|
154
|
+
const points = anchors
|
|
155
|
+
.map((hex) => CellExplorerColors.parseHex(hex))
|
|
156
|
+
.filter(Boolean);
|
|
157
|
+
if (points.length === 0) points.push([0, 0, 0], [255, 255, 255]);
|
|
158
|
+
if (points.length === 1) points.push(points[0]);
|
|
159
|
+
|
|
160
|
+
const stops = CellExplorerColors.RAMP_STOPS;
|
|
161
|
+
const out = new Uint8Array(stops * 3);
|
|
162
|
+
const span = points.length - 1;
|
|
163
|
+
for (let i = 0; i < stops; i += 1) {
|
|
164
|
+
const position = (i / (stops - 1)) * span;
|
|
165
|
+
const lower = Math.min(Math.floor(position), span - 1);
|
|
166
|
+
const t = position - lower;
|
|
167
|
+
const a = points[lower];
|
|
168
|
+
const b = points[lower + 1];
|
|
169
|
+
out[i * 3] = a[0] + (b[0] - a[0]) * t;
|
|
170
|
+
out[i * 3 + 1] = a[1] + (b[1] - a[1]) * t;
|
|
171
|
+
out[i * 3 + 2] = a[2] + (b[2] - a[2]) * t;
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** One ramp stop as a hex string, for the panel's own swatches. */
|
|
177
|
+
static rampStop(palette, custom, fraction) {
|
|
178
|
+
const ramp = CellExplorerColors.ramp(palette, custom);
|
|
179
|
+
const index = Math.max(0, Math.min(CellExplorerColors.RAMP_STOPS - 1,
|
|
180
|
+
Math.round(fraction * (CellExplorerColors.RAMP_STOPS - 1))));
|
|
181
|
+
return CellExplorerColors.toHex([
|
|
182
|
+
ramp[index * 3], ramp[index * 3 + 1], ramp[index * 3 + 2]]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// -- the lookup table ---------------------------------------------------
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build the cell-id -> colour table core draws from.
|
|
189
|
+
*
|
|
190
|
+
* @param spec
|
|
191
|
+
* ids Uint32Array, one per cell, from the values response
|
|
192
|
+
* kind "categorical" | "continuous"
|
|
193
|
+
* codes Uint16Array parallel to ids (categorical)
|
|
194
|
+
* values Float32Array parallel to ids (continuous)
|
|
195
|
+
* categories [{value}] in the server's order -- codes index into this
|
|
196
|
+
* colors { category: "#rrggbb" } overrides
|
|
197
|
+
* hidden Set of category labels to leave undrawn
|
|
198
|
+
* domain [low, high] for continuous
|
|
199
|
+
* palette ramp name, or "custom"
|
|
200
|
+
* custom { low, high } hex ends for "custom"
|
|
201
|
+
* showMissing whether cells with no value are drawn at all
|
|
202
|
+
* blank draw nothing: a table of the right shape, transparent
|
|
203
|
+
* @returns { colors, maxId } | { map } | null
|
|
204
|
+
*/
|
|
205
|
+
static buildLUT(spec) {
|
|
206
|
+
const ids = spec?.ids;
|
|
207
|
+
if (!ids || !ids.length) return null;
|
|
208
|
+
|
|
209
|
+
let maxId = 0;
|
|
210
|
+
for (let i = 0; i < ids.length; i += 1) {
|
|
211
|
+
if (ids[i] > maxId) maxId = ids[i];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const dense = maxId <= CellExplorerColors.DENSE_MAX_ID;
|
|
215
|
+
const table = dense ? new Uint8Array(4 * (maxId + 1)) : null;
|
|
216
|
+
const map = dense ? null : new Map();
|
|
217
|
+
const write = dense
|
|
218
|
+
? (id, r, g, b, a) => {
|
|
219
|
+
const offset = id * 4;
|
|
220
|
+
table[offset] = r;
|
|
221
|
+
table[offset + 1] = g;
|
|
222
|
+
table[offset + 2] = b;
|
|
223
|
+
table[offset + 3] = a;
|
|
224
|
+
}
|
|
225
|
+
// Alpha 0 is simply an absent entry in the sparse form: core treats
|
|
226
|
+
// "no entry" and "alpha 0" identically, so storing hidden cells
|
|
227
|
+
// would be bytes spent to say nothing.
|
|
228
|
+
: (id, r, g, b, a) => { if (a) map.set(id, [r, g, b, a]); };
|
|
229
|
+
|
|
230
|
+
// Nothing written is the whole of "drawn but hidden": a zeroed dense
|
|
231
|
+
// table is alpha 0 for every id, and an empty sparse map says the same.
|
|
232
|
+
// A table rather than a null LUT, because null means "this plugin is
|
|
233
|
+
// not colouring cells" -- core would then draw the layer in its own
|
|
234
|
+
// default white, which is not what hiding an overlay looks like.
|
|
235
|
+
if (!spec.blank) {
|
|
236
|
+
if (spec.kind === "continuous") {
|
|
237
|
+
CellExplorerColors._fillContinuous(spec, ids, write);
|
|
238
|
+
} else {
|
|
239
|
+
CellExplorerColors._fillCategorical(spec, ids, write);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return dense ? { colors: table, maxId } : { map };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static _fillCategorical(spec, ids, write) {
|
|
246
|
+
const categories = spec.categories || [];
|
|
247
|
+
const hidden = spec.hidden || new Set();
|
|
248
|
+
const overrides = spec.colors || {};
|
|
249
|
+
|
|
250
|
+
// One RGBA per code, resolved before the loop over cells. Doing the
|
|
251
|
+
// override lookup and the hex parse per cell instead would be a string
|
|
252
|
+
// operation per cell per redraw.
|
|
253
|
+
const swatches = categories.map((entry, index) => {
|
|
254
|
+
const label = entry.value;
|
|
255
|
+
if (hidden.has(label)) return [0, 0, 0, 0];
|
|
256
|
+
const rgb = CellExplorerColors.parseHex(overrides[label])
|
|
257
|
+
|| CellExplorerColors.parseHex(
|
|
258
|
+
CellExplorerColors.defaultCategoryColor(index));
|
|
259
|
+
return [rgb[0], rgb[1], rgb[2], 255];
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const missingHidden = spec.showMissing === false
|
|
263
|
+
|| hidden.has(CellExplorerColors.UNASSIGNED_LABEL);
|
|
264
|
+
const missingRgb = CellExplorerColors.parseHex(
|
|
265
|
+
overrides[CellExplorerColors.UNASSIGNED_LABEL]
|
|
266
|
+
|| CellExplorerColors.UNASSIGNED);
|
|
267
|
+
const missing = missingHidden
|
|
268
|
+
? [0, 0, 0, 0] : [missingRgb[0], missingRgb[1], missingRgb[2], 255];
|
|
269
|
+
|
|
270
|
+
const codes = spec.codes;
|
|
271
|
+
for (let i = 0; i < ids.length; i += 1) {
|
|
272
|
+
const code = codes[i];
|
|
273
|
+
// Anything not in the dictionary -- the missing sentinel, or a code
|
|
274
|
+
// from a stale payload -- is Unassigned rather than a guess at a
|
|
275
|
+
// nearby category.
|
|
276
|
+
const swatch = code < swatches.length ? swatches[code] : missing;
|
|
277
|
+
write(ids[i], swatch[0], swatch[1], swatch[2], swatch[3]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
static _fillContinuous(spec, ids, write) {
|
|
282
|
+
const ramp = CellExplorerColors.ramp(spec.palette, spec.custom);
|
|
283
|
+
const stops = CellExplorerColors.RAMP_STOPS;
|
|
284
|
+
const values = spec.values;
|
|
285
|
+
let [low, high] = spec.domain || [0, 1];
|
|
286
|
+
if (!Number.isFinite(low) || !Number.isFinite(high) || high <= low) {
|
|
287
|
+
// A constant column, or a range that stopped making sense. Every
|
|
288
|
+
// cell takes the same stop rather than dividing by zero -- the
|
|
289
|
+
// panel says so in words, which is more use than a uniform picture
|
|
290
|
+
// the user has to work out for themselves.
|
|
291
|
+
const flat = Math.floor(stops * 0.75) * 3;
|
|
292
|
+
for (let i = 0; i < ids.length; i += 1) {
|
|
293
|
+
if (!Number.isFinite(values[i])) continue;
|
|
294
|
+
write(ids[i], ramp[flat], ramp[flat + 1], ramp[flat + 2], 255);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const scale = (stops - 1) / (high - low);
|
|
300
|
+
for (let i = 0; i < ids.length; i += 1) {
|
|
301
|
+
const value = values[i];
|
|
302
|
+
// NaN is how the server says "no value", including the infinities
|
|
303
|
+
// it folded in. Left undrawn rather than clamped to an end, which
|
|
304
|
+
// would put a handful of cells at the extreme colour and say
|
|
305
|
+
// nothing about why.
|
|
306
|
+
if (!Number.isFinite(value)) continue;
|
|
307
|
+
// Clipping is display-only. The stored value is untouched; a cell
|
|
308
|
+
// past the top of the range simply takes the top colour.
|
|
309
|
+
let index = Math.round((value - low) * scale);
|
|
310
|
+
if (index < 0) index = 0;
|
|
311
|
+
else if (index >= stops) index = stops - 1;
|
|
312
|
+
const offset = index * 3;
|
|
313
|
+
write(ids[i], ramp[offset], ramp[offset + 1], ramp[offset + 2], 255);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|