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,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class ChannelList - A view to select and deselect active image channels, set color and transfer functions
|
|
3
|
+
*/
|
|
4
|
+
class ChannelList {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @constructor
|
|
8
|
+
* @param config the cinfiguration file (json)
|
|
9
|
+
* @param columns - all the channel names
|
|
10
|
+
* @param dataLayer - the data layer (stub) that executes server requests and holds client side data
|
|
11
|
+
* @param eventHandler - the event handler for distributing interface and data updates
|
|
12
|
+
*/
|
|
13
|
+
constructor(config, columns, dataLayer, eventHandler) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.columns = [...columns];
|
|
16
|
+
this.databaseDescription = {};
|
|
17
|
+
this.maxSelections = config.maxSelections;
|
|
18
|
+
this.eventHandler = eventHandler;
|
|
19
|
+
this.dataLayer = dataLayer;
|
|
20
|
+
this.selections = [];
|
|
21
|
+
this.hasChannelGMM = {};
|
|
22
|
+
this.ranges = {};
|
|
23
|
+
this.sliders = new Map();
|
|
24
|
+
this.image_channels = {};
|
|
25
|
+
this.sel = {};
|
|
26
|
+
this.currentChannels = {};
|
|
27
|
+
this.rangeConnector = {};
|
|
28
|
+
this.colorConnector = {};
|
|
29
|
+
this.channelIDs = {};
|
|
30
|
+
this.createColorPicker();
|
|
31
|
+
this.container = d3.select("#channel_list");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A color picker that can be activated on each channel
|
|
36
|
+
*/
|
|
37
|
+
createColorPicker(){
|
|
38
|
+
// create a color picker
|
|
39
|
+
this.rainbow = rainbow();
|
|
40
|
+
this.colorTransferHandle = null;
|
|
41
|
+
d3.select(document.body)//add it to body
|
|
42
|
+
.call(this.rainbow
|
|
43
|
+
.on('save', (color, x) => {
|
|
44
|
+
let data = this.colorTransferHandle.datum();
|
|
45
|
+
let packet = {
|
|
46
|
+
name: data.name, // cell name : string
|
|
47
|
+
type: data.color, // white, black : string
|
|
48
|
+
color: d3.rgb(color), // parse using d3.rgb(color) : https://github.com/d3/d3-color#rgb
|
|
49
|
+
};
|
|
50
|
+
this.eventHandler.trigger(ChannelList.events.COLOR_TRANSFER_CHANGE, packet);
|
|
51
|
+
this.colorTransferHandle.style('fill', color);
|
|
52
|
+
})
|
|
53
|
+
.on('close', () => this.colorTransferHandle = null));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Removes a channel form the current selection
|
|
58
|
+
* @param name - the name of the channel to remove
|
|
59
|
+
*/
|
|
60
|
+
removeChannel(name) {
|
|
61
|
+
// Update selections
|
|
62
|
+
delete this.sel[this.dataLayer.getFullChannelName(name)];
|
|
63
|
+
|
|
64
|
+
// Trigger
|
|
65
|
+
// this.eventHandler.trigger(ChannelList.events.CHANNEL_SELECT, this.sel);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fetches and caches a channel's image_min/image_max/image_histogram on
|
|
70
|
+
* first activation, then builds its slider -- lazy per-channel
|
|
71
|
+
* replacement for what init(dd) used to do eagerly for every channel.
|
|
72
|
+
* @param name - the channel to fetch stats for
|
|
73
|
+
*/
|
|
74
|
+
async ensureChannelStats(name) {
|
|
75
|
+
const fullName = this.dataLayer.getFullChannelName(name);
|
|
76
|
+
if (this.databaseDescription[fullName]?.image_histogram) return;
|
|
77
|
+
const stats = await this.dataLayer.getImageChannelStats(fullName);
|
|
78
|
+
this.databaseDescription[fullName] = { ...this.databaseDescription[fullName], ...stats };
|
|
79
|
+
this.image_channels[name] = [stats.image_min, stats.image_max];
|
|
80
|
+
const channelListWidth = document.getElementById("channel_list").getBoundingClientRect().width;
|
|
81
|
+
this.addSlider(name, channelListWidth, [stats.image_min, stats.image_max]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Selects a channel as active and adds the respective viual components to the channel panel in the list view
|
|
86
|
+
* @param name - the channel to set and display as selected
|
|
87
|
+
*/
|
|
88
|
+
async selectChannel(name) {
|
|
89
|
+
await this.ensureChannelStats(name);
|
|
90
|
+
|
|
91
|
+
let fullName = this.dataLayer.getFullChannelName(name);
|
|
92
|
+
let channelIdx = imageChannels[fullName];
|
|
93
|
+
let channelID = this.channelIDs[name];
|
|
94
|
+
|
|
95
|
+
if (!this.rangeConnector[channelIdx]) {
|
|
96
|
+
let defaultRange = this.dataLayer.imageBitRange;
|
|
97
|
+
this.sliders.get(name).value([defaultRange[0], defaultRange[1]]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!this.colorConnector[channelIdx]) {
|
|
101
|
+
let rgbColor = `rgb(255, 255, 255)`;
|
|
102
|
+
let selectorColor = `#color_${channelID}`;
|
|
103
|
+
let selectorDoc = document.querySelector(selectorColor);
|
|
104
|
+
selectorDoc.style.fill = rgbColor;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!(name in this.hasChannelGMM)) {
|
|
108
|
+
this.getAndDrawChannelGMM(name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Update selections
|
|
112
|
+
this.selections.push(name);
|
|
113
|
+
this.sel[fullName] = this.image_channels[name];
|
|
114
|
+
|
|
115
|
+
// Trigger
|
|
116
|
+
// this.eventHandler.trigger(ChannelList.events.CHANNEL_SELECT, this.sel);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* triggerss an event that distributes the selected channel information.
|
|
121
|
+
*/
|
|
122
|
+
triggerChannelSelect() {
|
|
123
|
+
// Trigger
|
|
124
|
+
this.eventHandler.trigger(ChannelList.events.CHANNEL_SELECT, this.sel);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* initializes the view (channel list)
|
|
129
|
+
* @param dd - database description
|
|
130
|
+
* @returns {Promise<void>}
|
|
131
|
+
*/
|
|
132
|
+
init(dd) {
|
|
133
|
+
this.databaseDescription = dd;
|
|
134
|
+
this.rainbow.hide();
|
|
135
|
+
// Hide the Loader
|
|
136
|
+
document.getElementById('channel_list_loader').style.display = "none";
|
|
137
|
+
let channel_list = document.getElementById("channel_list");
|
|
138
|
+
let list = document.createElement("ul");
|
|
139
|
+
list.classList.add("list-group")
|
|
140
|
+
channel_list.appendChild(list)
|
|
141
|
+
// Read once, before the loop below, instead of once per channel --
|
|
142
|
+
// channel_list's width doesn't change per row, so re-reading it
|
|
143
|
+
// inside the loop only forces redundant layout reflows.
|
|
144
|
+
const channelListWidth = channel_list.getBoundingClientRect().width;
|
|
145
|
+
// Will show the picker when you click on a color rect
|
|
146
|
+
let showPicker = e => {
|
|
147
|
+
this.colorTransferHandle = d3.select(e.target);
|
|
148
|
+
let color = this.colorTransferHandle.style('fill');
|
|
149
|
+
this.rainbow.show(e.clientX, e.clientY);
|
|
150
|
+
this.rainbow.set(d3.hsl(color));
|
|
151
|
+
|
|
152
|
+
};
|
|
153
|
+
// Draws rows in the channel list
|
|
154
|
+
_.each(this.columns, (column, index) => {
|
|
155
|
+
let channelID = `channel_${index}`;
|
|
156
|
+
this.channelIDs[column] = channelID;
|
|
157
|
+
// div for each row in channel list
|
|
158
|
+
let listItemParentDiv = document.createElement("div");
|
|
159
|
+
listItemParentDiv.classList.add("list-group-item");
|
|
160
|
+
listItemParentDiv.classList.add("container");
|
|
161
|
+
listItemParentDiv.classList.add("channel-list-content");
|
|
162
|
+
// row
|
|
163
|
+
let row = document.createElement("div");
|
|
164
|
+
row.classList.add("row");
|
|
165
|
+
listItemParentDiv.appendChild(row);
|
|
166
|
+
// row
|
|
167
|
+
let row2 = document.createElement("div");
|
|
168
|
+
row2.classList.add("row");
|
|
169
|
+
listItemParentDiv.appendChild(row2);
|
|
170
|
+
|
|
171
|
+
// column within row that contains the name of the channel
|
|
172
|
+
let nameCol = document.createElement("div");
|
|
173
|
+
nameCol.classList.add("col-md-4");
|
|
174
|
+
nameCol.classList.add("channel-col");
|
|
175
|
+
row.appendChild(nameCol);
|
|
176
|
+
|
|
177
|
+
// column within row that Contains the slider for the channel
|
|
178
|
+
let sliderCol = document.createElement("div");
|
|
179
|
+
sliderCol.classList.add("col-md-12");
|
|
180
|
+
sliderCol.classList.add("channel-slider");
|
|
181
|
+
sliderCol.setAttribute('id', "channel-slider_" + channelID)
|
|
182
|
+
row2.appendChild(sliderCol);
|
|
183
|
+
|
|
184
|
+
// column within row that contains svg for color pickers
|
|
185
|
+
let svgCol = document.createElement("div");
|
|
186
|
+
svgCol.classList.add("col-md-4");
|
|
187
|
+
svgCol.classList.add("ms-auto");
|
|
188
|
+
svgCol.classList.add("channel-col");
|
|
189
|
+
svgCol.classList.add("channel-svg-wrapper");
|
|
190
|
+
svgCol.classList.add("col-svg-wrapper");
|
|
191
|
+
row.appendChild(svgCol);
|
|
192
|
+
|
|
193
|
+
let colorLabel = document.createElement("span");
|
|
194
|
+
colorLabel.textContent = "Color:";
|
|
195
|
+
svgCol.appendChild(colorLabel);
|
|
196
|
+
|
|
197
|
+
let svg = d3.select(svgCol)
|
|
198
|
+
.append("svg")
|
|
199
|
+
.attr("width", 15)
|
|
200
|
+
.attr("height", 15)
|
|
201
|
+
svg.selectAll("circle")
|
|
202
|
+
.data([{"color": "white", "name": column}])
|
|
203
|
+
.enter().append("rect")
|
|
204
|
+
.attr("id", "color_" + channelID)
|
|
205
|
+
.attr("class", "color-transfer")
|
|
206
|
+
.attr("cursor", "pointer")
|
|
207
|
+
.attr("stroke", "#757575")
|
|
208
|
+
.attr("fill", d => d.color)
|
|
209
|
+
.attr("width", "10")
|
|
210
|
+
.attr("height", "10")
|
|
211
|
+
.attr("rx", "2")
|
|
212
|
+
.attr("ry", "2")
|
|
213
|
+
.attr("x", "5")
|
|
214
|
+
.attr("y", "2")
|
|
215
|
+
.on('pointerup', showPicker);
|
|
216
|
+
//<rect class="color-transfer" cursor="pointer" stroke="#757575" fill="black" width="10" height="10" rx="2" ry="2" x="-5" y="4.725680443548387" transform="translate(65,0)"></rect>
|
|
217
|
+
svgCol.style.display = "none";
|
|
218
|
+
|
|
219
|
+
let autoCol = document.createElement("div");
|
|
220
|
+
autoCol.classList.add("col-md-4");
|
|
221
|
+
autoCol.classList.add("ms-auto");
|
|
222
|
+
autoCol.classList.add("image-auto")
|
|
223
|
+
autoCol.setAttribute('id', "image-auto_" + channelID)
|
|
224
|
+
autoCol.classList.add("channel-col");
|
|
225
|
+
autoCol.classList.add("channel-svg-wrapper");
|
|
226
|
+
row.appendChild(autoCol);
|
|
227
|
+
|
|
228
|
+
let autoBtn = document.createElement("button");
|
|
229
|
+
autoBtn.classList.add('auto-btn');
|
|
230
|
+
autoBtn.classList.add('auto-loading');
|
|
231
|
+
autoBtn.setAttribute('id', "auto-btn_" + channelID);
|
|
232
|
+
autoBtn.textContent = "auto";
|
|
233
|
+
const clickHandler = this.auto_channel.bind(this, column);
|
|
234
|
+
autoBtn.addEventListener("click", clickHandler);
|
|
235
|
+
|
|
236
|
+
autoCol.appendChild(autoBtn);
|
|
237
|
+
autoBtn.addEventListener("click", e => e.stopPropagation());
|
|
238
|
+
d3.select(autoCol).style('display', "none");
|
|
239
|
+
|
|
240
|
+
let channelName = document.createElement("span");
|
|
241
|
+
channelName.classList.add("channel-name");
|
|
242
|
+
channelName.classList.add("list-button");
|
|
243
|
+
channelName.textContent = column;
|
|
244
|
+
nameCol.appendChild(channelName);
|
|
245
|
+
|
|
246
|
+
listItemParentDiv.addEventListener("click", e => this.toggleChannelPanel(e, svgCol));
|
|
247
|
+
list.appendChild(listItemParentDiv);
|
|
248
|
+
|
|
249
|
+
//hide the (not-yet-built) channel slider row -- image_min/image_max/
|
|
250
|
+
//image_histogram are fetched lazily per channel on first activation
|
|
251
|
+
//(see ensureChannelStats) instead of eagerly for every channel here,
|
|
252
|
+
//so there's nothing to build a slider from yet.
|
|
253
|
+
const defaultRange = this.dataLayer.imageBitRange;
|
|
254
|
+
this.image_channels[column] = [defaultRange[0], defaultRange[1]];
|
|
255
|
+
d3.select('div#channel-slider_' + channelID).style('display', "none");
|
|
256
|
+
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
let arrow = document.getElementById('channels_upload_icon')
|
|
260
|
+
// Gating markers are the feature-table columns get_datasource_description()
|
|
261
|
+
// could actually build a value histogram for -- a channel with no
|
|
262
|
+
// matching marker only has image-side stats (image_min/image_max),
|
|
263
|
+
// no 'histogram' key. If none of this datasource's channels have
|
|
264
|
+
// one, gating auto-match has nothing to match against, so hint at
|
|
265
|
+
// the rename upload rather than showing it unconditionally.
|
|
266
|
+
let hasAnyGatingMatch = _.some(this.columns, column => {
|
|
267
|
+
let fullName = this.dataLayer.getFullChannelName(column);
|
|
268
|
+
let description = this.databaseDescription[fullName];
|
|
269
|
+
return description && description.histogram;
|
|
270
|
+
});
|
|
271
|
+
if (!hasAnyGatingMatch) {
|
|
272
|
+
arrow.title = "No image channel matches a gating marker -- upload a CSV (one name per row, in channel order) to rename channels.";
|
|
273
|
+
}
|
|
274
|
+
arrow.onclick = function () {
|
|
275
|
+
let elem = document.getElementById('channels-upload-from-arrow');
|
|
276
|
+
if (elem && document.createEvent) {
|
|
277
|
+
let evt = document.createEvent("MouseEvents");
|
|
278
|
+
evt.initEvent("click", true, false);
|
|
279
|
+
elem.dispatchEvent(evt);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
document.getElementById("channels-upload-from-arrow").onchange = async () => {
|
|
283
|
+
if (document.getElementById("channels-upload-from-arrow").files) {
|
|
284
|
+
let file = document.getElementById("channels-upload-from-arrow").files[0]
|
|
285
|
+
let formData = new FormData();
|
|
286
|
+
formData.append("file", file);
|
|
287
|
+
let result = await this.dataLayer.submitChannelUpload(formData);
|
|
288
|
+
document.getElementById("channels-upload-from-arrow").value = []
|
|
289
|
+
if (result && result.success) {
|
|
290
|
+
// Channel names changed on disk -- reload so the channel
|
|
291
|
+
// list, gating panel, and cached description all pick up
|
|
292
|
+
// the renamed channels instead of trying to patch every
|
|
293
|
+
// dependent piece of UI state in place.
|
|
294
|
+
window.location.reload();
|
|
295
|
+
} else {
|
|
296
|
+
alert('Failed to rename channels: ' + (result && result.error ? result.error : 'unknown error'));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
auto_channel(name) {
|
|
306
|
+
let fullName = this.dataLayer.getFullChannelName(name);
|
|
307
|
+
if (!(name in this.hasChannelGMM)) {
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
let vmin = this.hasChannelGMM[name]['vmin'];
|
|
311
|
+
let vmax = this.hasChannelGMM[name]['vmax'];
|
|
312
|
+
this.sliders.get(name).value([vmin, vmax]);
|
|
313
|
+
|
|
314
|
+
let channelIdx = imageChannels[fullName];
|
|
315
|
+
let defaultRange = this.dataLayer.imageBitRange;
|
|
316
|
+
this.rangeConnector[channelIdx] = [vmin / defaultRange[1], vmax / defaultRange[1]];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @function toggleChannelPanel
|
|
322
|
+
*
|
|
323
|
+
* @param {Event} event - The event
|
|
324
|
+
* @param svgCol - the column to expand or collapse
|
|
325
|
+
*/
|
|
326
|
+
toggleChannelPanel(event, svgCol) {
|
|
327
|
+
|
|
328
|
+
// If you clicked on the svg, ignore this behavior
|
|
329
|
+
if (event.target.closest("svg")) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get info
|
|
334
|
+
let parent = event.target.closest(".list-group-item");
|
|
335
|
+
let name = parent.querySelector('.channel-name').textContent;
|
|
336
|
+
let channelID = this.channelIDs[name];
|
|
337
|
+
let status = !parent.classList.contains("active");
|
|
338
|
+
|
|
339
|
+
// If active - else inactive
|
|
340
|
+
if (status) {
|
|
341
|
+
|
|
342
|
+
// Don't add channel is the max are selected
|
|
343
|
+
if (_.size(this.selections) >= this.maxSelections) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Update properties and add slider
|
|
348
|
+
d3.select(parent).classed("active", true);
|
|
349
|
+
svgCol.style.display = "block";
|
|
350
|
+
d3.select('div#channel-slider_' + channelID).style('display', "block")
|
|
351
|
+
d3.select('div#image-auto_' + channelID).style('display', "block");
|
|
352
|
+
|
|
353
|
+
// Add channel
|
|
354
|
+
this.selectChannel(name);
|
|
355
|
+
|
|
356
|
+
} else {
|
|
357
|
+
// Clear panel visibility
|
|
358
|
+
// clearOut();
|
|
359
|
+
this.selections = _.pull(this.selections, name);
|
|
360
|
+
|
|
361
|
+
// Remove channel and rerender
|
|
362
|
+
this.removeChannel(name);
|
|
363
|
+
|
|
364
|
+
// Hide
|
|
365
|
+
d3.select(parent).classed("active", false);
|
|
366
|
+
svgCol.style.display = "none";
|
|
367
|
+
d3.select('div#channel-slider_' + channelID).style('display', "none")
|
|
368
|
+
d3.select('div#image-auto_' + channelID).style('display', "none");
|
|
369
|
+
|
|
370
|
+
// Trigger viewer cleanse
|
|
371
|
+
// this.eventHandler.trigger(ChannelList.events.CHANNELS_CHANGE, this.selections);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//
|
|
375
|
+
let selectionsHeaderDiv = document.getElementById("selected-channels-header-div");
|
|
376
|
+
if (selectionsHeaderDiv) {
|
|
377
|
+
if (_.size(this.selections) >= this.maxSelections) {
|
|
378
|
+
selectionsHeaderDiv.classList.add('bold-selections-header');
|
|
379
|
+
} else {
|
|
380
|
+
selectionsHeaderDiv.classList.remove('bold-selections-header');
|
|
381
|
+
}
|
|
382
|
+
let packet = {selections: this.selections, name, status};
|
|
383
|
+
// console.log('channels_change', packet);
|
|
384
|
+
document.getElementById("num-selected-channels").textContent = _.size(this.selections);
|
|
385
|
+
|
|
386
|
+
// Trigger event
|
|
387
|
+
this.eventHandler.trigger(ChannelList.events.CHANNELS_CHANGE, packet);
|
|
388
|
+
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
add a slider to a channel
|
|
394
|
+
@param {} data - the min and max range of the slider
|
|
395
|
+
@param {} activeRange - the predefined values for the lower and upper handle
|
|
396
|
+
@param {String} name - the name of the slider (used as part of the id)
|
|
397
|
+
@param {} swidth - the pixel width of the slider
|
|
398
|
+
*/
|
|
399
|
+
addSlider(name, swidth, activeRange) {
|
|
400
|
+
|
|
401
|
+
const f = (d3.format('.2%'))
|
|
402
|
+
let channelID = this.channelIDs[name];
|
|
403
|
+
const fullName = this.dataLayer.getFullChannelName(name);
|
|
404
|
+
const { xDomain, yDomain, histogramData} = this.histogramData(fullName);
|
|
405
|
+
let data_min = this.databaseDescription[fullName]['image_min'];
|
|
406
|
+
let data_max = this.databaseDescription[fullName]['image_max'];
|
|
407
|
+
|
|
408
|
+
//add range slider row content
|
|
409
|
+
const sliderSimple = d3.sliderBottom(d3.scaleLog())
|
|
410
|
+
.min(data_min)
|
|
411
|
+
.max(data_max)
|
|
412
|
+
.width(swidth - 75)//.tickFormat(d3.format("s"))
|
|
413
|
+
.fill('orange')
|
|
414
|
+
.on('onchange', (range) => {
|
|
415
|
+
const v0 = Math.round(range[0]);
|
|
416
|
+
const v1 = Math.round(range[1]);
|
|
417
|
+
d3.select('#slider-input' + channelID + 0).attr('value', v0);
|
|
418
|
+
d3.select('#slider-input' + channelID + 0).property('value', v0);
|
|
419
|
+
d3.select('#slider-input' + channelID + 1).attr('value', v1);
|
|
420
|
+
d3.select('#slider-input' + channelID + 1).property('value', v1);
|
|
421
|
+
this.moveSliderHandles(sliderSimple, [v0, v1], name);
|
|
422
|
+
})
|
|
423
|
+
.ticks(5)
|
|
424
|
+
.default([Math.round(activeRange[0]), Math.round(activeRange[1])])
|
|
425
|
+
.handle(
|
|
426
|
+
d3.symbol()
|
|
427
|
+
.type(d3.symbolCircle)
|
|
428
|
+
.size(100)
|
|
429
|
+
)
|
|
430
|
+
.tickValues([])
|
|
431
|
+
|
|
432
|
+
this.sliders.set(name, sliderSimple);
|
|
433
|
+
|
|
434
|
+
//create the slider svg and call the slider
|
|
435
|
+
var gSimple = d3
|
|
436
|
+
.select('#channel-slider_' + channelID)
|
|
437
|
+
.append('svg')
|
|
438
|
+
.attr('class', 'svgslider')
|
|
439
|
+
.attr('id', 'channel-slider_svg_' + channelID)
|
|
440
|
+
.attr('width', swidth)
|
|
441
|
+
.attr('height', 80)
|
|
442
|
+
.append('g')
|
|
443
|
+
.attr('transform', 'translate(20,40)');
|
|
444
|
+
|
|
445
|
+
let xScale = d3.scaleLinear()
|
|
446
|
+
.domain(xDomain)
|
|
447
|
+
.range([0, swidth - 73])
|
|
448
|
+
|
|
449
|
+
let yScale = d3.scaleLinear()
|
|
450
|
+
.domain(yDomain)
|
|
451
|
+
.range([0, 23])
|
|
452
|
+
|
|
453
|
+
let line = d3.line()
|
|
454
|
+
.x(d => xScale(d.x))
|
|
455
|
+
.y(d => yScale(d.y))
|
|
456
|
+
.curve(d3.curveMonotoneX)
|
|
457
|
+
|
|
458
|
+
const lines = gSimple.selectAll('.image_distribution_line');
|
|
459
|
+
const paths = lines.data([histogramData]).enter().append('path');
|
|
460
|
+
paths
|
|
461
|
+
.attr('d', line)
|
|
462
|
+
.attr('class', 'image_distribution_line')
|
|
463
|
+
.attr('transform', 'translate(0,-31)')
|
|
464
|
+
.attr('fill', 'none')
|
|
465
|
+
|
|
466
|
+
gSimple.call(sliderSimple);
|
|
467
|
+
|
|
468
|
+
//slider value to be displayed closer to the slider than default
|
|
469
|
+
d3.selectAll('.parameter-value').select('text')
|
|
470
|
+
.attr("y", 10);
|
|
471
|
+
|
|
472
|
+
//both handles
|
|
473
|
+
const { sliders } = this;
|
|
474
|
+
const handles = d3.select('#channel-slider_' + channelID).selectAll(".parameter-value");
|
|
475
|
+
handles.each(function(d, i) {
|
|
476
|
+
d3.select(this).append("foreignObject")
|
|
477
|
+
.attr('id', 'foreignObject_' + channelID + i)
|
|
478
|
+
.attr("width", 50)
|
|
479
|
+
.attr("height", 40)
|
|
480
|
+
.attr('x', -25)
|
|
481
|
+
.attr( 'y', -17)
|
|
482
|
+
.style('padding',"10px")
|
|
483
|
+
.append("xhtml:body")
|
|
484
|
+
.attr('xmlns','http://www.w3.org/1999/xhtml')
|
|
485
|
+
.style('background', 'none')
|
|
486
|
+
.append('input')
|
|
487
|
+
.attr( 'y', -17)
|
|
488
|
+
.attr('id', 'slider-input' + channelID + i)
|
|
489
|
+
.attr('type', 'text')
|
|
490
|
+
.attr('class', 'input')
|
|
491
|
+
.attr('value', () => {
|
|
492
|
+
return sliders.get(name).value()[i]
|
|
493
|
+
});
|
|
494
|
+
//remove the previous text label
|
|
495
|
+
d3.select(this).select('text').remove();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
//entering a value in the input field of a slider handle
|
|
499
|
+
const moveSliderHandles = this.moveSliderHandles.bind(this);
|
|
500
|
+
handles.selectAll('.input').on('keydown', function (event, d) {
|
|
501
|
+
if(event.key == "Enter"){
|
|
502
|
+
const val = parseFloat(this.value.replace("%", ""));
|
|
503
|
+
const vals = sliderSimple.silentValue();
|
|
504
|
+
vals[d.index] = val;
|
|
505
|
+
moveSliderHandles(sliderSimple, vals, name);
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
return sliderSimple;
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
histogramData(fullName) {
|
|
513
|
+
const histogramData = this.databaseDescription[fullName].image_histogram;
|
|
514
|
+
const xMin = Math.min(...histogramData.map(e => e.x));
|
|
515
|
+
const xMax = Math.max(...histogramData.map(e => e.x));
|
|
516
|
+
const yMax = Math.max(...histogramData.map(e => e.y));
|
|
517
|
+
return {
|
|
518
|
+
histogramData,
|
|
519
|
+
xDomain: [xMin, xMax],
|
|
520
|
+
yDomain: [yMax, 0],
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async getAndDrawChannelGMM(name) {
|
|
525
|
+
// drawChannelGMM() below draws onto the slider's SVG group, which
|
|
526
|
+
// only exists once ensureChannelStats has run for this channel --
|
|
527
|
+
// guard here (not just in selectChannel) since viewerSidebar.js also
|
|
528
|
+
// calls this directly, bypassing selectChannel.
|
|
529
|
+
await this.ensureChannelStats(name);
|
|
530
|
+
const fullName = this.dataLayer.getFullChannelName(name);
|
|
531
|
+
const packet = await this.dataLayer.getChannelGMM(fullName);
|
|
532
|
+
const channelID = this.channelIDs[name];
|
|
533
|
+
const autoBtn = document.getElementById(`auto-btn_${channelID}`);
|
|
534
|
+
autoBtn.classList.remove("auto-loading")
|
|
535
|
+
this.hasChannelGMM[name] = packet;
|
|
536
|
+
|
|
537
|
+
this.drawChannelGMM(name);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
drawChannelGMM(name) {
|
|
541
|
+
let channelID = this.channelIDs[name];
|
|
542
|
+
let packet = this.hasChannelGMM[name];
|
|
543
|
+
let channel_gmm1Data = packet['image_gmm_1'];
|
|
544
|
+
let channel_gmm2Data = packet['image_gmm_2'];
|
|
545
|
+
let channel_gmm3Data = packet['image_gmm_3'];
|
|
546
|
+
const fullName = this.dataLayer.getFullChannelName(name);
|
|
547
|
+
const { xDomain, yDomain } = this.histogramData(fullName);
|
|
548
|
+
|
|
549
|
+
const channelListEl = document.getElementById("channel_list");
|
|
550
|
+
const swidth = channelListEl.getBoundingClientRect().width;
|
|
551
|
+
|
|
552
|
+
let xScale = d3.scaleLinear()
|
|
553
|
+
.domain(xDomain)
|
|
554
|
+
.range([0, swidth - 73])
|
|
555
|
+
|
|
556
|
+
let yScale = d3.scaleLinear()
|
|
557
|
+
.domain(yDomain)
|
|
558
|
+
.range([0, 23])
|
|
559
|
+
|
|
560
|
+
let line = d3.line()
|
|
561
|
+
.x(d => xScale(d.x))
|
|
562
|
+
.y(d => yScale(d.y))
|
|
563
|
+
.curve(d3.curveMonotoneX)
|
|
564
|
+
|
|
565
|
+
let gSimple = d3.select('#channel-slider_svg_' + channelID + ' g')
|
|
566
|
+
|
|
567
|
+
gSimple.selectAll('.image_gmm1_line')
|
|
568
|
+
.data([channel_gmm1Data])
|
|
569
|
+
.enter()
|
|
570
|
+
.append('path')
|
|
571
|
+
.attr('d', line)
|
|
572
|
+
.attr('class', 'gmm_line')
|
|
573
|
+
.attr('class', 'gmm_line_'+name)
|
|
574
|
+
.attr('transform', 'translate(0,-31)')
|
|
575
|
+
.attr('fill', 'none')
|
|
576
|
+
.attr('stroke', 'green')
|
|
577
|
+
|
|
578
|
+
gSimple.selectAll('.image_gmm2_line')
|
|
579
|
+
.data([channel_gmm2Data])
|
|
580
|
+
.enter()
|
|
581
|
+
.append('path')
|
|
582
|
+
.attr('d', line)
|
|
583
|
+
.attr('class', 'gmm_line')
|
|
584
|
+
.attr('class', 'gmm_line_'+name)
|
|
585
|
+
.attr('transform', 'translate(0,-31)')
|
|
586
|
+
.attr('fill', 'none')
|
|
587
|
+
.attr('stroke', 'blue')
|
|
588
|
+
|
|
589
|
+
gSimple.selectAll('.image_gmm3_line')
|
|
590
|
+
.data([channel_gmm3Data])
|
|
591
|
+
.enter()
|
|
592
|
+
.append('path')
|
|
593
|
+
.attr('d', line)
|
|
594
|
+
.attr('class', 'gmm_line')
|
|
595
|
+
.attr('class', 'gmm_line_'+name)
|
|
596
|
+
.attr('transform', 'translate(0,-31)')
|
|
597
|
+
.attr('fill', 'none')
|
|
598
|
+
.attr('stroke', 'red')
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @function moveSliderHandles - move the slider handles and input fields so that input fields don't overlap when handles are close
|
|
603
|
+
*
|
|
604
|
+
* @param slider - the slider affected
|
|
605
|
+
* @param vals - holds the new positions
|
|
606
|
+
* @param name - the name of the slider
|
|
607
|
+
*/
|
|
608
|
+
moveSliderHandles(slider, vals, name){
|
|
609
|
+
let channelID = this.channelIDs[name];
|
|
610
|
+
this.image_channels[name] = vals;
|
|
611
|
+
slider.silentValue(vals);
|
|
612
|
+
if (vals[1] - vals[0] < 0.41){
|
|
613
|
+
console.log('slider handles overlap..do something');
|
|
614
|
+
d3.select('#foreignObject_' + channelID + 1).attr('x', 5);
|
|
615
|
+
}else{
|
|
616
|
+
d3.select('#foreignObject_' + channelID + 1).attr('x', -25);
|
|
617
|
+
}
|
|
618
|
+
const packet = {name: name, dataRange: [...vals]};
|
|
619
|
+
this.eventHandler.trigger(ChannelList.events.BRUSH_MOVE, packet);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
*/
|
|
624
|
+
resetChannelList() {
|
|
625
|
+
let channelList = _.clone(this.selections);
|
|
626
|
+
_.each(channelList, col => {
|
|
627
|
+
let channelID = this.channelIDs[col];
|
|
628
|
+
let channel_selector = `#channel-slider_${channelID}`;
|
|
629
|
+
document.querySelector(channel_selector).click();
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* on window resize we re-initialize (this should be better handled with an update pattern)
|
|
636
|
+
*/
|
|
637
|
+
window.addEventListener("resize", function () {
|
|
638
|
+
const { channelList } = __plexora;
|
|
639
|
+
if (typeof channelList != "undefined" && channelList) {
|
|
640
|
+
channelList.sliders.forEach((slider, name) => {
|
|
641
|
+
let channelID = channelList.channelIDs[name]
|
|
642
|
+
d3.select('div#channel-slider_' + channelID).select('svg').remove();
|
|
643
|
+
const channelListEl = document.getElementById("channel_list");
|
|
644
|
+
if (channelListEl) {
|
|
645
|
+
const swidth = channelListEl.getBoundingClientRect().width;
|
|
646
|
+
channelList.addSlider(name, swidth, slider.value());
|
|
647
|
+
if (channelList.hasChannelGMM[name]) {
|
|
648
|
+
channelList.drawChannelGMM(name);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
//static vars: events introduced in this class and used across the app
|
|
656
|
+
ChannelList.events = {
|
|
657
|
+
BRUSH_MOVE: "BRUSH_MOVE",
|
|
658
|
+
COLOR_TRANSFER_CHANGE_MOVE: "COLOR_TRANSFER_CHANGE_MOVE",
|
|
659
|
+
COLOR_TRANSFER_CHANGE: "COLOR_TRANSFER_CHANGE",
|
|
660
|
+
CHANNELS_CHANGE: "CHANNELS_CHANGE",
|
|
661
|
+
CHANNEL_SELECT: "CHANNEL_SELECT",
|
|
662
|
+
RESET_LISTS: "RESET_LISTS"
|
|
663
|
+
};
|