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.
Files changed (248) hide show
  1. plexora/__init__.py +129 -0
  2. plexora/__main__.py +15 -0
  3. plexora/_url.py +105 -0
  4. plexora/api/__init__.py +55 -0
  5. plexora/api/dataset.py +398 -0
  6. plexora/api/http.py +19 -0
  7. plexora/api/plugin.py +541 -0
  8. plexora/api/store.py +221 -0
  9. plexora/cli.py +777 -0
  10. plexora/client/dist/354_bundle.js +2 -0
  11. plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
  12. plexora/client/dist/418_bundle.js +2 -0
  13. plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
  14. plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
  15. plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
  16. plexora/client/dist/770_bundle.js +1 -0
  17. plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
  18. plexora/client/dist/vendor_bundle.js +2 -0
  19. plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
  20. plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
  21. plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
  22. plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
  23. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
  24. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
  25. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
  26. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
  27. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
  28. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
  29. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
  30. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
  31. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
  32. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
  33. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
  34. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
  35. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
  36. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
  37. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
  38. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
  39. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
  40. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
  41. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
  42. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
  43. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
  44. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
  45. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
  46. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
  47. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
  48. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
  49. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
  50. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
  51. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
  52. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
  53. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
  54. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
  55. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
  56. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
  57. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
  58. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
  59. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
  60. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
  61. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
  62. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
  63. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
  64. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
  65. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
  66. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
  67. plexora/client/src/css/import.css +474 -0
  68. plexora/client/src/css/main.css +903 -0
  69. plexora/client/src/css/openProject.css +363 -0
  70. plexora/client/src/css/quickView.css +166 -0
  71. plexora/client/src/css/tokens.css +51 -0
  72. plexora/client/src/css/viewer.css +1655 -0
  73. plexora/client/src/img/apple-touch-icon.png +0 -0
  74. plexora/client/src/img/favicon.ico +0 -0
  75. plexora/client/src/img/logo.ai +8892 -33
  76. plexora/client/src/img/logo.svg +1 -0
  77. plexora/client/src/img/logo_with_text.ai +9011 -34
  78. plexora/client/src/img/logo_with_text.svg +1 -0
  79. plexora/client/src/js/main.js +704 -0
  80. plexora/client/src/js/pluginRegistry.js +108 -0
  81. plexora/client/src/js/services/appStatus.js +286 -0
  82. plexora/client/src/js/services/browsePicker.js +73 -0
  83. plexora/client/src/js/services/dataLayer.js +400 -0
  84. plexora/client/src/js/services/datasetContext.js +143 -0
  85. plexora/client/src/js/services/glRenderer.js +143 -0
  86. plexora/client/src/js/services/importFormValidation.js +257 -0
  87. plexora/client/src/js/services/numericData.js +100 -0
  88. plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
  89. plexora/client/src/js/services/simpleEventHandler.js +25 -0
  90. plexora/client/src/js/vendor.js +29 -0
  91. plexora/client/src/js/views/channelList.js +663 -0
  92. plexora/client/src/js/views/colorSwatchPicker.js +202 -0
  93. plexora/client/src/js/views/columnClassifier.js +146 -0
  94. plexora/client/src/js/views/coordinateField.js +133 -0
  95. plexora/client/src/js/views/dataSourceField.js +276 -0
  96. plexora/client/src/js/views/imageViewer.js +3306 -0
  97. plexora/client/src/js/views/miniMap.js +787 -0
  98. plexora/client/src/js/views/navbarControls.js +178 -0
  99. plexora/client/src/js/views/openProjectPage.js +239 -0
  100. plexora/client/src/js/views/projectEdit.js +335 -0
  101. plexora/client/src/js/views/quickViewLanding.js +136 -0
  102. plexora/client/src/js/views/rainbow.js +326 -0
  103. plexora/client/src/js/views/requirementsModal.js +572 -0
  104. plexora/client/src/js/views/rgbImageViewer.js +115 -0
  105. plexora/client/src/js/views/searchableSelect.js +411 -0
  106. plexora/client/src/js/views/segmentationProgress.js +122 -0
  107. plexora/client/src/js/views/toolLoader.js +747 -0
  108. plexora/client/src/js/views/viewerControls.js +689 -0
  109. plexora/client/src/js/views/viewerManager.js +434 -0
  110. plexora/client/src/js/views/viewerSidebar.js +1117 -0
  111. plexora/client/src/js/workers/tileDecoder.js +130 -0
  112. plexora/client/src/shaders/frag.glsl +528 -0
  113. plexora/client/src/shaders/vert.glsl +12 -0
  114. plexora/client/templates/base.html +212 -0
  115. plexora/client/templates/index.html +238 -0
  116. plexora/client/templates/open_project.html +96 -0
  117. plexora/client/templates/project_columns.html +86 -0
  118. plexora/client/templates/project_edit.html +196 -0
  119. plexora/client/templates/upload.html +118 -0
  120. plexora/connect.py +528 -0
  121. plexora/datasource.py +782 -0
  122. plexora/jupyter.py +406 -0
  123. plexora/notebook_env.py +232 -0
  124. plexora/paths.py +486 -0
  125. plexora/plugins/__init__.py +12 -0
  126. plexora/plugins/cell_explorer/__init__.py +100 -0
  127. plexora/plugins/cell_explorer/server/__init__.py +11 -0
  128. plexora/plugins/cell_explorer/server/routes.py +167 -0
  129. plexora/plugins/cell_explorer/server/state.py +281 -0
  130. plexora/plugins/cell_explorer/server/values.py +145 -0
  131. plexora/plugins/cell_explorer/server/variables.py +367 -0
  132. plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
  133. plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
  134. plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
  135. plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
  136. plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
  137. plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
  138. plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
  139. plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
  140. plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
  141. plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
  142. plexora/plugins/figure_builder/__init__.py +125 -0
  143. plexora/plugins/figure_builder/server/__init__.py +7 -0
  144. plexora/plugins/figure_builder/server/compose.py +365 -0
  145. plexora/plugins/figure_builder/server/export.py +784 -0
  146. plexora/plugins/figure_builder/server/export_jobs.py +148 -0
  147. plexora/plugins/figure_builder/server/operations.py +556 -0
  148. plexora/plugins/figure_builder/server/pixels.py +142 -0
  149. plexora/plugins/figure_builder/server/provenance.py +174 -0
  150. plexora/plugins/figure_builder/server/render.py +278 -0
  151. plexora/plugins/figure_builder/server/repository.py +701 -0
  152. plexora/plugins/figure_builder/server/routes.py +594 -0
  153. plexora/plugins/figure_builder/server/schema.py +1023 -0
  154. plexora/plugins/figure_builder/server/sources.py +120 -0
  155. plexora/plugins/figure_builder/server/textmetrics.py +155 -0
  156. plexora/plugins/figure_builder/static/figureActions.js +363 -0
  157. plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
  158. plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
  159. plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
  160. plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
  161. plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
  162. plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
  163. plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
  164. plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
  165. plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
  166. plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
  167. plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
  168. plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
  169. plexora/plugins/figure_builder/static/figureRichText.js +634 -0
  170. plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
  171. plexora/plugins/figure_builder/static/figureSchema.js +218 -0
  172. plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
  173. plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
  174. plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
  175. plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
  176. plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
  177. plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
  178. plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
  179. plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
  180. plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
  181. plexora/plugins/gating/__init__.py +76 -0
  182. plexora/plugins/gating/server/__init__.py +0 -0
  183. plexora/plugins/gating/server/anndata_gates.py +367 -0
  184. plexora/plugins/gating/server/database.py +16 -0
  185. plexora/plugins/gating/server/model.py +320 -0
  186. plexora/plugins/gating/server/routes.py +235 -0
  187. plexora/plugins/gating/static/csvGatingList.js +947 -0
  188. plexora/plugins/gating/static/gating.css +289 -0
  189. plexora/plugins/gating/static/gatingApi.js +221 -0
  190. plexora/plugins/gating/static/gatingSidebarController.js +513 -0
  191. plexora/plugins/gating/templates/gating/legacy.html +26 -0
  192. plexora/plugins/gating/templates/gating/panel.html +69 -0
  193. plexora/plugins/roi/__init__.py +95 -0
  194. plexora/plugins/roi/server/__init__.py +8 -0
  195. plexora/plugins/roi/server/adapters.py +655 -0
  196. plexora/plugins/roi/server/geojson.py +267 -0
  197. plexora/plugins/roi/server/geometry.py +181 -0
  198. plexora/plugins/roi/server/mapping.py +169 -0
  199. plexora/plugins/roi/server/operations.py +293 -0
  200. plexora/plugins/roi/server/repository.py +215 -0
  201. plexora/plugins/roi/server/routes.py +409 -0
  202. plexora/plugins/roi/server/schema.py +323 -0
  203. plexora/plugins/roi/static/roi.css +583 -0
  204. plexora/plugins/roi/static/roiApi.js +177 -0
  205. plexora/plugins/roi/static/roiFigureBridge.js +107 -0
  206. plexora/plugins/roi/static/roiGeometry.js +350 -0
  207. plexora/plugins/roi/static/roiRenderer.js +278 -0
  208. plexora/plugins/roi/static/roiSidebarController.js +1038 -0
  209. plexora/plugins/roi/static/roiState.js +518 -0
  210. plexora/plugins/roi/static/roiTools.js +993 -0
  211. plexora/plugins/roi/templates/roi/panel.html +240 -0
  212. plexora/proxy.py +57 -0
  213. plexora/server/models/adapters/__init__.py +94 -0
  214. plexora/server/models/adapters/anndata_adapter.py +388 -0
  215. plexora/server/models/adapters/base.py +93 -0
  216. plexora/server/models/adapters/classify.py +192 -0
  217. plexora/server/models/adapters/csv_adapter.py +105 -0
  218. plexora/server/models/adapters/inspection.py +265 -0
  219. plexora/server/models/adapters/spatialdata_adapter.py +248 -0
  220. plexora/server/models/centroid_tiles.py +305 -0
  221. plexora/server/models/data_model.py +1659 -0
  222. plexora/server/models/database_model.py +169 -0
  223. plexora/server/models/project.py +1270 -0
  224. plexora/server/plugins.py +199 -0
  225. plexora/server/routes/browse_routes.py +41 -0
  226. plexora/server/routes/data_routes.py +294 -0
  227. plexora/server/routes/import_routes.py +603 -0
  228. plexora/server/routes/page_routes.py +139 -0
  229. plexora/server/routes/project_routes.py +438 -0
  230. plexora/server/routes/quick_view_routes.py +62 -0
  231. plexora/server/routes/system_routes.py +39 -0
  232. plexora/server/routes/tool_routes.py +418 -0
  233. plexora/server/utils/addHEColumns.py +11 -0
  234. plexora/server/utils/fast_png.py +97 -0
  235. plexora/server/utils/fullConversion.py +50 -0
  236. plexora/server/utils/native_dialog.py +168 -0
  237. plexora/server/utils/pre_normalization.py +47 -0
  238. plexora/server/utils/segmentation_pyramid.py +598 -0
  239. plexora/server/utils/smallestenclosingcircle.py +126 -0
  240. plexora/server/utils/tiffsurgeon.py +372 -0
  241. plexora/server_cli.py +48 -0
  242. plexora/test.py +0 -0
  243. plexora-0.0.1.dist-info/METADATA +341 -0
  244. plexora-0.0.1.dist-info/RECORD +248 -0
  245. plexora-0.0.1.dist-info/WHEEL +5 -0
  246. plexora-0.0.1.dist-info/entry_points.txt +12 -0
  247. plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
  248. 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
+ };