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,400 @@
|
|
|
1
|
+
//todo add crossfilter stuff here... build some lensingFilters and sorters for individual and combined dimensions
|
|
2
|
+
|
|
3
|
+
class DataLayer {
|
|
4
|
+
|
|
5
|
+
constructor(config, imageChannels) {
|
|
6
|
+
var that = this;
|
|
7
|
+
//vars and consts
|
|
8
|
+
this.config = config;
|
|
9
|
+
//all image channels
|
|
10
|
+
this.imageChannels = imageChannels;
|
|
11
|
+
|
|
12
|
+
this.imageBitRange = [0, 65536];
|
|
13
|
+
//selections
|
|
14
|
+
this.currentSelection = new Map();
|
|
15
|
+
// Roles, not literal column names -- see services/datasetContext.js.
|
|
16
|
+
// Null for an image-only project, and also for one whose coordinate
|
|
17
|
+
// columns nobody has identified yet; nothing needing real coordinates
|
|
18
|
+
// should be reachable in either case (callers check `schema` first).
|
|
19
|
+
this.schema = PlexoraDataset.resolveSchema(this.config);
|
|
20
|
+
this.x = this.schema?.x;
|
|
21
|
+
this.y = this.schema?.y;
|
|
22
|
+
this.phenotypes = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async init() {
|
|
26
|
+
try {
|
|
27
|
+
await fetch(plexoraUrl('init_database') + '?' + new URLSearchParams({
|
|
28
|
+
datasource: datasource
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.log("Error Initializing Dataset", e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getRow(row) {
|
|
37
|
+
try {
|
|
38
|
+
let response = await fetch(plexoraUrl('get_database_row') + '?' + new URLSearchParams({
|
|
39
|
+
row: row,
|
|
40
|
+
datasource: datasource
|
|
41
|
+
}))
|
|
42
|
+
let response_data = await response.json();
|
|
43
|
+
return response_data;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.log("Error Getting Row", e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getSavedChannelList() {
|
|
50
|
+
try {
|
|
51
|
+
let response = await fetch(plexoraUrl('get_saved_channel_list') + '?' + new URLSearchParams({
|
|
52
|
+
datasource: datasource
|
|
53
|
+
}))
|
|
54
|
+
let response_data = await response.json();
|
|
55
|
+
return response_data;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.log("Error Getting Saved Channel List", e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async saveChannelList(map_channels, active_channels, list_colors, list_ranges, list_channels) {
|
|
62
|
+
const self = this;
|
|
63
|
+
try {
|
|
64
|
+
let response = await fetch(plexoraUrl('save_channel_list'), {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: {
|
|
67
|
+
'Accept': 'application/json',
|
|
68
|
+
'Content-Type': 'application/json'
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify(
|
|
71
|
+
{
|
|
72
|
+
datasource: datasource,
|
|
73
|
+
map_channels: map_channels,
|
|
74
|
+
active_channels: active_channels,
|
|
75
|
+
list_colors: list_colors,
|
|
76
|
+
list_ranges: list_ranges,
|
|
77
|
+
list_channels: list_channels
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
});
|
|
81
|
+
let response_data = await response.json();
|
|
82
|
+
return response_data;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.log("Error Saving Channel List", e);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getColumnDistributions(columns) {
|
|
89
|
+
try {
|
|
90
|
+
let response = await fetch(plexoraUrl('get_column_distributions') + '?' + new URLSearchParams({
|
|
91
|
+
columns: columns,
|
|
92
|
+
datasource: datasource
|
|
93
|
+
}))
|
|
94
|
+
let distributions = await response.json();
|
|
95
|
+
return distributions;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.log("Error Getting Nearest Cell", e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async submitChannelUpload(formData) {
|
|
102
|
+
try {
|
|
103
|
+
formData.append('datasource', datasource);
|
|
104
|
+
let response = await fetch(plexoraUrl('upload_channels'), {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: formData
|
|
107
|
+
})
|
|
108
|
+
let cell = await response.json();
|
|
109
|
+
return cell;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.log("Error Getting Submitting Form Upload", e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async getAllCells(start_keys, use_integer) {
|
|
116
|
+
const dtype = use_integer ? 'integer' : 'float'
|
|
117
|
+
const base_url = plexoraUrl(`get_all_cells/${dtype}/`) + '?'
|
|
118
|
+
try {
|
|
119
|
+
const headers = new Headers();
|
|
120
|
+
headers.append("Content-Type","application/octet-stream");
|
|
121
|
+
headers.append("Content-Encoding","gzip");
|
|
122
|
+
const response = await fetch(base_url + new URLSearchParams({
|
|
123
|
+
start_keys: start_keys,
|
|
124
|
+
datasource: datasource
|
|
125
|
+
}), {
|
|
126
|
+
headers: headers
|
|
127
|
+
})
|
|
128
|
+
return response.arrayBuffer();
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.log("Error Getting Gated Cell Ids", e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getCentroidManifest() {
|
|
135
|
+
try {
|
|
136
|
+
let response = await fetch(plexoraUrl('get_centroid_manifest') + '?' + new URLSearchParams({
|
|
137
|
+
datasource: datasource
|
|
138
|
+
}))
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error(`Centroid manifest request failed: ${response.status}`);
|
|
141
|
+
}
|
|
142
|
+
const text = await response.text();
|
|
143
|
+
if (!text.trim().startsWith("{")) {
|
|
144
|
+
throw new Error("Centroid manifest response was not JSON");
|
|
145
|
+
}
|
|
146
|
+
return JSON.parse(text);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.log("Error Getting Centroid Manifest", e);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getCentroidTiles(level, tiles, filter = {}, maxPoints = 50000) {
|
|
154
|
+
try {
|
|
155
|
+
let response = await fetch(plexoraUrl('get_centroid_tiles'), {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: {
|
|
158
|
+
'Accept': 'application/octet-stream',
|
|
159
|
+
'Content-Type': 'application/json'
|
|
160
|
+
},
|
|
161
|
+
body: JSON.stringify(
|
|
162
|
+
{
|
|
163
|
+
datasource: datasource,
|
|
164
|
+
level: level,
|
|
165
|
+
tiles: tiles,
|
|
166
|
+
filter: filter || {},
|
|
167
|
+
max_points: maxPoints
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`Centroid tile request failed: ${response.status}`);
|
|
173
|
+
}
|
|
174
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
175
|
+
if (!contentType.includes("application/octet-stream")) {
|
|
176
|
+
throw new Error("Centroid tile response was not binary");
|
|
177
|
+
}
|
|
178
|
+
return response.arrayBuffer();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.log("Error Getting Centroid Tiles", e);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getDatabaseDescription() {
|
|
186
|
+
try {
|
|
187
|
+
let response = await fetch(plexoraUrl('get_database_description') + '?' + new URLSearchParams({
|
|
188
|
+
datasource: datasource
|
|
189
|
+
}))
|
|
190
|
+
let description = await response.json();
|
|
191
|
+
return description;
|
|
192
|
+
} catch (e) {
|
|
193
|
+
console.log("Error Getting DB Description", e);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async getSegmentationStatus() {
|
|
198
|
+
try {
|
|
199
|
+
let response = await fetch(plexoraUrl('get_segmentation_status') + '?' + new URLSearchParams({
|
|
200
|
+
datasource: datasource
|
|
201
|
+
}))
|
|
202
|
+
return await response.json();
|
|
203
|
+
} catch (e) {
|
|
204
|
+
console.log("Error Getting Segmentation Status", e);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getChannelGMM(channel) {
|
|
209
|
+
try {
|
|
210
|
+
let response = await fetch(plexoraUrl('get_channel_gmm') + '?' + new URLSearchParams({
|
|
211
|
+
channel: channel,
|
|
212
|
+
datasource: datasource
|
|
213
|
+
}))
|
|
214
|
+
let packet_gmm = await response.json();
|
|
215
|
+
return packet_gmm;
|
|
216
|
+
} catch (e) {
|
|
217
|
+
console.log("Error Getting Channel GMM", e);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async getImageChannelStats(channel) {
|
|
222
|
+
try {
|
|
223
|
+
let response = await fetch(plexoraUrl('get_image_channel_stats') + '?' + new URLSearchParams({
|
|
224
|
+
channel: channel,
|
|
225
|
+
datasource: datasource
|
|
226
|
+
}))
|
|
227
|
+
return await response.json();
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.log("Error Getting Image Channel Stats", e);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async getChannelCellIds(sels) {
|
|
234
|
+
try {
|
|
235
|
+
let response = await fetch(plexoraUrl('get_channel_cell_ids') + '?' + new URLSearchParams({
|
|
236
|
+
filter: JSON.stringify(sels),
|
|
237
|
+
datasource: datasource
|
|
238
|
+
}))
|
|
239
|
+
let cellIds = await response.json();
|
|
240
|
+
return cellIds;
|
|
241
|
+
} catch (e) {
|
|
242
|
+
console.log("Error Getting Channel Cell Ids", e);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async getChannelNames(shortNames = true) {
|
|
247
|
+
try {
|
|
248
|
+
let response = await fetch(plexoraUrl('get_channel_names') + '?' + new URLSearchParams({
|
|
249
|
+
datasource: datasource,
|
|
250
|
+
shortNames: shortNames
|
|
251
|
+
}))
|
|
252
|
+
let response_data = await response.json();
|
|
253
|
+
return response_data;
|
|
254
|
+
} catch (e) {
|
|
255
|
+
console.log("Error Getting Sample Row", e);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async getNearestCell(point_x, point_y) {
|
|
260
|
+
try {
|
|
261
|
+
let response = await fetch(plexoraUrl('get_nearest_cell') + '?' + new URLSearchParams({
|
|
262
|
+
point_x: point_x,
|
|
263
|
+
point_y: point_y,
|
|
264
|
+
datasource: datasource
|
|
265
|
+
}))
|
|
266
|
+
let cell = await response.json();
|
|
267
|
+
return cell;
|
|
268
|
+
} catch (e) {
|
|
269
|
+
console.log("Error Getting Nearest Cell", e);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
getCurrentSelection() {
|
|
275
|
+
return this.currentSelection;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
clearCurrentSelection() {
|
|
279
|
+
this.currentSelection.clear();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getImageBitRange(float = false) {
|
|
283
|
+
const self = this;
|
|
284
|
+
if (!float) {
|
|
285
|
+
return self.imageBitRange;
|
|
286
|
+
} else {
|
|
287
|
+
return [0.0, 1.0];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
addToCurrentSelection(item, allowDelete, clearPriors) {
|
|
292
|
+
// No real per-cell id field for a quick-view (no feature data)
|
|
293
|
+
// datasource -- nothing to key a selection by.
|
|
294
|
+
if (!this.schema) return;
|
|
295
|
+
|
|
296
|
+
// delete item on second click
|
|
297
|
+
if (allowDelete && this.currentSelection.has(item)) {
|
|
298
|
+
this.currentSelection.delete(item);
|
|
299
|
+
if (clearPriors) {
|
|
300
|
+
this.currentSelection.clear();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// console.log('current selection size:', this.currentSelection.size);
|
|
304
|
+
if (this.currentSelection.size > 0) {
|
|
305
|
+
// console.log('id: ', this.currentSelection.values().next().value.id);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// clear previous items
|
|
311
|
+
if (clearPriors) {
|
|
312
|
+
this.currentSelection.clear();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// add new item
|
|
316
|
+
this.currentSelection.set(item[this.schema.cellId], item);
|
|
317
|
+
|
|
318
|
+
// console.log('current selection size:', this.currentSelection.size);
|
|
319
|
+
if (this.currentSelection.size > 0) {
|
|
320
|
+
// console.log('id: ', this.currentSelection.values().next().value.id);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
addAllToCurrentSelection(items, allowDelete, clearPriors) {
|
|
326
|
+
// console.log("update current selection")
|
|
327
|
+
// No real per-cell id field for a quick-view (no feature data)
|
|
328
|
+
// datasource -- nothing to key a selection by.
|
|
329
|
+
if (!this.schema) return;
|
|
330
|
+
var that = this;
|
|
331
|
+
let idField = this.schema.cellId
|
|
332
|
+
that.currentSelection = new Map(items.map(i => [(i[idField]), i]));
|
|
333
|
+
// console.log("update current selection done")
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
isImageFeature(key) {
|
|
337
|
+
if (this.imageChannels.hasOwnProperty(key)
|
|
338
|
+
&& key != 'CellId' && key != 'id' && key != 'CellID' && key != 'ID' && key != 'Area') {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
getShortChannelName(fullname) {
|
|
345
|
+
var shortname = fullname;
|
|
346
|
+
this.config["imageData"].forEach(function (channel) {
|
|
347
|
+
if (channel.fullname == fullname) {
|
|
348
|
+
shortname = channel.name;
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return shortname;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
getFullChannelName(shortname) {
|
|
355
|
+
var fullname = shortname;
|
|
356
|
+
this.config["imageData"].forEach(function (channel) {
|
|
357
|
+
if (channel.name == shortname) {
|
|
358
|
+
fullname = channel.fullname;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return fullname;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async getMetadata() {
|
|
365
|
+
try {
|
|
366
|
+
let response = await fetch(plexoraUrl('get_ome_metadata') + '?' + new URLSearchParams({
|
|
367
|
+
datasource: datasource
|
|
368
|
+
}))
|
|
369
|
+
let response_data = await response.json();
|
|
370
|
+
return response_data;
|
|
371
|
+
} catch (e) {
|
|
372
|
+
console.log("Error Getting Metadata", e);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Decimal-place precision for gate slider rounding, derived from the
|
|
378
|
+
* channel's own observed [min, max] span rather than a stored config
|
|
379
|
+
* flag -- aims for ~200 distinguishable steps across the range. Wide
|
|
380
|
+
* integer-scale channels (e.g. raw 0-65535 pixel intensities) land on 0
|
|
381
|
+
* decimals (whole-number gates); narrow float-scale channels (e.g.
|
|
382
|
+
* already log/arcsinh-transformed markers spanning ~1.7-2.2) get enough
|
|
383
|
+
* decimal places to stay meaningful. Replaces the old
|
|
384
|
+
* dataset.isTransformed flag, which had to be set correctly
|
|
385
|
+
* (and consistently across datasources built from the same source data)
|
|
386
|
+
* at import time and silently broke gating whenever it didn't match the
|
|
387
|
+
* data's actual scale -- this is instead computed fresh from the same
|
|
388
|
+
* min/max every request already returns, so it can't drift out of sync.
|
|
389
|
+
* @param {number[]} range - [min, max] of the channel being gated
|
|
390
|
+
* @returns {number}
|
|
391
|
+
*/
|
|
392
|
+
gateDecimals(range) {
|
|
393
|
+
const span = Math.abs(((range && range[1]) || 0) - ((range && range[0]) || 0));
|
|
394
|
+
if (!Number.isFinite(span) || span <= 0) return 0;
|
|
395
|
+
const decimals = Math.ceil(Math.log10(200 / span));
|
|
396
|
+
return Math.max(0, Math.min(6, decimals));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* datasetContext.js - the dataset a plugin is handed, client-side.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors plexora/api/dataset.py so a plugin sees the same shape on both
|
|
5
|
+
* sides: image data always, segmentation and a feature table when the project
|
|
6
|
+
* has them, and a role->column map read from the project record.
|
|
7
|
+
*
|
|
8
|
+
* The point is that plugins read ROLES, not column names. `schema.x` resolves
|
|
9
|
+
* to whatever the project recorded for the x role; a plugin that hardcodes
|
|
10
|
+
* "X_centroid" breaks on the next dataset. Roles this version has no field for
|
|
11
|
+
* survive in `extra`, so adding one later needs no plugin change.
|
|
12
|
+
*
|
|
13
|
+
* It also replaces the bare globals plugins used to reach for. csvGatingList
|
|
14
|
+
* read `imageChannels`, `datasource` and `__plexora` straight off window,
|
|
15
|
+
* which meant core could not rename or scope any of them, and a plugin loaded
|
|
16
|
+
* out of order got undefined rather than an error.
|
|
17
|
+
*/
|
|
18
|
+
window.PlexoraDataset = (function () {
|
|
19
|
+
// Role name on the wire -> the camelCase name plugins read. The record
|
|
20
|
+
// stores roles under one explicit key (server/models/project.py's
|
|
21
|
+
// ROLE_NAMES), so unlike the old shape there is no alias list and no
|
|
22
|
+
// denylist -- a role can no longer be confused with a file path or a
|
|
23
|
+
// processing flag, because they are not in the same dict any more.
|
|
24
|
+
const ROLE_KEYS = {
|
|
25
|
+
cell_id: "cellId",
|
|
26
|
+
x: "x",
|
|
27
|
+
y: "y",
|
|
28
|
+
celltype: "celltype",
|
|
29
|
+
image_id: "imageId",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function resolveSchema(config) {
|
|
33
|
+
const spec = config.dataset;
|
|
34
|
+
if (!spec) return null;
|
|
35
|
+
const roles = spec.roles || {};
|
|
36
|
+
const schema = { extra: {} };
|
|
37
|
+
for (const [wire, name] of Object.entries(ROLE_KEYS)) {
|
|
38
|
+
schema[name] = roles[wire] || null;
|
|
39
|
+
}
|
|
40
|
+
// A role the record has learned but this version has no field for
|
|
41
|
+
// still reaches plugins, so adding one needs no client change.
|
|
42
|
+
for (const [wire, value] of Object.entries(roles)) {
|
|
43
|
+
if (!ROLE_KEYS[wire] && typeof value === "string") schema.extra[wire] = value;
|
|
44
|
+
}
|
|
45
|
+
return schema;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param config - this datasource's /config entry
|
|
50
|
+
* @param imageChannels - fullname -> index lookup built by main.js
|
|
51
|
+
* @param databaseDescription - per-column stats, or {} before it arrives
|
|
52
|
+
*/
|
|
53
|
+
function build(config, imageChannels, databaseDescription = {}) {
|
|
54
|
+
const schema = resolveSchema(config);
|
|
55
|
+
|
|
56
|
+
// Reserved columns are numeric but are not markers. Kept in step with
|
|
57
|
+
// the server's TableHandle.markers, which likewise reports only
|
|
58
|
+
// columns a histogram could be built for.
|
|
59
|
+
const reserved = new Set(["id"]);
|
|
60
|
+
[schema?.cellId, schema?.x, schema?.y].forEach((c) => c && reserved.add(c));
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: config.name || window.datasource,
|
|
64
|
+
image: {
|
|
65
|
+
channels: (config.imageData || []).filter((c) => c.fullname !== "Area"),
|
|
66
|
+
get channelNames() {
|
|
67
|
+
return this.channels.map((c) => c.fullname);
|
|
68
|
+
},
|
|
69
|
+
kind: config.image_kind || null,
|
|
70
|
+
index: imageChannels || {},
|
|
71
|
+
/** Whether a marker name is also a real image channel. */
|
|
72
|
+
has(fullName) {
|
|
73
|
+
return (imageChannels || {})[fullName] !== undefined;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
// Getters, not values: a mask whose pyramid was still building when
|
|
77
|
+
// the page opened is adopted in place when the job lands (main.js's
|
|
78
|
+
// adoptSegmentation), and a plugin holding this object from before
|
|
79
|
+
// that would otherwise go on being told there is no mask.
|
|
80
|
+
segmentation: {
|
|
81
|
+
get available() {
|
|
82
|
+
return Boolean(config.segmentation);
|
|
83
|
+
},
|
|
84
|
+
get pending() {
|
|
85
|
+
return config.segmentation_status === "pending";
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
table: {
|
|
89
|
+
// No dataset block IS the image-only state; there is no
|
|
90
|
+
// separate flag that could disagree with it.
|
|
91
|
+
available: Boolean(config.dataset),
|
|
92
|
+
sourceKind: config.dataset?.type || "csv",
|
|
93
|
+
describe: databaseDescription,
|
|
94
|
+
/**
|
|
95
|
+
* Columns a plugin can threshold or plot -- the classification
|
|
96
|
+
* the project recorded at import, so every plugin sees the same
|
|
97
|
+
* answer. Deliberately not image.channelNames: a structural
|
|
98
|
+
* channel like DNA is a real image channel with no feature
|
|
99
|
+
* column, and conflating the two has caused bugs here.
|
|
100
|
+
*
|
|
101
|
+
* Narrowed to columns the server could describe, because that
|
|
102
|
+
* is what "can be thresholded" actually means: a gate needs a
|
|
103
|
+
* range and a histogram to draw, and a recorded marker the
|
|
104
|
+
* loaded table no longer holds has neither.
|
|
105
|
+
*
|
|
106
|
+
* The derivation is the fallback, not the answer. It cannot
|
|
107
|
+
* tell a stain from a measurement -- Area and Eccentricity are
|
|
108
|
+
* as numeric as CD3 is, and in a CSV they sit in the same
|
|
109
|
+
* header, which is the entire reason the import step asks. It
|
|
110
|
+
* covers a project whose columns were never classified; better
|
|
111
|
+
* a usable guess than an empty panel.
|
|
112
|
+
*/
|
|
113
|
+
get markers() {
|
|
114
|
+
const describable = (column) => Boolean(databaseDescription[column]?.histogram);
|
|
115
|
+
const recorded = (config.dataset?.columns?.markers || []).filter(describable);
|
|
116
|
+
if (recorded.length) return recorded;
|
|
117
|
+
return Object.keys(databaseDescription).filter(
|
|
118
|
+
(column) => !reserved.has(column) && describable(column)
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
get metadataColumns() {
|
|
122
|
+
return [...(config.dataset?.columns?.metadata || [])];
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
schema,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Whether this project has per-cell positions to draw.
|
|
131
|
+
*
|
|
132
|
+
* Not the same question as "has a feature table": a table whose coordinate
|
|
133
|
+
* columns nobody has identified yet has no positions either, and the
|
|
134
|
+
* server's centroid manifest reports `missing` for both cases. Callers use
|
|
135
|
+
* it to skip a centroid round trip that was never going to return points.
|
|
136
|
+
*/
|
|
137
|
+
function hasCentroids(config) {
|
|
138
|
+
const roles = config?.dataset?.roles;
|
|
139
|
+
return Boolean(roles && roles.x && roles.y);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { build, resolveSchema, hasCentroids };
|
|
143
|
+
})();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGL2 tile-colorization engine.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately OpenSeadragon-independent: this module is only the WebGL
|
|
5
|
+
* rendering engine, so the app can run against upstream OpenSeadragon rather
|
|
6
|
+
* than a fork pinned to a patched commit. The OpenSeadragon integration
|
|
7
|
+
* (tile-loaded/tile-drawing wiring) lives in imageViewer.js rather than a
|
|
8
|
+
* separate glue class, since imageViewer.js already overrides most of that
|
|
9
|
+
* layer's behavior.
|
|
10
|
+
*/
|
|
11
|
+
export class GLRenderer {
|
|
12
|
+
constructor(incoming) {
|
|
13
|
+
// Custom hook points, callable via `this['gl-drawing']`/`this['gl-loaded']`
|
|
14
|
+
this['gl-drawing'] = function (e) { return e; };
|
|
15
|
+
this['gl-loaded'] = function (e) { return e; };
|
|
16
|
+
|
|
17
|
+
// Vertex input buffer describing a full-canvas quad
|
|
18
|
+
this.one_point_size = 2 * Float32Array.BYTES_PER_ELEMENT;
|
|
19
|
+
this.points_list_size = 4 * this.one_point_size;
|
|
20
|
+
this.points_buffer = new Float32Array([
|
|
21
|
+
0, 1, 0, 0, 1, 1, 1, 0,
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
// Offscreen WebGL2 context and texture/buffer handles
|
|
25
|
+
this.gl = document.createElement('canvas').getContext('webgl2');
|
|
26
|
+
this.texture = this.gl.createTexture();
|
|
27
|
+
this.buffer = this.gl.createBuffer();
|
|
28
|
+
|
|
29
|
+
// Placeholder defaults, overwritten by the caller before init()
|
|
30
|
+
this.vShader = 'vShader.glsl';
|
|
31
|
+
this.fShader = 'fShader.glsl';
|
|
32
|
+
this.height = 0;
|
|
33
|
+
this.width = 0;
|
|
34
|
+
|
|
35
|
+
for (const key in incoming) {
|
|
36
|
+
this[key] = incoming[key];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
init() {
|
|
41
|
+
const step = [[this.vShader, this.fShader].map(this.getter)];
|
|
42
|
+
step.push(this.toProgram.bind(this), this.toBuffers.bind(this));
|
|
43
|
+
return Promise.all(step[0]).then(step[1]).then(step[2]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
updateShape(width, height) {
|
|
47
|
+
this.width = width;
|
|
48
|
+
this.height = height;
|
|
49
|
+
this.gl.canvas.width = width;
|
|
50
|
+
this.gl.canvas.height = height;
|
|
51
|
+
this.gl.viewport(0, 0, width, height);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Fetch a shader source file (or pass through a non-.glsl value unchanged)
|
|
55
|
+
getter(where) {
|
|
56
|
+
return new Promise((done) => {
|
|
57
|
+
if (where.slice(-4) != 'glsl') {
|
|
58
|
+
return done(where);
|
|
59
|
+
}
|
|
60
|
+
const bid = new XMLHttpRequest();
|
|
61
|
+
const win = function () {
|
|
62
|
+
if (bid.status == 200) {
|
|
63
|
+
return done(bid.response);
|
|
64
|
+
}
|
|
65
|
+
return done(where);
|
|
66
|
+
};
|
|
67
|
+
bid.open('GET', where, true);
|
|
68
|
+
bid.setRequestHeader('Cache-Control', 'no-cache, no-store, max-age=0');
|
|
69
|
+
bid.onerror = bid.onload = win;
|
|
70
|
+
bid.send();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Compile and link the vertex/fragment shader pair
|
|
75
|
+
toProgram(files) {
|
|
76
|
+
const gl = this.gl;
|
|
77
|
+
const program = gl.createProgram();
|
|
78
|
+
const ok = function (kind, status, value, sh) {
|
|
79
|
+
if (!gl['get' + kind + 'Parameter'](value, gl[status + '_STATUS'])) {
|
|
80
|
+
console.log((sh || 'LINK') + ':\n' + gl['get' + kind + 'InfoLog'](value));
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
};
|
|
84
|
+
files.map((given, i) => {
|
|
85
|
+
const sh = ['VERTEX_SHADER', 'FRAGMENT_SHADER'][i];
|
|
86
|
+
const shader = gl.createShader(gl[sh]);
|
|
87
|
+
gl.shaderSource(shader, given);
|
|
88
|
+
gl.compileShader(shader);
|
|
89
|
+
gl.attachShader(program, shader);
|
|
90
|
+
ok('Shader', 'COMPILE', shader, sh);
|
|
91
|
+
});
|
|
92
|
+
gl.linkProgram(program);
|
|
93
|
+
return ok('Program', 'LINK', program);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Bind the quad buffer, texture parameters, and uniforms
|
|
97
|
+
toBuffers(program) {
|
|
98
|
+
const gl = this.gl;
|
|
99
|
+
gl.useProgram(program);
|
|
100
|
+
this['gl-loaded'].call(this, program);
|
|
101
|
+
|
|
102
|
+
const u_tile = gl.getUniformLocation(program, 'u_tile');
|
|
103
|
+
const a_uv = gl.getAttribLocation(program, 'a_uv');
|
|
104
|
+
const u8 = gl.getUniformLocation(program, 'u8');
|
|
105
|
+
|
|
106
|
+
gl.uniform1ui(u8, 255);
|
|
107
|
+
gl.uniform1i(u_tile, 0);
|
|
108
|
+
|
|
109
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
110
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.points_buffer, gl.STATIC_DRAW);
|
|
111
|
+
|
|
112
|
+
gl.enableVertexAttribArray(a_uv);
|
|
113
|
+
gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, 0, this.one_point_size, 0 * this.points_list_size);
|
|
114
|
+
|
|
115
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
116
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
117
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
|
|
118
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
119
|
+
|
|
120
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
121
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
122
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
123
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Default single-texture draw path. imageViewer.js overrides this with
|
|
127
|
+
// its own multi-texture-cache implementation; kept here for fidelity/reuse.
|
|
128
|
+
loadArray(width, height, pixels, format = 'u16') {
|
|
129
|
+
this['gl-drawing'].call(this);
|
|
130
|
+
const gl = this.gl;
|
|
131
|
+
|
|
132
|
+
if (format == 'u16') {
|
|
133
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG8UI, width, height, 0, gl.RG_INTEGER, gl.UNSIGNED_BYTE, pixels);
|
|
134
|
+
} else if (format == 'u32') {
|
|
135
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, width, height, 0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, pixels);
|
|
136
|
+
} else if (format == 'u8') {
|
|
137
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8UI, width, height, 0, gl.RED_INTEGER, gl.UNSIGNED_BYTE, pixels);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
141
|
+
return this.gl.canvas;
|
|
142
|
+
}
|
|
143
|
+
}
|