├── LICENSE ├── Node_Based_Shader_Editor_for_Volume_Rendering_in_WebGL.pdf ├── README.md ├── css ├── external │ ├── litegraph.css │ └── w2ui-1.5.rc1.css └── style.css ├── datasets └── ct-torax │ ├── image-00000.dcm │ ├── image-00001.dcm │ ├── image-00002.dcm │ ├── image-00003.dcm │ ├── image-00004.dcm │ ├── image-00005.dcm │ ├── image-00006.dcm │ ├── image-00007.dcm │ ├── image-00008.dcm │ ├── image-00009.dcm │ ├── image-00010.dcm │ ├── image-00011.dcm │ ├── image-00012.dcm │ ├── image-00013.dcm │ ├── image-00014.dcm │ ├── image-00015.dcm │ ├── image-00016.dcm │ ├── image-00017.dcm │ ├── image-00018.dcm │ ├── image-00019.dcm │ ├── image-00020.dcm │ ├── image-00021.dcm │ ├── image-00022.dcm │ ├── image-00023.dcm │ ├── image-00024.dcm │ ├── image-00025.dcm │ ├── image-00026.dcm │ ├── image-00027.dcm │ ├── image-00028.dcm │ ├── image-00029.dcm │ ├── image-00030.dcm │ ├── image-00031.dcm │ ├── image-00032.dcm │ ├── image-00033.dcm │ ├── image-00034.dcm │ ├── image-00035.dcm │ ├── image-00036.dcm │ ├── image-00037.dcm │ ├── image-00038.dcm │ ├── image-00039.dcm │ ├── image-00040.dcm │ ├── image-00041.dcm │ ├── image-00042.dcm │ ├── image-00043.dcm │ ├── image-00044.dcm │ ├── image-00045.dcm │ ├── image-00046.dcm │ ├── image-00047.dcm │ ├── image-00048.dcm │ ├── image-00049.dcm │ ├── image-00050.dcm │ ├── image-00051.dcm │ ├── image-00052.dcm │ ├── image-00053.dcm │ ├── image-00054.dcm │ ├── image-00055.dcm │ ├── image-00056.dcm │ ├── image-00057.dcm │ ├── image-00058.dcm │ ├── image-00059.dcm │ ├── image-00060.dcm │ ├── image-00061.dcm │ ├── image-00062.dcm │ ├── image-00063.dcm │ ├── image-00064.dcm │ ├── image-00065.dcm │ ├── image-00066.dcm │ ├── image-00067.dcm │ ├── image-00068.dcm │ ├── image-00069.dcm │ ├── image-00070.dcm │ ├── image-00071.dcm │ ├── image-00072.dcm │ ├── image-00073.dcm │ ├── image-00074.dcm │ ├── image-00075.dcm │ ├── image-00076.dcm │ ├── image-00077.dcm │ ├── image-00078.dcm │ ├── image-00079.dcm │ ├── image-00080.dcm │ ├── image-00081.dcm │ ├── image-00082.dcm │ ├── image-00083.dcm │ ├── image-00084.dcm │ ├── image-00085.dcm │ ├── image-00086.dcm │ ├── image-00087.dcm │ ├── image-00088.dcm │ ├── image-00089.dcm │ ├── image-00090.dcm │ ├── image-00091.dcm │ ├── image-00092.dcm │ ├── image-00093.dcm │ ├── image-00094.dcm │ ├── image-00095.dcm │ ├── image-00096.dcm │ ├── image-00097.dcm │ ├── image-00098.dcm │ ├── image-00099.dcm │ ├── image-00100.dcm │ ├── image-00101.dcm │ ├── image-00102.dcm │ ├── image-00103.dcm │ ├── image-00104.dcm │ ├── image-00105.dcm │ ├── image-00106.dcm │ ├── image-00107.dcm │ ├── image-00108.dcm │ ├── image-00109.dcm │ ├── image-00110.dcm │ ├── image-00111.dcm │ ├── image-00112.dcm │ ├── image-00113.dcm │ ├── image-00114.dcm │ ├── image-00115.dcm │ ├── image-00116.dcm │ ├── image-00117.dcm │ ├── image-00118.dcm │ ├── image-00119.dcm │ ├── image-00120.dcm │ ├── image-00121.dcm │ ├── image-00122.dcm │ ├── image-00123.dcm │ ├── image-00124.dcm │ ├── image-00125.dcm │ ├── image-00126.dcm │ ├── image-00127.dcm │ ├── image-00128.dcm │ ├── image-00129.dcm │ ├── image-00130.dcm │ ├── image-00131.dcm │ ├── image-00132.dcm │ ├── image-00133.dcm │ ├── image-00134.dcm │ ├── image-00135.dcm │ ├── image-00136.dcm │ ├── image-00137.dcm │ ├── image-00138.dcm │ ├── image-00139.dcm │ ├── image-00140.dcm │ ├── image-00141.dcm │ ├── image-00142.dcm │ ├── image-00143.dcm │ ├── image-00144.dcm │ ├── image-00145.dcm │ ├── image-00146.dcm │ ├── image-00147.dcm │ ├── image-00148.dcm │ ├── image-00149.dcm │ ├── image-00150.dcm │ ├── image-00151.dcm │ ├── image-00152.dcm │ ├── image-00153.dcm │ ├── image-00154.dcm │ ├── image-00155.dcm │ ├── image-00156.dcm │ ├── image-00157.dcm │ ├── image-00158.dcm │ ├── image-00159.dcm │ ├── image-00160.dcm │ ├── image-00161.dcm │ ├── image-00162.dcm │ ├── image-00163.dcm │ ├── image-00164.dcm │ ├── image-00165.dcm │ ├── image-00166.dcm │ ├── image-00167.dcm │ ├── image-00168.dcm │ ├── image-00169.dcm │ ├── image-00170.dcm │ ├── image-00171.dcm │ ├── image-00172.dcm │ ├── image-00173.dcm │ ├── image-00174.dcm │ ├── image-00175.dcm │ ├── image-00176.dcm │ ├── image-00177.dcm │ ├── image-00178.dcm │ ├── image-00179.dcm │ ├── image-00180.dcm │ ├── image-00181.dcm │ ├── image-00182.dcm │ ├── image-00183.dcm │ ├── image-00184.dcm │ ├── image-00185.dcm │ ├── image-00186.dcm │ ├── image-00187.dcm │ ├── image-00188.dcm │ ├── image-00189.dcm │ ├── image-00190.dcm │ ├── image-00191.dcm │ ├── image-00192.dcm │ ├── image-00193.dcm │ └── image-00194.dcm ├── demos ├── demo_case1.html ├── demo_case1.js ├── demo_case2.html └── demo_case2.js ├── img ├── migrid.png └── readme_examples │ ├── example_clouds.PNG │ └── example_torax.PNG ├── index.html ├── js ├── external │ ├── daikon.js │ ├── gl-matrix.js │ ├── jquery-3.5.1.min.js │ ├── jscolor │ │ ├── arrow.gif │ │ ├── cross.gif │ │ ├── demo.html │ │ ├── hs.png │ │ ├── hv.png │ │ └── jscolor.js │ ├── litegl.js │ ├── litegraph.js │ ├── rendeer.js │ ├── volume-base.js │ ├── volume-loader.js │ └── w2ui-1.5.rc1.js ├── sceneTools.js ├── utils.js └── volumeNodes.js ├── main.js ├── readme.html └── shaders.glsl /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Víctor Ubieto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Node_Based_Shader_Editor_for_Volume_Rendering_in_WebGL.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/Node_Based_Shader_Editor_for_Volume_Rendering_in_WebGL.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node-Based Shader Editor 2 | 3 | This is the result of my final degree project, which consist on a node-based shader editor implemented on web. The framework is based mainly on the usage of this libraries: 4 | 5 | * [Litegl.js](https://github.com/jagenjo/litegl.js) - It helps simplifying working with WebGL. 6 | * [Litegraph.js](https://github.com/jagenjo/litegraph.js) - A library in Javascript to create graphs in the browser. 7 | 8 | ## Functionalities 9 | 10 | This is the list of the most important functionalities of the application. 11 | 12 | * Manipulation of the Volume Render algorithm via nodes. 13 | * Load and visualization of Dicom datasets. In the case you don't have any but you want to try it, I uploaded an anonymized dataset in the repository. More info [here](https://www.dicomlibrary.com/). 14 | * Manipulation of the datasets by editing the transfer function. 15 | * Download of the Vertex Shader and Fragment Shader createds by the graph editor. 16 | * Try an online demo [here](https://victorubieto.github.io/graph_system/). 17 | 18 | ## List of Nodes 19 | 20 | This is the list of the nodes currently available. There are more comming soon, so stay tunned. 21 | 22 | * **Input:** Number, Color, Coordinates. 23 | * **Texture:** Gradient, Noise, Dicom, Transfer Function. 24 | * **Operator:** Math, MixRGB, ColorRamp, Translate, Scale, Rotate, Separate, Combine. 25 | * **Shader:** Volume. 26 | * **Output:** Material Output. 27 | 28 | ## Other libraries used 29 | 30 | * [W2ui.js](https://github.com/vitmalina/w2ui) 31 | * [JsColor.js](https://github.com/EastDesire/jscolor) 32 | * [Rendeer.js](https://github.com/jagenjo/rendeer.js) 33 | * [Volume-base.js](https://github.com/upf-gti/Volumetrics/blob/master/src/volume-base.js) 34 | * [Volume-loader.js](https://github.com/upf-gti/Volumetrics/blob/master/src/volume-loader.js) 35 | * [JQuery.js](https://github.com/jquery/jquery) 36 | * [Gl-Matrix.js](https://github.com/toji/gl-matrix) 37 | * [Daikon.js](https://github.com/rii-mango/Daikon) 38 | 39 | ## Examples 40 | 41 | Clouds Modeling 42 | ![alt text](https://github.com/victorubieto/graph_system/blob/master/img/readme_examples/example_clouds.PNG?raw=true) 43 | 44 | Dicom Visualization 45 | ![alt text](https://github.com/victorubieto/graph_system/blob/master/img/readme_examples/example_torax.PNG?raw=true) -------------------------------------------------------------------------------- /css/external/litegraph.css: -------------------------------------------------------------------------------- 1 | /* this CSS contains only the basic CSS needed to run the app and use it */ 2 | 3 | .lgraphcanvas { 4 | /*cursor: crosshair;*/ 5 | user-select: none; 6 | -moz-user-select: none; 7 | -webkit-user-select: none; 8 | } 9 | 10 | .litegraph.litecontextmenu { 11 | font-family: Tahoma, sans-serif; 12 | position: fixed; 13 | top: 100px; 14 | left: 100px; 15 | min-width: 100px; 16 | color: #aaf; 17 | padding: 0; 18 | box-shadow: 0 0 10px black !important; 19 | background-color: #2e2e2e !important; 20 | z-index: 10; 21 | } 22 | 23 | .litegraph.litecontextmenu.dark { 24 | background-color: #000 !important; 25 | } 26 | 27 | .litegraph.litecontextmenu .litemenu-title img { 28 | margin-top: 2px; 29 | margin-left: 2px; 30 | margin-right: 4px; 31 | } 32 | 33 | .litegraph.litecontextmenu .litemenu-entry { 34 | margin: 2px; 35 | padding: 2px; 36 | } 37 | 38 | .litegraph.litecontextmenu .litemenu-entry.submenu { 39 | background-color: #2e2e2e !important; 40 | } 41 | 42 | .litegraph.litecontextmenu.dark .litemenu-entry.submenu { 43 | background-color: #000 !important; 44 | } 45 | 46 | .litegraph .litemenubar ul { 47 | font-family: Tahoma, sans-serif; 48 | margin: 0; 49 | padding: 0; 50 | } 51 | 52 | .litegraph .litemenubar li { 53 | font-size: 14px; 54 | color: #999; 55 | display: inline-block; 56 | min-width: 50px; 57 | padding-left: 10px; 58 | padding-right: 10px; 59 | user-select: none; 60 | -moz-user-select: none; 61 | -webkit-user-select: none; 62 | cursor: pointer; 63 | } 64 | 65 | .litegraph .litemenubar li:hover { 66 | background-color: #777; 67 | color: #eee; 68 | } 69 | 70 | .litegraph .litegraph .litemenubar-panel { 71 | position: absolute; 72 | top: 5px; 73 | left: 5px; 74 | min-width: 100px; 75 | background-color: #444; 76 | box-shadow: 0 0 3px black; 77 | padding: 4px; 78 | border-bottom: 2px solid #aaf; 79 | z-index: 10; 80 | } 81 | 82 | .litegraph .litemenu-entry, 83 | .litemenu-title { 84 | font-size: 12px; 85 | color: #aaa; 86 | padding: 0 0 0 4px; 87 | margin: 2px; 88 | padding-left: 2px; 89 | -moz-user-select: none; 90 | -webkit-user-select: none; 91 | user-select: none; 92 | cursor: pointer; 93 | } 94 | 95 | .litegraph .litemenu-entry .icon { 96 | display: inline-block; 97 | width: 12px; 98 | height: 12px; 99 | margin: 2px; 100 | vertical-align: top; 101 | } 102 | 103 | .litegraph .litemenu-entry.checked .icon { 104 | background-color: #aaf; 105 | } 106 | 107 | .litegraph .litemenu-entry .more { 108 | float: right; 109 | padding-right: 5px; 110 | } 111 | 112 | .litegraph .litemenu-entry.disabled { 113 | opacity: 0.5; 114 | cursor: default; 115 | } 116 | 117 | .litegraph .litemenu-entry.separator { 118 | display: block; 119 | border-top: 1px solid #333; 120 | border-bottom: 1px solid #666; 121 | width: 100%; 122 | height: 0px; 123 | margin: 3px 0 2px 0; 124 | background-color: transparent; 125 | padding: 0 !important; 126 | cursor: default !important; 127 | } 128 | 129 | .litegraph .litemenu-entry.has_submenu { 130 | border-right: 2px solid cyan; 131 | } 132 | 133 | .litegraph .litemenu-title { 134 | color: #dde; 135 | background-color: #111; 136 | margin: 0; 137 | padding: 2px; 138 | cursor: default; 139 | } 140 | 141 | .litegraph .litemenu-entry:hover:not(.disabled):not(.separator) { 142 | background-color: #444 !important; 143 | color: #eee; 144 | transition: all 0.2s; 145 | } 146 | 147 | .litegraph .litemenu-entry .property_name { 148 | display: inline-block; 149 | text-align: left; 150 | min-width: 80px; 151 | min-height: 1.2em; 152 | } 153 | 154 | .litegraph .litemenu-entry .property_value { 155 | display: inline-block; 156 | background-color: rgba(0, 0, 0, 0.5); 157 | text-align: right; 158 | min-width: 80px; 159 | min-height: 1.2em; 160 | vertical-align: middle; 161 | padding-right: 10px; 162 | } 163 | 164 | .litegraph.litesearchbox { 165 | font-family: Tahoma, sans-serif; 166 | position: absolute; 167 | background-color: rgba(0, 0, 0, 0.5); 168 | padding-top: 4px; 169 | } 170 | 171 | .litegraph.litesearchbox input, 172 | .litegraph.litesearchbox select { 173 | margin-top: 3px; 174 | min-width: 60px; 175 | min-height: 1.5em; 176 | background-color: black; 177 | border: 0; 178 | color: white; 179 | padding-left: 10px; 180 | margin-right: 5px; 181 | } 182 | 183 | .litegraph.litesearchbox .name { 184 | display: inline-block; 185 | min-width: 60px; 186 | min-height: 1.5em; 187 | padding-left: 10px; 188 | } 189 | 190 | .litegraph.litesearchbox .helper { 191 | overflow: auto; 192 | max-height: 200px; 193 | margin-top: 2px; 194 | } 195 | 196 | .litegraph.lite-search-item { 197 | font-family: Tahoma, sans-serif; 198 | background-color: rgba(0, 0, 0, 0.5); 199 | color: white; 200 | padding-top: 2px; 201 | } 202 | 203 | .litegraph.lite-search-item:hover, 204 | .litegraph.lite-search-item.selected { 205 | cursor: pointer; 206 | background-color: white; 207 | color: black; 208 | } 209 | 210 | /* OLD */ 211 | 212 | .graphcontextmenu { 213 | padding: 4px; 214 | min-width: 100px; 215 | } 216 | 217 | .graphcontextmenu-title { 218 | color: #dde; 219 | background-color: #222; 220 | margin: 0; 221 | padding: 2px; 222 | cursor: default; 223 | } 224 | 225 | .graphmenu-entry { 226 | box-sizing: border-box; 227 | margin: 2px; 228 | padding-left: 20px; 229 | user-select: none; 230 | -moz-user-select: none; 231 | -webkit-user-select: none; 232 | transition: all linear 0.3s; 233 | } 234 | 235 | .graphmenu-entry.event, 236 | .litemenu-entry.event { 237 | border-left: 8px solid orange; 238 | padding-left: 12px; 239 | } 240 | 241 | .graphmenu-entry.disabled { 242 | opacity: 0.3; 243 | } 244 | 245 | .graphmenu-entry.submenu { 246 | border-right: 2px solid #eee; 247 | } 248 | 249 | .graphmenu-entry:hover { 250 | background-color: #555; 251 | } 252 | 253 | .graphmenu-entry.separator { 254 | background-color: #111; 255 | border-bottom: 1px solid #666; 256 | height: 1px; 257 | width: calc(100% - 20px); 258 | -moz-width: calc(100% - 20px); 259 | -webkit-width: calc(100% - 20px); 260 | } 261 | 262 | .graphmenu-entry .property_name { 263 | display: inline-block; 264 | text-align: left; 265 | min-width: 80px; 266 | min-height: 1.2em; 267 | } 268 | 269 | .graphmenu-entry .property_value, 270 | .litemenu-entry .property_value { 271 | display: inline-block; 272 | background-color: rgba(0, 0, 0, 0.5); 273 | text-align: right; 274 | min-width: 80px; 275 | min-height: 1.2em; 276 | vertical-align: middle; 277 | padding-right: 10px; 278 | } 279 | 280 | .graphdialog { 281 | position: absolute; 282 | top: 10px; 283 | left: 10px; 284 | min-height: 2em; 285 | background-color: #333; 286 | font-size: 1.2em; 287 | box-shadow: 0 0 10px black !important; 288 | z-index: 10; 289 | } 290 | 291 | .graphdialog.rounded { 292 | border-radius: 12px; 293 | padding-right: 2px; 294 | } 295 | 296 | .graphdialog .name { 297 | display: inline-block; 298 | min-width: 60px; 299 | min-height: 1.5em; 300 | padding-left: 10px; 301 | } 302 | 303 | .graphdialog input, 304 | .graphdialog select { 305 | margin: 3px; 306 | min-width: 60px; 307 | min-height: 1.5em; 308 | background-color: black; 309 | border: 0; 310 | color: white; 311 | padding-left: 10px; 312 | outline: none; 313 | } 314 | 315 | .graphdialog button { 316 | margin-top: 3px; 317 | vertical-align: top; 318 | } 319 | 320 | .graphdialog button.rounded, 321 | .graphdialog input.rounded { 322 | border-radius: 0 12px 12px 0; 323 | } 324 | 325 | .graphdialog .helper { 326 | overflow: auto; 327 | max-height: 200px; 328 | } 329 | 330 | .graphdialog .help-item { 331 | padding-left: 10px; 332 | } 333 | 334 | .graphdialog .help-item:hover, 335 | .graphdialog .help-item.selected { 336 | cursor: pointer; 337 | background-color: white; 338 | color: black; 339 | } 340 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | /* Víctor Ubieto 2020 */ 2 | /* this CSS defines the style of the app interface */ 3 | 4 | * { padding: 0; margin: 0; font-family: Arial, Helvetica, sans-serif; overflow: hidden;} 5 | 6 | html, body { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | #main { 12 | height: calc(100% - 25px); 13 | width: 100%; 14 | } 15 | 16 | #top-bar { 17 | height: 25px; 18 | width: 100%; 19 | padding: 6px; 20 | 21 | background: #1d1d1d; 22 | } 23 | 24 | #top-bar .text { 25 | color: #e4e4e4; 26 | padding-top: 3px; 27 | } 28 | 29 | #top-bar .button { 30 | width: 100px; 31 | height: 25px; 32 | border-radius: 2px; 33 | 34 | border: none; 35 | transition: 0.3s; 36 | 37 | color: #1d1d1d; 38 | background-color: #e4e4e4; 39 | } 40 | 41 | #top-bar .button:hover { 42 | color: #e4e4e4; 43 | background-color: #5c5a5a; 44 | } 45 | 46 | #graph-area { 47 | width: 50%; 48 | height: 100%; 49 | float: left; 50 | 51 | background-color: #464646; 52 | } 53 | 54 | #editor { 55 | width: 100%; 56 | height: 100%; 57 | } 58 | 59 | #view-area { 60 | width: 50%; 61 | height: 100%; 62 | float: right; 63 | 64 | background-color: #00ff9d; 65 | } 66 | 67 | pre { 68 | overflow-x: auto; 69 | white-space: pre-wrap; 70 | white-space: -moz-pre-wrap; 71 | white-space: -pre-wrap; 72 | white-space: -o-pre-wrap; 73 | word-wrap: break-word; 74 | } 75 | 76 | 77 | /* SLIDER */ 78 | 79 | .slidecontainer { 80 | width: 63%; 81 | } 82 | 83 | .slider { 84 | -webkit-appearance: none; 85 | width: 63%; 86 | height: 15px; 87 | background: #e4e4e4; 88 | outline: none; 89 | opacity: 0.7; 90 | -webkit-transition: .2s; 91 | transition: opacity .2s; 92 | border-color: #8d8d8d; 93 | border-style: groove; 94 | } 95 | 96 | .slider:hover { 97 | opacity: 1; 98 | } 99 | 100 | .slider::-webkit-slider-thumb { 101 | -webkit-appearance: none; 102 | appearance: none; 103 | width: 15px; 104 | height: 15px; 105 | background: #272727; 106 | cursor: pointer; 107 | } 108 | 109 | .slider::-moz-range-thumb { 110 | width: 15px; 111 | height: 15px; 112 | background: #272727; 113 | cursor: pointer; 114 | } 115 | 116 | 117 | /* PROGRESS BAR */ 118 | 119 | #myProgress { 120 | width: 75%; 121 | background-color: #ddd; 122 | margin-top: 10px; 123 | margin-left: 3px; 124 | } 125 | 126 | #myBar { 127 | width: 1%; 128 | height: 10px; 129 | background-color: rgb(255, 97, 35);; 130 | text-align: center; 131 | line-height: 10px; 132 | color: white; 133 | } 134 | 135 | 136 | /* DIALOG */ 137 | 138 | .dialog { 139 | position: absolute; 140 | top: 50%; 141 | left: 50%; 142 | margin-top: -150px; 143 | margin-left: -200px; 144 | 145 | background-color: #222222; 146 | 147 | min-width: 300px; 148 | min-height: 200px; 149 | box-shadow: 0 0 4px #111; 150 | border-radius: 6px; 151 | } 152 | 153 | .dialog.settings { 154 | left: 10px; 155 | bottom: 20px; 156 | height: calc( 50% ); 157 | margin: auto; 158 | overflow: auto; 159 | } 160 | 161 | .dialog .close { 162 | float: right; 163 | margin: 4px; 164 | margin-right: 10px; 165 | cursor: pointer; 166 | font-size: 1.4em; 167 | } 168 | 169 | .dialog .close:hover { 170 | color: white; 171 | } 172 | 173 | .dialog .dialog-header { 174 | color: #AAA; 175 | border-bottom: 1px solid #161616; 176 | } 177 | 178 | .dialog .dialog-header { height: 40px; } 179 | .dialog .dialog-footer { height: 30px; padding: 5px; padding-bottom: 0px; border-top: 1px solid #1a1a1a;} 180 | 181 | .dialog .dialog-header .dialog-title { 182 | font: 20px "Arial"; 183 | margin: 4px; 184 | padding: 4px 10px; 185 | display: inline-block; 186 | } 187 | 188 | .dialog .dialog-content { 189 | height: calc(100% - 90px); 190 | width: calc(100% - 10px); 191 | padding: 4px; 192 | display: inline-block; 193 | color: #AAA; 194 | } 195 | 196 | .dialog .dialog-content h3 { 197 | margin: 10px; 198 | } 199 | 200 | .dialog .dialog-content .connections { 201 | flex-direction: row; 202 | } 203 | 204 | .dialog .dialog-content .connections .connections_side { 205 | width: calc(50% - 5px); 206 | min-height: 100px; 207 | background-color: black; 208 | display: flex; 209 | } 210 | 211 | .dialog .node_type { 212 | font-size: 15px; 213 | display: block; 214 | margin: 10px; 215 | } 216 | 217 | .dialog .node_desc { 218 | opacity: 0.5; 219 | font-size: 14px; 220 | display: block; 221 | margin: 10px; 222 | } 223 | 224 | .dialog .separator { 225 | display: block; 226 | width: calc( 100% - 4px ); 227 | height: 1px; 228 | border-top: 1px solid #000; 229 | border-bottom: 1px solid #333; 230 | margin: 10px 2px; 231 | padding: 0; 232 | } 233 | 234 | .dialog .property { 235 | margin-bottom: 2px; 236 | padding: 10px; 237 | padding-top: 5px; 238 | padding-bottom: 5px; 239 | } 240 | 241 | .dialog .property_name { 242 | 243 | color: #737373; 244 | display: inline-block; 245 | text-align: left; 246 | vertical-align: top; 247 | width: 120px; 248 | padding-left: 4px; 249 | overflow: hidden; 250 | } 251 | 252 | .dialog .property_value { 253 | display: inline-block; 254 | text-align: right; 255 | font: 15px "Arial"; 256 | color: #AAA; 257 | background-color: #1A1A1A; 258 | width: calc( 100% - 122px ); 259 | max-height: 300px; 260 | padding: 4px; 261 | padding-right: 12px; 262 | overflow: hidden; 263 | cursor: pointer; 264 | border-radius: 3px; 265 | } 266 | 267 | .dialog .property_value:hover { 268 | color: white; 269 | } 270 | 271 | .dialog .property.boolean .property_value { 272 | padding-right: 30px; 273 | } 274 | 275 | .dialog button { 276 | border-radius: 4px; 277 | border: none; 278 | padding: 4px 20px; 279 | margin-left: 0px; 280 | margin-bottom: 0px; 281 | background-color: #060606; 282 | color: #8e8e8e; 283 | } 284 | 285 | .dialog button:hover { 286 | background-color: #111; 287 | color: #FFF; 288 | } 289 | 290 | .dialog button.delete:hover { 291 | background-color: rgb(255, 97, 35); 292 | color: black; 293 | } 294 | -------------------------------------------------------------------------------- /datasets/ct-torax/image-00000.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00000.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00001.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00001.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00002.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00002.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00003.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00003.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00004.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00004.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00005.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00005.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00006.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00006.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00007.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00007.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00008.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00008.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00009.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00009.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00010.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00010.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00011.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00011.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00012.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00012.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00013.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00013.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00014.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00014.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00015.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00015.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00016.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00016.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00017.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00017.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00018.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00018.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00019.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00019.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00020.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00020.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00021.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00021.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00022.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00022.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00023.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00023.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00024.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00024.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00025.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00025.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00026.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00026.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00027.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00027.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00028.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00028.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00029.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00029.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00030.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00030.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00031.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00031.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00032.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00032.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00033.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00033.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00034.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00034.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00035.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00035.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00036.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00036.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00037.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00037.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00038.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00038.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00039.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00039.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00040.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00040.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00041.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00041.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00042.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00042.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00043.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00043.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00044.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00044.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00045.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00045.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00046.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00046.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00047.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00047.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00048.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00048.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00049.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00049.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00050.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00050.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00051.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00051.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00052.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00052.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00053.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00053.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00054.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00054.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00055.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00055.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00056.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00056.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00057.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00057.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00058.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00058.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00059.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00059.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00060.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00060.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00061.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00061.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00062.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00062.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00063.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00063.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00064.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00064.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00065.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00065.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00066.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00066.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00067.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00067.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00068.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00068.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00069.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00069.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00070.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00070.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00071.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00071.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00072.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00072.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00073.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00073.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00074.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00074.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00075.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00075.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00076.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00076.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00077.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00077.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00078.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00078.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00079.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00079.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00080.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00080.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00081.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00081.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00082.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00082.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00083.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00083.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00084.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00084.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00085.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00085.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00086.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00086.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00087.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00087.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00088.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00088.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00089.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00089.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00090.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00090.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00091.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00091.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00092.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00092.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00093.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00093.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00094.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00094.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00095.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00095.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00096.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00096.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00097.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00097.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00098.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00098.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00099.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00099.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00100.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00100.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00101.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00101.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00102.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00102.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00103.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00103.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00104.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00104.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00105.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00105.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00106.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00106.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00107.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00107.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00108.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00108.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00109.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00109.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00110.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00110.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00111.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00111.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00112.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00112.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00113.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00113.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00114.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00114.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00115.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00115.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00116.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00116.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00117.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00117.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00118.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00118.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00119.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00119.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00120.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00120.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00121.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00121.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00122.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00122.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00123.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00123.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00124.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00124.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00125.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00125.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00126.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00126.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00127.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00127.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00128.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00128.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00129.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00129.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00130.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00130.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00131.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00131.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00132.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00132.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00133.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00133.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00134.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00134.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00135.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00135.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00136.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00136.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00137.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00137.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00138.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00138.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00139.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00139.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00140.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00140.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00141.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00141.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00142.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00142.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00143.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00143.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00144.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00144.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00145.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00145.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00146.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00146.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00147.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00147.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00148.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00148.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00149.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00149.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00150.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00150.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00151.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00151.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00152.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00152.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00153.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00153.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00154.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00154.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00155.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00155.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00156.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00156.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00157.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00157.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00158.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00158.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00159.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00159.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00160.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00160.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00161.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00161.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00162.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00162.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00163.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00163.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00164.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00164.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00165.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00165.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00166.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00166.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00167.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00167.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00168.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00168.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00169.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00169.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00170.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00170.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00171.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00171.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00172.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00172.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00173.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00173.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00174.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00174.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00175.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00175.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00176.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00176.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00177.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00177.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00178.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00178.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00179.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00179.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00180.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00180.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00181.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00181.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00182.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00182.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00183.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00183.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00184.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00184.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00185.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00185.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00186.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00186.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00187.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00187.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00188.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00188.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00189.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00189.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00190.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00190.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00191.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00191.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00192.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00192.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00193.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00193.dcm -------------------------------------------------------------------------------- /datasets/ct-torax/image-00194.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/datasets/ct-torax/image-00194.dcm -------------------------------------------------------------------------------- /demos/demo_case1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Node-Based Shader Editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | Node-Based Shader Editor 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /demos/demo_case1.js: -------------------------------------------------------------------------------- 1 | 2 | /** --- Víctor Ubieto 2020 --- 3 | * Main file that controls the application 4 | **/ 5 | 6 | "use strict" 7 | 8 | // Global variables 9 | var shader_atlas = []; 10 | var shader; 11 | var gl; 12 | 13 | var camera = null; 14 | var mouse = null; 15 | var entity = null; 16 | 17 | const options = { 18 | quality: 64.0, 19 | color_bg: [0.34, 0.34, 0.34, 1.0], 20 | color_mesh: [1.0, 0.0, 1.0, 1.0], 21 | brightness: 1.0 22 | } 23 | 24 | const time = { 25 | last: 0, 26 | now: null, 27 | dt: null 28 | } 29 | 30 | // Executes the app // 31 | init(); 32 | 33 | function init() 34 | { 35 | // ----- INIT CANVAS FUNCTIONALITIES ----- 36 | //GraphCanvas 37 | var root = document.getElementById("editor"); 38 | initGraphCanvas(root); 39 | //WebGL View 40 | root = document.getElementById("view-area"); 41 | initWebGLView(root); 42 | //Init Buttons 43 | initListeners(); 44 | 45 | // ----- LOAD MATERIAL NEEDED ----- 46 | //Load the shaders from a file 47 | loadShaderAtlas(); 48 | //Load the nodes created 49 | addNodes(); 50 | 51 | // ----- INIT BASIC SCENE ----- 52 | //Creates some essential nodes on the initial canvas 53 | graphTemplate(); 54 | //Inits the scene to render 55 | createScene(); 56 | 57 | // ----- MAIN LOOP ----- 58 | onLoad(); 59 | } 60 | 61 | // ---------------------------------------- INIT ----------------------------------------------- // 62 | 63 | // Graph Canvas Creation 64 | function initGraphCanvas(container_id) 65 | { 66 | var canvas = container_id.querySelector(".Graph"); 67 | canvas.height = container_id.offsetHeight; 68 | canvas.width = container_id.offsetWidth; 69 | 70 | var graph = new LGraph(); 71 | var graphcanvas = new LGraphCanvas(canvas, graph); 72 | 73 | graphcanvas.background_image = "../../graph_system/img/migrid.png"; 74 | 75 | graph.onAfterExecute = function() { 76 | graphcanvas.draw(true); 77 | }; 78 | 79 | window.graphcanvas = graphcanvas; 80 | window.graph = graph; 81 | 82 | graphcanvas.onShowNodePanel = onShowNodePanel.bind(this); 83 | } 84 | 85 | // WebGL Canvas Creation 86 | function initWebGLView(container_id) 87 | { 88 | var canvas = container_id.querySelector(".View"); 89 | canvas.height = container_id.offsetHeight; 90 | canvas.width = container_id.offsetWidth; 91 | gl = GL.create({canvas: canvas, version: 2}); //webgl2 for the 3d textures 92 | if( gl.webgl_version != 2 || !gl ){ 93 | alert("WebGL 2.0 not supported by your browser"); 94 | } 95 | gl.animate(); 96 | } 97 | 98 | // This function inits listeners (buttons, mouse, keys, ...) 99 | function initListeners() 100 | { 101 | // Set buttons 102 | var optButton = document.getElementById("options"); 103 | var viewShader = document.getElementById("viewShader"); 104 | var cleanGraph = document.getElementById("cleanGraph"); 105 | var aboutButton = document.getElementById("about"); 106 | 107 | optButton.addEventListener("click", function(){ 108 | w2popup.open({ 109 | width: 300, height: 300, 110 | url: 'readme.html', 111 | title: 'Visualization Options', 112 | body: `
113 |

114 |

Background Color

115 | 119 | 120 |

121 |

Mesh Color (surface rendering)

122 | 126 | 127 |

128 |

Quality:

129 | 130 |

131 | 140 |

Brightness:

141 | 142 | 151 |
`, 152 | onOpen : function () { 153 | console.log('opened'); 154 | } 155 | }); 156 | }, false); 157 | 158 | viewShader.addEventListener("click", function(){ 159 | w2popup.open({ 160 | width: 700, height: 500, 161 | title: 'Shaders in Use', 162 | body: '

Vertex Shader

' + 163 | '
' + Previous_VS + '
' + 164 | '

Fragment Shader

' + 165 | '
' + Previous_FS + '
', 166 | buttons: '', 167 | onOpen : function () { 168 | console.log('opened'); 169 | } 170 | }); 171 | }, false); 172 | 173 | cleanGraph.addEventListener("click", function(){ 174 | if (confirm("Do you want to clean the editor?")) { 175 | var length = graph._nodes_in_order.length; 176 | for (var i = 0; i < (length - 2); i++){ 177 | var node = graph._nodes_in_order[0]; 178 | node.graph.remove(node); 179 | } 180 | console.log("Existing graph deleted."); 181 | } else { 182 | console.log("Cleaning operation canceled."); 183 | } 184 | }, false); 185 | 186 | aboutButton.addEventListener("click", function(){ 187 | w2popup.load({ 188 | url: 'readme.html', 189 | showMax: true, 190 | width: 600, 191 | height: 500}); 192 | }, false); 193 | 194 | window.addEventListener("resize", resizeView.bind(this)); 195 | 196 | // Get mouse actions 197 | mouse = new Mouse(); 198 | } 199 | 200 | // This function reads the file that contains the shaders and store them 201 | function loadShaderAtlas() 202 | { 203 | GL.loadFileAtlas("../shaders.glsl", function(files){ 204 | shader_atlas = files; //parsed file 205 | }); 206 | } 207 | 208 | // Basic Template that inits the web with the essential nodes 209 | function graphTemplate() 210 | { 211 | var node_tf = LiteGraph.createNode("Texture/Transfer Function"); 212 | node_tf.pos = [50,50]; 213 | graph.add(node_tf); 214 | 215 | var node_dicom = LiteGraph.createNode("Texture/Dicom"); 216 | node_dicom.pos = [50,350]; 217 | graph.add(node_dicom); 218 | 219 | var node_math = LiteGraph.createNode("Operator/Math"); 220 | node_math.pos = [250,350]; 221 | node_math.properties.OP = "*"; 222 | graph.add(node_math); 223 | 224 | var node_rot = LiteGraph.createNode("Operator/Rotate"); 225 | node_rot.pos = [50,575]; 226 | node_rot.setY(-90.0); 227 | graph.add(node_rot); 228 | 229 | var node_tra = LiteGraph.createNode("Operator/Translate"); 230 | node_tra.pos = [300,575]; 231 | graph.add(node_tra); 232 | 233 | var node_grad = LiteGraph.createNode("Texture/Gradient"); 234 | node_grad.pos = [540,575]; 235 | graph.add(node_grad); 236 | 237 | var node_ramp = LiteGraph.createNode("Operator/ColorRamp"); 238 | node_ramp.pos = [500,400]; 239 | node_ramp.setMinValue(0.0); 240 | node_ramp.setMaxValue(1.0); 241 | graph.add(node_ramp); 242 | 243 | var node_volume = LiteGraph.createNode("Shader/Volume"); 244 | node_volume.pos = [350,150]; 245 | graph.add(node_volume); 246 | 247 | var node_out = LiteGraph.createNode("Output/Material Output"); 248 | node_out.pos = [600,200]; 249 | graph.add(node_out); 250 | 251 | //Connections 252 | node_tf.connect(0, node_volume, 0); 253 | node_math.connect(0, node_volume, 1); 254 | node_ramp.connect(0, node_math, 1); 255 | node_grad.connect(1, node_ramp, 0); 256 | node_tra.connect(0, node_grad, 0); 257 | node_rot.connect(0, node_tra, 0); 258 | node_volume.connect(0, node_out, 0); 259 | } 260 | 261 | function createScene() 262 | { 263 | //create camera 264 | camera = new RD.Camera({position: [0,10,10], aspect: gl.canvas.width / gl.canvas.height}); 265 | 266 | //create default mesh 267 | entity = new Entity({type: "cube", size: 2}); 268 | 269 | //adjust camera to mesh bounding 270 | entity.centerInView(camera); 271 | 272 | //Basic shader while the parser hasn't read the other shaders yet 273 | var fastVS = ` 274 | precision highp float; 275 | attribute vec3 a_vertex; 276 | attribute vec3 a_normal; 277 | attribute vec4 a_color; 278 | uniform mat4 u_mvp; 279 | uniform mat4 u_model; 280 | varying vec3 v_normal; 281 | varying vec4 v_color; 282 | void main() { 283 | v_normal = (u_model * vec4(a_normal,0.0)).xyz; 284 | gl_Position = u_mvp * vec4(a_vertex,1.0); 285 | } 286 | `; 287 | var fastFS = ` 288 | precision highp float; 289 | varying vec3 v_normal; 290 | uniform vec4 u_camera_position; 291 | uniform vec4 u_color; 292 | 293 | uniform vec3 u_vertex; 294 | void main() { 295 | vec4 final_color = u_color; 296 | gl_FragColor = vec4( final_color.xyz, 1.0 ); 297 | } 298 | `; 299 | 300 | //Basic shader 301 | shader = new Shader( fastVS, fastFS ); 302 | } 303 | 304 | // ---------------------------------------- RENDER ----------------------------------------------- // 305 | 306 | // Main Loop 307 | function onLoad() 308 | { 309 | window.graph.runStep(); 310 | 311 | time.last = time.now || 0; 312 | time.now = getTime(); 313 | time.dt = (time.now - time.last) * 0.001; 314 | update(time.dt); 315 | render(); 316 | 317 | requestAnimationFrame( onLoad.bind(this) ); 318 | } 319 | 320 | function render() 321 | { 322 | //clear 323 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 324 | gl.clearColor(options.color_bg[0], options.color_bg[1], options.color_bg[2], options.color_bg[3]); 325 | 326 | //generic gl flags and settings 327 | gl.disable(gl.DEPTH_TEST); 328 | gl.enable(gl.CULL_FACE); 329 | gl.cullFace(gl.BACK); 330 | 331 | var inv_model = mat4.create(); 332 | mat4.invert(inv_model, entity._model_matrix); 333 | 334 | //get local camera position (makes it easier in the shader) 335 | var aux_vec4 = vec4.fromValues(camera._position[0], camera._position[1], camera._position[2], 1); 336 | vec4.transformMat4(aux_vec4, aux_vec4, inv_model); 337 | var local_cam_pos = vec3.fromValues(aux_vec4[0]/aux_vec4[3], aux_vec4[1]/aux_vec4[3], aux_vec4[2]/aux_vec4[3]); 338 | 339 | if (shader != null) 340 | { 341 | //render mesh using the shader 342 | if(entity._mesh) 343 | shader.uniforms({ 344 | u_camera_position: camera._position, 345 | u_local_camera_position: local_cam_pos, 346 | u_model: entity._model_matrix, 347 | u_obj_size: entity._mesh.size/2.0 || entity._mesh.radius, //divided by 2 because it is centered in 0 348 | u_mvp: camera._viewprojection_matrix, 349 | u_color: options.color_mesh, 350 | u_quality: options.quality, 351 | u_brightness: options.brightness 352 | }).draw(entity._mesh); 353 | } 354 | } 355 | 356 | function update(dt) 357 | { 358 | updateCamera(dt); 359 | } 360 | 361 | function updateCamera(dt) 362 | { 363 | if (mouse._button == 1 || mouse._button == 4) //left,center: orbit 364 | { 365 | if (mouse._drag_state) 366 | { 367 | var yaw = -mouse._delta_x * dt * 0.7; 368 | var pitch = -mouse._delta_y * dt * 0.7; 369 | orbitCamera(yaw, pitch); 370 | 371 | mouse._drag_state = false; 372 | } 373 | } 374 | if (mouse._button == 2) //right: pan 375 | { 376 | if (mouse._drag_state) 377 | { 378 | camera.moveLocal([-mouse._delta_x * dt * 0.3, mouse._delta_y * dt * 0.3, 0], camera._fov/45); 379 | mouse._drag_state = false; 380 | } 381 | } 382 | if (mouse._wheel_state) //wheel: zoom 383 | { 384 | zoomCamera(dt); 385 | mouse._wheel_state = false; 386 | } 387 | 388 | //update camera 389 | mat4.lookAt(camera._view_matrix, camera._position, camera._target, [0,1,0]); 390 | mat4.perspective(camera._projection_matrix, camera._fov * DEG2RAD, camera._aspect, 0.1, 1000); 391 | 392 | //update modelview and projection matrices 393 | mat4.multiply(entity._modelview_matrix, camera._view_matrix, entity._model_matrix); 394 | mat4.multiply(camera._viewprojection_matrix, camera._projection_matrix, camera._view_matrix); 395 | } 396 | 397 | function orbitCamera(yaw, pitch) 398 | { 399 | camera.orbit(yaw, camera._up); 400 | 401 | var front = vec3.create(); 402 | vec3.subtract(front, camera._target, camera._position) 403 | vec3.normalize(front, front); 404 | var up = vec3.clone(camera._up); 405 | vec3.normalize(up, up); 406 | var problem_angle = vec3.dot(front, up); 407 | if(!((problem_angle > 0.99 && pitch > 0) || (problem_angle < -0.99 && pitch < 0))) 408 | { 409 | var right = vec3.create(); 410 | camera.getLocalVector([1.0, 0, 0], right); 411 | camera.orbit(pitch, right); 412 | } 413 | } 414 | 415 | function zoomCamera(dt) 416 | { 417 | camera._fov += -mouse._wheel_value * dt; 418 | camera._fov = Math.max(0.0, camera._fov); 419 | } -------------------------------------------------------------------------------- /demos/demo_case2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Node-Based Shader Editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | Node-Based Shader Editor 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /demos/demo_case2.js: -------------------------------------------------------------------------------- 1 | 2 | /** --- Víctor Ubieto 2020 --- 3 | * Main file that controls the application 4 | **/ 5 | 6 | "use strict" 7 | 8 | // Global variables 9 | var shader_atlas = []; 10 | var shader; 11 | var gl; 12 | 13 | var camera = null; 14 | var mouse = null; 15 | var entity = null; 16 | 17 | const options = { 18 | quality: 32.0, 19 | color_bg: [0.82, 0.91, 0.98, 1.0], 20 | color_mesh: [1.0, 0.0, 1.0, 1.0], 21 | brightness: 1.0 22 | } 23 | 24 | const time = { 25 | last: 0, 26 | now: null, 27 | dt: null 28 | } 29 | 30 | // Executes the app // 31 | init(); 32 | 33 | function init() 34 | { 35 | // ----- INIT CANVAS FUNCTIONALITIES ----- 36 | //GraphCanvas 37 | var root = document.getElementById("editor"); 38 | initGraphCanvas(root); 39 | //WebGL View 40 | root = document.getElementById("view-area"); 41 | initWebGLView(root); 42 | //Init Buttons 43 | initListeners(); 44 | 45 | // ----- LOAD MATERIAL NEEDED ----- 46 | //Load the shaders from a file 47 | loadShaderAtlas(); 48 | //Load the nodes created 49 | addNodes(); 50 | 51 | // ----- INIT BASIC SCENE ----- 52 | //Creates some essential nodes on the initial canvas 53 | graphTemplate(); 54 | //Inits the scene to render 55 | createScene(); 56 | 57 | // ----- MAIN LOOP ----- 58 | onLoad(); 59 | } 60 | 61 | // ---------------------------------------- INIT ----------------------------------------------- // 62 | 63 | // Graph Canvas Creation 64 | function initGraphCanvas(container_id) 65 | { 66 | var canvas = container_id.querySelector(".Graph"); 67 | canvas.height = container_id.offsetHeight; 68 | canvas.width = container_id.offsetWidth; 69 | 70 | var graph = new LGraph(); 71 | var graphcanvas = new LGraphCanvas(canvas, graph); 72 | 73 | graphcanvas.background_image = "../../graph_system/img/migrid.png"; 74 | 75 | graph.onAfterExecute = function() { 76 | graphcanvas.draw(true); 77 | }; 78 | 79 | window.graphcanvas = graphcanvas; 80 | window.graph = graph; 81 | 82 | graphcanvas.onShowNodePanel = onShowNodePanel.bind(this); 83 | } 84 | 85 | // WebGL Canvas Creation 86 | function initWebGLView(container_id) 87 | { 88 | var canvas = container_id.querySelector(".View"); 89 | canvas.height = container_id.offsetHeight; 90 | canvas.width = container_id.offsetWidth; 91 | gl = GL.create({canvas: canvas, version: 2}); //webgl2 for the 3d textures 92 | if( gl.webgl_version != 2 || !gl ){ 93 | alert("WebGL 2.0 not supported by your browser"); 94 | } 95 | gl.animate(); 96 | } 97 | 98 | // This function inits listeners (buttons, mouse, keys, ...) 99 | function initListeners() 100 | { 101 | // Set buttons 102 | var optButton = document.getElementById("options"); 103 | var viewShader = document.getElementById("viewShader"); 104 | var cleanGraph = document.getElementById("cleanGraph"); 105 | var aboutButton = document.getElementById("about"); 106 | 107 | optButton.addEventListener("click", function(){ 108 | w2popup.open({ 109 | width: 300, height: 300, 110 | url: 'readme.html', 111 | title: 'Visualization Options', 112 | body: `
113 |

114 |

Background Color

115 | 119 | 120 |

121 |

Mesh Color (surface rendering)

122 | 126 | 127 |

128 |

Quality:

129 | 130 |

131 | 140 |

Brightness:

141 | 142 | 151 |
`, 152 | onOpen : function () { 153 | console.log('opened'); 154 | } 155 | }); 156 | }, false); 157 | 158 | viewShader.addEventListener("click", function(){ 159 | w2popup.open({ 160 | width: 700, height: 500, 161 | title: 'Shaders in Use', 162 | body: '

Vertex Shader

' + 163 | '
' + Previous_VS + '
' + 164 | '

Fragment Shader

' + 165 | '
' + Previous_FS + '
', 166 | buttons: '', 167 | onOpen : function () { 168 | console.log('opened'); 169 | } 170 | }); 171 | }, false); 172 | 173 | cleanGraph.addEventListener("click", function(){ 174 | if (confirm("Do you want to clean the editor?")) { 175 | var length = graph._nodes_in_order.length; 176 | for (var i = 0; i < (length - 2); i++){ 177 | var node = graph._nodes_in_order[0]; 178 | node.graph.remove(node); 179 | } 180 | console.log("Existing graph deleted."); 181 | } else { 182 | console.log("Cleaning operation canceled."); 183 | } 184 | }, false); 185 | 186 | aboutButton.addEventListener("click", function(){ 187 | w2popup.load({ 188 | url: 'readme.html', 189 | showMax: true, 190 | width: 600, 191 | height: 500}); 192 | }, false); 193 | 194 | window.addEventListener("resize", resizeView.bind(this)); 195 | 196 | // Get mouse actions 197 | mouse = new Mouse(); 198 | } 199 | 200 | // This function reads the file that contains the shaders and store them 201 | function loadShaderAtlas() 202 | { 203 | GL.loadFileAtlas("../shaders.glsl", function(files){ 204 | shader_atlas = files; //parsed file 205 | }); 206 | } 207 | 208 | // Basic Template that inits the web with the essential nodes 209 | function graphTemplate() 210 | { 211 | var node_color = LiteGraph.createNode("Input/Color"); 212 | node_color.pos = [350,75]; 213 | node_color.setValue([1.0,1.0,1.0]); 214 | graph.add(node_color); 215 | 216 | var node_math = LiteGraph.createNode("Operator/Math"); 217 | node_math.pos = [150,75]; 218 | node_math.properties.OP = "*"; 219 | graph.add(node_math); 220 | 221 | var node_tra1 = LiteGraph.createNode("Operator/Translate"); 222 | node_tra1.pos = [50,530]; 223 | graph.add(node_tra1); 224 | 225 | var node_noise = LiteGraph.createNode("Texture/Noise"); 226 | node_noise.pos = [50,350]; 227 | node_noise.setScale(2.0); 228 | node_noise.setDetail(3.0); 229 | graph.add(node_noise); 230 | 231 | var node_ramp = LiteGraph.createNode("Operator/ColorRamp"); 232 | node_ramp.pos = [50,200]; 233 | graph.add(node_ramp); 234 | 235 | var node_rot = LiteGraph.createNode("Operator/Rotate"); 236 | node_rot.pos = [550,575]; 237 | node_rot.setZ(-90.0); 238 | graph.add(node_rot); 239 | 240 | var node_tra2 = LiteGraph.createNode("Operator/Translate"); 241 | node_tra2.pos = [300,575]; 242 | node_tra2.setY(-0.5); 243 | graph.add(node_tra2); 244 | 245 | var node_grad = LiteGraph.createNode("Texture/Gradient"); 246 | node_grad.pos = [520,420]; 247 | graph.add(node_grad); 248 | 249 | var node_volume = LiteGraph.createNode("Shader/Volume"); 250 | node_volume.pos = [500,175]; 251 | graph.add(node_volume); 252 | 253 | var node_out = LiteGraph.createNode("Output/Material Output"); 254 | node_out.pos = [600,80]; 255 | graph.add(node_out); 256 | 257 | //Connections 258 | node_color.connect(0, node_volume, 0); 259 | node_tra1.connect(0, node_noise, 0); 260 | node_noise.connect(1, node_ramp, 0); 261 | node_ramp.connect(1, node_math, 0); 262 | node_tra2.connect(0, node_rot, 0); 263 | node_rot.connect(0, node_grad, 0); 264 | node_volume.connect(0, node_out, 0); 265 | } 266 | 267 | function createScene() 268 | { 269 | //create camera 270 | camera = new RD.Camera({position: [0,10,10], aspect: gl.canvas.width / gl.canvas.height}); 271 | 272 | //create default mesh 273 | entity = new Entity({type: "cube", size: 2}); 274 | 275 | //adjust camera to mesh bounding 276 | entity.centerInView(camera); 277 | 278 | //Basic shader while the parser hasn't read the other shaders yet 279 | var fastVS = ` 280 | precision highp float; 281 | attribute vec3 a_vertex; 282 | attribute vec3 a_normal; 283 | attribute vec4 a_color; 284 | uniform mat4 u_mvp; 285 | uniform mat4 u_model; 286 | varying vec3 v_normal; 287 | varying vec4 v_color; 288 | void main() { 289 | v_normal = (u_model * vec4(a_normal,0.0)).xyz; 290 | gl_Position = u_mvp * vec4(a_vertex,1.0); 291 | } 292 | `; 293 | var fastFS = ` 294 | precision highp float; 295 | varying vec3 v_normal; 296 | uniform vec4 u_camera_position; 297 | uniform vec4 u_color; 298 | 299 | uniform vec3 u_vertex; 300 | void main() { 301 | vec4 final_color = u_color; 302 | gl_FragColor = vec4( final_color.xyz, 1.0 ); 303 | } 304 | `; 305 | 306 | //Basic shader 307 | shader = new Shader( fastVS, fastFS ); 308 | } 309 | 310 | // ---------------------------------------- RENDER ----------------------------------------------- // 311 | 312 | // Main Loop 313 | function onLoad() 314 | { 315 | window.graph.runStep(); 316 | 317 | time.last = time.now || 0; 318 | time.now = getTime(); 319 | time.dt = (time.now - time.last) * 0.001; 320 | update(time.dt); 321 | render(); 322 | 323 | requestAnimationFrame( onLoad.bind(this) ); 324 | } 325 | 326 | function render() 327 | { 328 | //clear 329 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 330 | gl.clearColor(options.color_bg[0], options.color_bg[1], options.color_bg[2], options.color_bg[3]); 331 | 332 | //generic gl flags and settings 333 | gl.disable(gl.DEPTH_TEST); 334 | gl.enable(gl.CULL_FACE); 335 | gl.cullFace(gl.BACK); 336 | 337 | var inv_model = mat4.create(); 338 | mat4.invert(inv_model, entity._model_matrix); 339 | 340 | //get local camera position (makes it easier in the shader) 341 | var aux_vec4 = vec4.fromValues(camera._position[0], camera._position[1], camera._position[2], 1); 342 | vec4.transformMat4(aux_vec4, aux_vec4, inv_model); 343 | var local_cam_pos = vec3.fromValues(aux_vec4[0]/aux_vec4[3], aux_vec4[1]/aux_vec4[3], aux_vec4[2]/aux_vec4[3]); 344 | 345 | if (shader != null) 346 | { 347 | //render mesh using the shader 348 | if(entity._mesh) 349 | shader.uniforms({ 350 | u_camera_position: camera._position, 351 | u_local_camera_position: local_cam_pos, 352 | u_model: entity._model_matrix, 353 | u_obj_size: entity._mesh.size/2.0 || entity._mesh.radius, //divided by 2 because it is centered in 0 354 | u_mvp: camera._viewprojection_matrix, 355 | u_color: options.color_mesh, 356 | u_quality: options.quality, 357 | u_brightness: options.brightness 358 | }).draw(entity._mesh); 359 | } 360 | } 361 | 362 | function update(dt) 363 | { 364 | updateCamera(dt); 365 | } 366 | 367 | function updateCamera(dt) 368 | { 369 | if (mouse._button == 1 || mouse._button == 4) //left,center: orbit 370 | { 371 | if (mouse._drag_state) 372 | { 373 | var yaw = -mouse._delta_x * dt * 0.7; 374 | var pitch = -mouse._delta_y * dt * 0.7; 375 | orbitCamera(yaw, pitch); 376 | 377 | mouse._drag_state = false; 378 | } 379 | } 380 | if (mouse._button == 2) //right: pan 381 | { 382 | if (mouse._drag_state) 383 | { 384 | camera.moveLocal([-mouse._delta_x * dt * 0.3, mouse._delta_y * dt * 0.3, 0], camera._fov/45); 385 | mouse._drag_state = false; 386 | } 387 | } 388 | if (mouse._wheel_state) //wheel: zoom 389 | { 390 | zoomCamera(dt); 391 | mouse._wheel_state = false; 392 | } 393 | 394 | //update camera 395 | mat4.lookAt(camera._view_matrix, camera._position, camera._target, [0,1,0]); 396 | mat4.perspective(camera._projection_matrix, camera._fov * DEG2RAD, camera._aspect, 0.1, 1000); 397 | 398 | //update modelview and projection matrices 399 | mat4.multiply(entity._modelview_matrix, camera._view_matrix, entity._model_matrix); 400 | mat4.multiply(camera._viewprojection_matrix, camera._projection_matrix, camera._view_matrix); 401 | } 402 | 403 | function orbitCamera(yaw, pitch) 404 | { 405 | camera.orbit(yaw, camera._up); 406 | 407 | var front = vec3.create(); 408 | vec3.subtract(front, camera._target, camera._position) 409 | vec3.normalize(front, front); 410 | var up = vec3.clone(camera._up); 411 | vec3.normalize(up, up); 412 | var problem_angle = vec3.dot(front, up); 413 | if(!((problem_angle > 0.99 && pitch > 0) || (problem_angle < -0.99 && pitch < 0))) 414 | { 415 | var right = vec3.create(); 416 | camera.getLocalVector([1.0, 0, 0], right); 417 | camera.orbit(pitch, right); 418 | } 419 | } 420 | 421 | function zoomCamera(dt) 422 | { 423 | camera._fov += -mouse._wheel_value * dt; 424 | camera._fov = Math.max(0.0, camera._fov); 425 | } -------------------------------------------------------------------------------- /img/migrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/img/migrid.png -------------------------------------------------------------------------------- /img/readme_examples/example_clouds.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/img/readme_examples/example_clouds.PNG -------------------------------------------------------------------------------- /img/readme_examples/example_torax.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/img/readme_examples/example_torax.PNG -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Node-Based Shader Editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | Node-Based Shader Editor 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /js/external/jscolor/arrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/js/external/jscolor/arrow.gif -------------------------------------------------------------------------------- /js/external/jscolor/cross.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/js/external/jscolor/cross.gif -------------------------------------------------------------------------------- /js/external/jscolor/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | jscolor demo 4 | 5 | 6 | 7 | 8 | 9 | Click here: 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /js/external/jscolor/hs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/js/external/jscolor/hs.png -------------------------------------------------------------------------------- /js/external/jscolor/hv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorubieto/graph_system/373bb4a748ed8a9bb40d131b20814cea7cb5e482/js/external/jscolor/hv.png -------------------------------------------------------------------------------- /js/external/jscolor/jscolor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jscolor, JavaScript Color Picker 3 | * 4 | * @version 1.3.12 5 | * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html 6 | * @author Jan Odvarko, http://odvarko.cz 7 | * @created 2008-06-15 8 | * @updated 2012-01-05 9 | * @link http://jscolor.com 10 | */ 11 | 12 | 13 | var jscolor = { 14 | 15 | 16 | dir : '', // location of jscolor directory (leave empty to autodetect) 17 | bindClass : 'color', // class name 18 | binding : true, // automatic binding via 19 | preloading : true, // use image preloading? 20 | 21 | 22 | install : function() { 23 | jscolor.addEvent(window, 'load', jscolor.init); 24 | }, 25 | 26 | 27 | init : function() { 28 | if(jscolor.binding) { 29 | jscolor.bind(); 30 | } 31 | if(jscolor.preloading) { 32 | jscolor.preload(); 33 | } 34 | }, 35 | 36 | 37 | getDir : function() { 38 | if(!jscolor.dir) { 39 | var detected = jscolor.detectDir(); 40 | jscolor.dir = detected!==false ? detected : 'jscolor/'; 41 | } 42 | return jscolor.dir; 43 | }, 44 | 45 | 46 | detectDir : function() { 47 | var base = location.href; 48 | 49 | var e = document.getElementsByTagName('base'); 50 | for(var i=0; i vs[a] ? 396 | (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : 397 | tp[a], 398 | -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? 399 | (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : 400 | (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) 401 | ]; 402 | } 403 | drawPicker(pp[a], pp[b]); 404 | } 405 | }; 406 | 407 | 408 | this.importColor = function() { 409 | if(!valueElement) { 410 | this.exportColor(); 411 | } else { 412 | if(!this.adjust) { 413 | if(!this.fromString(valueElement.value, leaveValue)) { 414 | styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; 415 | styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; 416 | styleElement.style.color = styleElement.jscStyle.color; 417 | this.exportColor(leaveValue | leaveStyle); 418 | } 419 | } else if(!this.required && /^\s*$/.test(valueElement.value)) { 420 | valueElement.value = ''; 421 | styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; 422 | styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; 423 | styleElement.style.color = styleElement.jscStyle.color; 424 | this.exportColor(leaveValue | leaveStyle); 425 | 426 | } else if(this.fromString(valueElement.value)) { 427 | // OK 428 | } else { 429 | this.exportColor(); 430 | } 431 | } 432 | }; 433 | 434 | 435 | this.exportColor = function(flags) { 436 | if(!(flags & leaveValue) && valueElement) { 437 | var value = this.toString(); 438 | if(this.caps) { value = value.toUpperCase(); } 439 | if(this.hash) { value = '#'+value; } 440 | valueElement.value = value; 441 | } 442 | if(!(flags & leaveStyle) && styleElement) { 443 | styleElement.style.backgroundImage = "none"; 444 | styleElement.style.backgroundColor = 445 | '#'+this.toString(); 446 | styleElement.style.color = 447 | 0.213 * this.rgb[0] + 448 | 0.715 * this.rgb[1] + 449 | 0.072 * this.rgb[2] 450 | < 0.5 ? '#FFF' : '#000'; 451 | } 452 | if(!(flags & leavePad) && isPickerOwner()) { 453 | redrawPad(); 454 | } 455 | if(!(flags & leaveSld) && isPickerOwner()) { 456 | redrawSld(); 457 | } 458 | }; 459 | 460 | 461 | this.fromHSV = function(h, s, v, flags) { // null = don't change 462 | h<0 && (h=0) || h>6 && (h=6); 463 | s<0 && (s=0) || s>1 && (s=1); 464 | v<0 && (v=0) || v>1 && (v=1); 465 | this.rgb = HSV_RGB( 466 | h===null ? this.hsv[0] : (this.hsv[0]=h), 467 | s===null ? this.hsv[1] : (this.hsv[1]=s), 468 | v===null ? this.hsv[2] : (this.hsv[2]=v) 469 | ); 470 | this.exportColor(flags); 471 | }; 472 | 473 | 474 | this.fromRGB = function(r, g, b, flags) { // null = don't change 475 | r<0 && (r=0) || r>1 && (r=1); 476 | g<0 && (g=0) || g>1 && (g=1); 477 | b<0 && (b=0) || b>1 && (b=1); 478 | var hsv = RGB_HSV( 479 | r===null ? this.rgb[0] : (this.rgb[0]=r), 480 | g===null ? this.rgb[1] : (this.rgb[1]=g), 481 | b===null ? this.rgb[2] : (this.rgb[2]=b) 482 | ); 483 | if(hsv[0] !== null) { 484 | this.hsv[0] = hsv[0]; 485 | } 486 | if(hsv[2] !== 0) { 487 | this.hsv[1] = hsv[1]; 488 | } 489 | this.hsv[2] = hsv[2]; 490 | this.exportColor(flags); 491 | }; 492 | 493 | 494 | this.fromString = function(hex, flags) { 495 | var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i); 496 | if(!m) { 497 | return false; 498 | } else { 499 | if(m[1].length === 6) { // 6-char notation 500 | this.fromRGB( 501 | parseInt(m[1].substr(0,2),16) / 255, 502 | parseInt(m[1].substr(2,2),16) / 255, 503 | parseInt(m[1].substr(4,2),16) / 255, 504 | flags 505 | ); 506 | } else { // 3-char notation 507 | this.fromRGB( 508 | parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255, 509 | parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255, 510 | parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255, 511 | flags 512 | ); 513 | } 514 | return true; 515 | } 516 | }; 517 | 518 | 519 | this.toString = function() { 520 | return ( 521 | (0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) + 522 | (0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) + 523 | (0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1) 524 | ); 525 | }; 526 | 527 | 528 | function RGB_HSV(r, g, b) { 529 | var n = Math.min(Math.min(r,g),b); 530 | var v = Math.max(Math.max(r,g),b); 531 | var m = v - n; 532 | if(m === 0) { return [ null, 0, v ]; } 533 | var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); 534 | return [ h===6?0:h, m/v, v ]; 535 | } 536 | 537 | 538 | function HSV_RGB(h, s, v) { 539 | if(h === null) { return [ v, v, v ]; } 540 | var i = Math.floor(h); 541 | var f = i%2 ? h-i : 1-(h-i); 542 | var m = v * (1 - s); 543 | var n = v * (1 - s*f); 544 | switch(i) { 545 | case 6: 546 | case 0: return [v,n,m]; 547 | case 1: return [n,v,m]; 548 | case 2: return [m,v,n]; 549 | case 3: return [m,n,v]; 550 | case 4: return [n,m,v]; 551 | case 5: return [v,m,n]; 552 | } 553 | } 554 | 555 | 556 | function removePicker() { 557 | var doc = jscolor.picker.owner.valueElement.ownerDocument; 558 | delete jscolor.picker.owner; 559 | doc.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB); 560 | } 561 | 562 | 563 | function drawPicker(x, y) { 564 | if(!jscolor.picker) { 565 | jscolor.picker = { 566 | box : document.createElement('div'), 567 | boxB : document.createElement('div'), 568 | pad : document.createElement('div'), 569 | padB : document.createElement('div'), 570 | padM : document.createElement('div'), 571 | sld : document.createElement('div'), 572 | sldB : document.createElement('div'), 573 | sldM : document.createElement('div'), 574 | btn : document.createElement('div'), 575 | btnS : document.createElement('span'), 576 | btnT : document.createTextNode(THIS.pickerCloseText) 577 | }; 578 | for(var i=0,segSize=4; i 4){ 54 | console.warn("There should be at least 1 channel and at most 4."); 55 | } 56 | this._computeSize(); 57 | this._data = o.data || this._data; 58 | 59 | if(!this._data){ 60 | this._createArrayView(o.buffer); 61 | } 62 | 63 | this._minmax = false; 64 | } 65 | 66 | //Computes theoretical size of data in bytes 67 | Volume.prototype._computeSize = function(){ 68 | this._size = this.width * this.height * this.depth * this.voxelBytes * this.voxelChannels; 69 | if(this._size == 0 || this._size % (this.voxelBytes*this.voxelChannels) != 0){ 70 | console.warn("Size does not seem correct."); 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | //Creates an unsigned int arrayview based on the attribute values 77 | //Other types must be created manually 78 | Volume.prototype._createArrayView = function(aBuffer){ 79 | if(!this._computeSize()){ 80 | return; 81 | } 82 | aBuffer = aBuffer || new ArrayBuffer(this._size); 83 | var aView = null; 84 | 85 | if(this.voxelType == "I"){ 86 | if(this.voxelBytes == 1){ 87 | aView = new Int8Array(aBuffer); 88 | }else if(this.voxelBytes == 2){ 89 | aView = new Int16Array(aBuffer); 90 | }else if(this.voxelBytes == 4){ 91 | aView = new Int32Array(aBuffer); 92 | } 93 | }else if(this.voxelType == "F"){ 94 | if(this.voxelBytes == 2){ 95 | aView = new Uint16Array(aBuffer); 96 | }else if(this.voxelBytes == 4){ 97 | aView = new Float32Array(aBuffer); 98 | }else if(this.voxelBytes == 8){ 99 | aView = new Float64Array(aBuffer); 100 | } 101 | }else{ 102 | if(this.voxelBytes == 1){ 103 | aView = new Uint8Array(aBuffer); 104 | }else if(this.voxelBytes == 2){ 105 | aView = new Uint16Array(aBuffer); 106 | }else if(this.voxelBytes == 4){ 107 | aView = new Uint32Array(aBuffer); 108 | } 109 | } 110 | this._data = aView; 111 | } 112 | 113 | Volume.prototype.isValid = function(){ 114 | return this._computeSize(); 115 | } 116 | 117 | Volume.prototype.computeMinMax = function(){ 118 | var m = Infinity; 119 | var M = -Infinity; 120 | 121 | for(var i=0; i M) M = v; 125 | } 126 | 127 | this._max = M; 128 | this._min = m; 129 | } 130 | 131 | //Returns a 3D Texture from a Volume data 132 | Volume.prototype.createTexture = function(options){ 133 | options = options || {}; 134 | 135 | var width = parseInt(this.width); 136 | var height = parseInt(this.height); 137 | var depth = parseInt(this.depth); 138 | var channels = parseInt(this.voxelChannels); 139 | var data = this._data; 140 | 141 | //Check dimensions and data 142 | if(width < 1 || height < 1 || depth < 1){ 143 | console.warn("Volume dimensions must be positive"); 144 | return null; 145 | } 146 | 147 | if(data == null){ 148 | console.warn("Creating texture without data"); 149 | }else if(data.length != width*height*depth*channels){ 150 | console.warn("Volume size does not match with data size"); 151 | return null; 152 | } 153 | 154 | //Cannot be overrided from outside volume info 155 | options.depth = depth; 156 | options.pixel_data = data; 157 | options.texture_type = gl.TEXTURE_3D; 158 | 159 | //Check https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 texImage2D to see possible combinations for format, type and internalFormat 160 | //For example for pre-computed gradients {format: gl.RGB, type: gl.UNSIGNED_BYTE, internalFormat: gl.RGB8} 161 | var guessParams = this.guessTextureParams(); 162 | 163 | options.format = options.format || guessParams.format; 164 | options.type = options.type || guessParams.type; 165 | options.internalFormat = options.internalFormat || guessParams.internalFormat; 166 | options.minFilter = options.minFilter || gl.NEAREST; 167 | options.magFilter = options.magFilter || gl.NEAREST; 168 | options.wrap = options.wrap || gl.CLAMP_TO_EDGE; 169 | 170 | this._texture = new GL.Texture(width, height, options); 171 | this._texture._volume = this; 172 | return this._texture; 173 | } 174 | 175 | //Uploads data to an already created 3D texture 176 | Volume.prototype.updateTexture = function(){ 177 | var width = parseInt(this.width); 178 | var height = parseInt(this.height); 179 | var depth = parseInt(this.depth); 180 | var data = this._data; 181 | var texture = this._texture; 182 | 183 | if(texture.texture_type != gl.TEXTURE_3D){ 184 | console.warn("Texture type is not TEXTURE_3D"); 185 | return false; 186 | } 187 | 188 | if(data == null){ 189 | console.warn("There must be data to upload"); 190 | return false; 191 | } 192 | if(width != texture.width || height != texture.height || depth != texture.depth || data.length != texture.data.length){ 193 | console.warn("Texture and volume dimensions do not match"); 194 | return false; 195 | } 196 | 197 | texture.uploadData(data, {}, false); 198 | return true; 199 | } 200 | 201 | //Returns best texture parameters for volume data 202 | Volume.prototype.guessTextureParams = function(){ 203 | var bytes = this.voxelBytes; 204 | var channels = this.voxelChannels; 205 | var type = this.voxelType; 206 | 207 | var guess = { 208 | typeString: "", 209 | formatString: "", 210 | internalFormatString: "", 211 | type: null, 212 | format: null, 213 | internalFormat: null 214 | }; 215 | 216 | guess.formatString = (channels == 1 ? "RED" : channels == 2 ? "RG" : channels == 3 ? "RGB" : "RGBA"); 217 | guess.internalFormatString = (channels == 1 ? "R" : channels == 2 ? "RG" : channels == 3 ? "RGB" : "RGBA") + (bytes == 1 ? "8" : bytes == 2 ? "16" : "32"); 218 | 219 | switch(type){ 220 | case "UI": 221 | guess.typeString = "UNSIGNED_"; 222 | guess.internalFormatString += "U"; 223 | case "I": 224 | guess.typeString += (bytes == 1 ? "BYTE" : bytes == 2 ? "SHORT" : "INT"); 225 | guess.formatString += "_INTEGER"; 226 | guess.internalFormatString += "I"; 227 | break; 228 | case "F": 229 | guess.typeString = "FLOAT"; //1byte can't be float, 2 and 4 bytes can pass a FloatArray (there aren't HalfFloatArrays in JS) 230 | break; 231 | default: 232 | guess.typeString = "UNSIGNED_BYTE"; 233 | break; 234 | } 235 | 236 | guess.type = gl[guess.typeString]; 237 | guess.format = gl[guess.formatString]; 238 | guess.internalFormat = gl[guess.internalFormatString]; 239 | 240 | return guess; 241 | } 242 | 243 | /*** 244 | * ==TransferFunction class== 245 | * Represents a RGBA TransferFunction (1 byte per value) 246 | ***/ 247 | var TransferFunction = function TransferFunction(){ 248 | this.width = 256; 249 | 250 | this._view = null; 251 | this._texture = null; 252 | this._needUpload = false; 253 | 254 | this.init(); 255 | } 256 | 257 | TransferFunction.prototype.init = function(values){ 258 | //Create arraybuffer with addecuate size (deletes previous data) 259 | this._view = new Uint8Array(this.width * 4); 260 | if(values) 261 | this._view.set(values, 0); 262 | 263 | } 264 | 265 | TransferFunction.prototype.fromPoints = function(points){ 266 | //Fill buffer data 267 | var i, t, r, g, b, a; 268 | i = t = r = g = b = a = 0; 269 | 270 | for(var pos=0; pos<4*this.width; pos+=4){ 271 | var pos_01 = pos / (this.width-1) / 4; 272 | 273 | if(i < points.length && pos_01 > points[i].x) i++; 274 | if(points.length == 0){ 275 | r = g = b = a = 0; 276 | }else if(i == 0){ 277 | r = points[i].r; 278 | g = points[i].g; 279 | b = points[i].b; 280 | a = points[i].a; 281 | }else if(i == points.length){ 282 | r = points[i-1].r; 283 | g = points[i-1].g; 284 | b = points[i-1].b; 285 | a = points[i-1].a; 286 | }else{ 287 | if(points[i-1].x == points[i].x){ 288 | r = points[i].r; 289 | g = points[i].g; 290 | b = points[i].b; 291 | a = points[i].a; 292 | }else{ 293 | t = (pos_01-points[i-1].x)/(points[i].x-points[i-1].x); 294 | r = (1-t)*points[i-1].r + t*points[i].r; 295 | g = (1-t)*points[i-1].g + t*points[i].g; 296 | b = (1-t)*points[i-1].b + t*points[i].b; 297 | a = (1-t)*points[i-1].a + t*points[i].a; 298 | } 299 | } 300 | 301 | this._view[pos ] = Math.round(r * (this.width-1)); 302 | this._view[pos+1] = Math.round(g * (this.width-1)); 303 | this._view[pos+2] = Math.round(b * (this.width-1)); 304 | this._view[pos+3] = Math.round(a * (this.width-1)); 305 | } 306 | 307 | this._needUpload = true; 308 | } 309 | 310 | TransferFunction.prototype.update = function(){ 311 | if(this._needUpload){ 312 | this.updateTexture(); 313 | } 314 | } 315 | 316 | TransferFunction.prototype.updateTexture = function(){ 317 | if(this._texture != null){ 318 | //Update texture data in GPU 319 | this._texture.uploadData(this._view, {}, false); 320 | this._needUpload = false; 321 | } 322 | } 323 | 324 | TransferFunction.prototype.getTransferFunction = function(){ 325 | return this._view; 326 | } 327 | 328 | TransferFunction.prototype.getTexture = function(){ 329 | if(this._texture == null){ 330 | //Create GLTexture using that arraybuffer 331 | this._texture = new GL.Texture(this.width, 1, {texture_type: GL.TEXTURE_2D, format: gl.RGBA, magFilter: gl.NEAREST, pixel_data: this._view}); 332 | this._needUpload = false; 333 | } 334 | 335 | if(this._needUpload){ 336 | this.updateTexture(); 337 | } 338 | 339 | return this._texture; 340 | } 341 | 342 | TransferFunction.create = function(width, values){ 343 | var tf = new TransferFunction(); 344 | 345 | tf.width = width || tf.width; 346 | tf.init(); 347 | 348 | return tf; 349 | } 350 | 351 | TransferFunction.clone = function(tf){ 352 | return TransferFunction.create(tf.width, tf._view); 353 | } 354 | -------------------------------------------------------------------------------- /js/external/volume-loader.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | /*** 4 | * VOLUME-LOADER.js 5 | * Parsers for dicom, nifti and vl files 6 | ***/ 7 | 8 | /*** 9 | * ==VolumeLoader class== 10 | * Loads Volume objects from VL, Dicom or Nifti 11 | * Requires daikon.js for dicom files and nifti-reader.js for nifti files 12 | ***/ 13 | var VolumeLoader = {}; 14 | 15 | VolumeLoader.STARTING = 0; 16 | VolumeLoader.LOADINGFILES = 1; 17 | VolumeLoader.PARSINGFILES = 2; 18 | VolumeLoader.CREATINGVOLUMES = 3; 19 | VolumeLoader.DONE = 4; 20 | VolumeLoader.ERROR = 5; 21 | 22 | //Dicom utils 23 | VolumeLoader.DicomUtils = { 24 | TAGS: { 25 | modality : "00080060", 26 | studyDescription : "00081030", 27 | MRAcquisitionType : "00180023", //[1D, 2D, 3D] 28 | rows : "00280010", //# of rows 29 | columns : "00280011", //# of columns 30 | slices : "00201002", //# of images AKA slices, not allways defined! 31 | pixelSpacing : "00280030", //mm between 2 centers of pixels. Value[0] is for pixels in 2 adjacent rows and value[1] is for pixels in 2 djacent columns 32 | sliceThickness : "00180050", //mm between 2 centers of pixels in adjacent slices 33 | samplesPerPixel : "00280002", //[ 1 , 1 , 3 , 3 , 3 , 3 , 3 , 3 ] 34 | photometricInterpretation : "00280004", //[MONOCHROME2 , PALETTE COLOR , RGB , YBR_FULL , YBR_FULL_422 , YBR_RCT , YBR_ICT , YBR_PARTIAL_420 ] 35 | bitsAllocated : "00280100", //number of bits per value 36 | bitsStored : "00280101", //number of bits actually used, must be equal or less than bits allocated 37 | pixelData : "7FE00010", //if this tag exists the data is unsigned integer 38 | floatPixelData : "7FE00008", //if this tag exists the data is float 39 | doublePixelData : "7FE00009", //if this tag exists the data is double 40 | } 41 | }; 42 | 43 | //Nifti utils 44 | VolumeLoader.NiftiUtils = { 45 | DataTypes: { 46 | 0: "unknown", 47 | 1: "bool", 48 | 2: "unsigned char", 49 | 4: "signed short", 50 | 8: "signed int", 51 | 16: "float", 52 | 32: "complex", 53 | 64: "double", 54 | 128: "RGB", 55 | 255: "all", 56 | 256: "signed char", 57 | 512: "unsigned short", 58 | 768: "unsigned int", 59 | 1024: "long long", 60 | 1280: "unsigned long long", 61 | 1536: "long double", 62 | 1792: "double pair", 63 | 2048: "long double pair", 64 | 2304: "RGBA" 65 | } 66 | }; 67 | 68 | //Returns an array of values. Usually only contains 1 value inside the array. => Check Dicom Standard 69 | VolumeLoader.DicomUtils.getValue = function(image, tag){ 70 | if(image.tags[tag]) 71 | return image.tags[tag].value; 72 | return null; 73 | } 74 | 75 | VolumeLoader.loadFile = function(file, callback){ 76 | var reader = new FileReader(); 77 | reader.onloadend = function(event){ 78 | if(event.target.readyState === FileReader.DONE){ 79 | var buffer = event.target.result; 80 | callback(buffer); 81 | } 82 | } 83 | reader.readAsArrayBuffer(file); 84 | } 85 | 86 | VolumeLoader.loadVLFiles = async function(files, onend, oninfo){ 87 | var response = {}; //Info like progress to provide to callback while is parsing and creating Volumes 88 | 89 | response.status = VolumeLoader.STARTING; 90 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 91 | 92 | var currentFile = 0; 93 | var totalFiles = files.length; 94 | 95 | var buffers = []; 96 | 97 | function readFile(){ 98 | if(currentFile < totalFiles){ 99 | VolumeLoader.loadFile(files[currentFile++], onFileLoaded); 100 | }else{ 101 | VolumeLoader.parseVLBuffers(buffers, onend, oninfo); 102 | } 103 | } 104 | 105 | function onFileLoaded(buffer){ 106 | buffers.push(buffer); 107 | readFile(); 108 | } 109 | 110 | response.status = VolumeLoader.LOADINGFILES; 111 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 112 | readFile(); 113 | } 114 | 115 | VolumeLoader.parseVLBuffers = async function(buffers, onend, oninfo){ 116 | var response = {}; //Info like progress to provide to callback while is parsing and creating Volumes 117 | 118 | response.status = VolumeLoader.PARSINGFILES; 119 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 120 | 121 | var vls = []; 122 | var volumes = []; 123 | 124 | for(var buffer of buffers){ 125 | var vl = VolumeLoader.parseVL(buffer); 126 | 127 | if(vl.data){ 128 | vl.buffer = buffer; 129 | vls.push(vl); 130 | }else{ 131 | response.status = VolumeLoader.ERROR; 132 | response.explanation = "Could not parse VL file with version " + vl.version; 133 | if(oninfo) oninfo(response); 134 | } 135 | } 136 | 137 | if(vls.length == 0){ 138 | response.status = VolumeLoader.ERROR; 139 | response.explanation = "There are no valid VLs."; 140 | onend(response); 141 | return; 142 | } 143 | 144 | //Create a volume for each volume 145 | response.status = VolumeLoader.CREATINGVOLUMES; 146 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 147 | 148 | for(var vl of vls){ 149 | var volume = new Volume({ 150 | width: vl.width, 151 | height: vl.height, 152 | depth: vl.depth, 153 | widthSpacing: vl.widthSpacing, 154 | heightSpacing: vl.heightSpacing, 155 | depthSpacing: vl.depthSpacing, 156 | voxelChannels: vl.voxelChannels, 157 | voxelBytes: vl.voxelBytes, 158 | buffer: vl.data 159 | }); 160 | vl.volume = volume; 161 | volumes.push(volume); 162 | } 163 | 164 | response.status = VolumeLoader.DONE; 165 | response.vls = vls; 166 | response.volume = volumes[0]; 167 | response.volumes = volumes; 168 | 169 | onend(response); //Final callback, it does contain volumes (and also raw images and series from daikon) 170 | } 171 | 172 | VolumeLoader.parseVL = function(buffer){ 173 | var view32 = new Uint32Array(buffer); 174 | var view32F = new Float32Array(buffer); 175 | var vl = { 176 | version: view32[0], 177 | }; 178 | 179 | if(vl.version == 1){ 180 | vl = VolumeLoader._parseVL1(buffer, view32, view32F); 181 | }else if(vl.version == 2){ 182 | vl = VolumeLoader._parseVL2(buffer, view32, view32F); 183 | } 184 | 185 | return vl; 186 | } 187 | 188 | VolumeLoader._parseVL1 = function(buffer, view32, view32F){ 189 | var headerElements = 9; 190 | var vl = {}; 191 | vl.version = view32[0]; 192 | vl.width = view32[1]; 193 | vl.height = view32[2]; 194 | vl.depth = view32[3]; 195 | vl.widthSpacing = view32F[4]; 196 | vl.heightSpacing = view32F[5]; 197 | vl.depthSpacing = view32F[6]; 198 | vl.voxelChannels = view32[7]; 199 | vl.voxelBytes = view32[8] / 8; 200 | vl.voxelType = "UI"; 201 | vl.data = buffer.slice(4*headerElements); 202 | return vl; 203 | } 204 | 205 | VolumeLoader._parseVL2 = function(buffer, view32, view32F){ 206 | var headerElements = 10; 207 | var vl = {}; 208 | vl.version = view32[0]; 209 | vl.width = view32[1]; 210 | vl.height = view32[2]; 211 | vl.depth = view32[3]; 212 | vl.widthSpacing = view32F[4]; 213 | vl.heightSpacing = view32F[5]; 214 | vl.depthSpacing = view32F[6]; 215 | vl.voxelChannels = view32[7]; 216 | vl.voxelBytes = view32[8] / 8; 217 | vl.voxelType = view32[9] == 0 ? "UI" : view32[9] == 1 ? "I" : view32[9] == 2 ? "F" : "O"; 218 | vl.data = buffer.slice(4*headerElements); 219 | return vl; 220 | } 221 | 222 | VolumeLoader.loadDicomFiles = async function(files, onend, oninfo){ 223 | var response = {}; //Info like progress to provide to callback while is parsing and creating Volumes 224 | 225 | if(daikon === undefined){ 226 | console.warn("Library daikon.js is needed to perfom this action."); 227 | response.status = VolumeLoader.ERROR; 228 | response.explanation = "Library daikon.js is needed to perfom this action." 229 | onend(response); 230 | return; 231 | } 232 | 233 | response.status = VolumeLoader.STARTING; 234 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 235 | 236 | var currentFile = 0; 237 | var totalFiles = files.length; 238 | 239 | var buffers = []; 240 | 241 | function readFile(){ 242 | if(currentFile < totalFiles){ 243 | VolumeLoader.loadFile(files[currentFile++], onFileLoaded); 244 | }else{ 245 | VolumeLoader.parseDicomBuffers(buffers, onend, oninfo); 246 | } 247 | } 248 | 249 | function onFileLoaded(buffer){ 250 | buffers.push(buffer); 251 | readFile(); 252 | } 253 | 254 | response.status = VolumeLoader.LOADINGFILES; 255 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 256 | readFile(); 257 | } 258 | 259 | VolumeLoader.parseDicomBuffers = async function(buffers, onend, oninfo){ 260 | var response = {}; //Info like progress to provide to callback while is parsing and creating Volumes 261 | 262 | if(daikon === undefined){ 263 | console.warn("Library daikon.js is needed to perfom this action."); 264 | response.status = VolumeLoader.ERROR; 265 | response.explanation = "Library daikon.js is needed to perfom this action." 266 | onend(response); 267 | return; 268 | } 269 | 270 | response.status = VolumeLoader.PARSINGFILES; 271 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 272 | 273 | var series = {}; //Map seriesId of image with a serie 274 | var volumes = []; //Contains a volume for each serie 275 | 276 | //Extract images from dicoms and assign each to a serie 277 | for(var buffer of buffers){ 278 | var image = daikon.Series.parseImage(new DataView(buffer)) 279 | 280 | if(image !== null && image.hasPixelData()){ 281 | var seriesId = image.getSeriesId(); 282 | if(!series[seriesId]){ 283 | series[seriesId] = new daikon.Series(); 284 | series[seriesId].buffers = []; 285 | series[seriesId].volume = null; 286 | } 287 | 288 | series[seriesId].addImage(image); 289 | series[seriesId].buffers.push(buffer); 290 | } 291 | } 292 | 293 | if(Object.keys(series).length == 0){ 294 | response.status = VolumeLoader.ERROR; 295 | response.explanation = "There are no valid Dicoms."; 296 | onend(response); 297 | return; 298 | } 299 | 300 | //Create a volume for each serie 301 | response.status = VolumeLoader.CREATINGVOLUMES; 302 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 303 | for(var seriesId in series){ 304 | var serie = series[seriesId]; 305 | 306 | serie.buildSeries(); 307 | 308 | var width = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.columns)[0]; 309 | var height = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.rows)[0]; 310 | var depth = serie.images.length; 311 | 312 | var widthSpacing = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.pixelSpacing)[0] || 1; 313 | var heightSpacing = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.pixelSpacing)[1] || 1; 314 | var depthSpacing = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.sliceThickness)[0] || 1; 315 | 316 | var voxelChannels = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.samplesPerPixel)[0] || 1; 317 | var voxelBytes = VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.bitsAllocated)[0]/8 || 1; 318 | if(voxelBytes == 1/8){ //binary data 319 | //TODO 320 | voxelBytes = 1; 321 | } 322 | 323 | var totalVoxels = width * height * depth; 324 | var totalBytes = totalVoxels * voxelBytes * voxelChannels; 325 | var sliceValues = width * height * voxelChannels; 326 | 327 | var buffer = new ArrayBuffer(totalBytes); 328 | var view; 329 | 330 | var voxelType = "O"; 331 | if(VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.pixelData) != null){ //INTEGER DATA 332 | if(serie.images[0].getPixelRepresentation()){ //SIGNED 333 | voxelType = "I"; 334 | if(voxelBytes == 1){ 335 | view = new Int8Array(buffer); 336 | }else if(voxelBytes == 2){ 337 | view = new Int16Array(buffer); 338 | }else{ 339 | view = new Int32Array(buffer); 340 | } 341 | }else{ //UNSIGNED 342 | voxelType = "UI"; 343 | if(voxelBytes == 1){ 344 | view = new Uint8Array(buffer); 345 | }else if(voxelBytes == 2){ 346 | view = new Uint16Array(buffer); 347 | }else{ 348 | view = new Uint32Array(buffer); 349 | } 350 | } 351 | }else if(VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.floatPixelData) != null || //FLOAT DATA 352 | VolumeLoader.DicomUtils.getValue(serie.images[0], VolumeLoader.DicomUtils.TAGS.doublePixelData) != null){ 353 | voxelType = "F"; 354 | if(voxelBytes == 4){ 355 | view = new Float32Array(buffer); 356 | }else{ 357 | view = new Float64Array(buffer); 358 | } 359 | } 360 | 361 | var min = Infinity; 362 | var max = -Infinity; 363 | 364 | for(var i=0; i max) max = imageDataObject.max; 369 | var imageData = imageDataObject.data; 370 | view.set(imageData, i * sliceValues); 371 | } 372 | 373 | var volume = new Volume({ 374 | width: width, 375 | height: height, 376 | depth: depth, 377 | widthSpacing: widthSpacing, 378 | heightSpacing: heightSpacing, 379 | depthSpacing: depthSpacing, 380 | voxelChannels: voxelChannels, 381 | voxelBytes: voxelBytes, 382 | voxelType: voxelType, 383 | data: view 384 | }); 385 | serie.volume = volume; 386 | volumes.push(volume); 387 | } 388 | 389 | response.status = VolumeLoader.DONE; 390 | response.series = series; 391 | response.volume = volumes[0]; 392 | response.volumes = volumes; 393 | 394 | onend(response); //Final callback, it does contain volumes (and also raw images and series from daikon) 395 | } 396 | 397 | VolumeLoader.loadNiftiFiles = function(files, onend, oninfo){ 398 | var response = {}; //Info like progress to provide to callback while is parsing and creating Volumes 399 | 400 | if(nifti === undefined){ 401 | console.warn("Library nifti-reader.js is needed to perfom this action."); 402 | response.status = VolumeLoader.ERROR; 403 | response.explanation = "Library nifti-reader.js is needed to perfom this action." 404 | onend(response); 405 | return; 406 | } 407 | 408 | response.status = VolumeLoader.STARTING; 409 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 410 | 411 | var currentFile = 0; 412 | var totalFiles = files.length; 413 | 414 | var buffers = []; 415 | 416 | function readFile(){ 417 | if(currentFile < totalFiles){ 418 | VolumeLoader.loadFile(files[currentFile++], onFileLoaded); 419 | }else{ 420 | VolumeLoader.parseNiftiBuffers(buffers, onend, oninfo); 421 | } 422 | } 423 | 424 | function onFileLoaded(buffer){ 425 | buffers.push(buffer); 426 | readFile(); 427 | } 428 | 429 | response.status = VolumeLoader.LOADINGFILES; 430 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 431 | readFile(); 432 | } 433 | 434 | VolumeLoader.parseNiftiBuffers = function(buffers, onend, oninfo){ 435 | var response = {}; //Info like progress to provide to callback while is parsing and creating Volumes 436 | 437 | if(nifti === undefined){ 438 | console.warn("Library nifti-reader.js is needed to perfom this action."); 439 | response.status = VolumeLoader.ERROR; 440 | response.explanation = "Library nifti-reader.js is needed to perfom this action." 441 | onend(response); 442 | return; 443 | } 444 | 445 | response.status = VolumeLoader.PARSINGFILES; 446 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 447 | 448 | var niftis = []; //Contains all nifti objects 449 | var volumes = []; //Contains a volume for each serie 450 | 451 | for(var buffer of buffers){ 452 | var niftiData = buffer; 453 | var niftiHeader = null; 454 | 455 | if (nifti.isCompressed(niftiData)) { 456 | niftiData = nifti.decompress(niftiData); 457 | } 458 | 459 | if (nifti.isNIFTI(niftiData)) { 460 | niftiHeader = nifti.readHeader(niftiData); 461 | 462 | niftis.push({ 463 | niftiHeader: niftiHeader, 464 | buffer: buffer, 465 | volume: null, 466 | }); 467 | }else{ 468 | response.status = VolumeLoader.ERROR; 469 | response.explanation = "Nifti file does not contain data." 470 | onend(response); 471 | } 472 | } 473 | 474 | if(niftis.length == 0){ 475 | response.status = VolumeLoader.ERROR; 476 | response.explanation = "There are no valid Niftis."; 477 | onend(response); 478 | return; 479 | } 480 | 481 | response.status = VolumeLoader.CREATINGVOLUMES; 482 | if(oninfo) oninfo(response); //Informative callback, it does not contain volumes yet 483 | 484 | for(var nii of niftis){ 485 | var niftiHeader = nii.niftiHeader; 486 | var niftiData = nii.buffer; 487 | 488 | if(niftiHeader.dims[0] > 3){ 489 | console.log("Nifti data has more dimensions than 3, using only 3 first dimensions."); 490 | } 491 | 492 | var width = niftiHeader.dims[1]; 493 | var height = niftiHeader.dims[2]; 494 | var depth = niftiHeader.dims[3]; 495 | 496 | var widthSpacing = niftiHeader.pixDims[1]; 497 | var heightSpacing = niftiHeader.pixDims[2]; 498 | var depthSpacing = niftiHeader.pixDims[3]; 499 | 500 | var voxelBytes = niftiHeader.numBitsPerVoxel / 8; 501 | var voxelBuffer = nifti.readImage(niftiHeader, niftiData); 502 | var voxelView = null; 503 | var voxelType = "O"; 504 | switch(niftiHeader.datatypeCode){ 505 | case 2: //unsigned char (byte) 506 | case 128: //RGB bytes 507 | case 2304: //RGBA bytes 508 | voxelType = "UI"; 509 | voxelView = new Uint8Array(voxelBuffer); 510 | break; 511 | case 512: //unsigned short 512 | voxelType = "UI"; 513 | voxelView = new Uint16Array(voxelBuffer); 514 | break; 515 | case 768: //unsigned int 516 | voxelType = "UI"; 517 | voxelView = new Uint32Array(voxelBuffer); 518 | break; 519 | 520 | case 256: //signed byte 521 | voxelType = "I"; 522 | voxelView = new Int8Array(voxelBuffer); 523 | break; 524 | case 4: //signed short 525 | voxelType = "I"; 526 | voxelView = new Int16Array(voxelBuffer); 527 | break; 528 | case 8: //signed int 529 | voxelType = "I"; 530 | voxelView = new Int32Array(voxelBuffer); 531 | break; 532 | 533 | default: 534 | console.warn("Data type not covered, returning empty volume. Check dataTypeName for more info to manually create adequate view of voxelBuffer."); 535 | } 536 | 537 | var volume = new Volume({ 538 | width: width, 539 | height: height, 540 | depth: depth, 541 | widthSpacing: widthSpacing, 542 | heightSpacing: heightSpacing, 543 | depthSpacing: depthSpacing, 544 | voxelBytes: voxelBytes, 545 | voxelType: voxelType, 546 | data: voxelView 547 | }); 548 | nii.dataTypeName = VolumeLoader.NiftiUtils.DataTypes[niftiHeader.datatypeCode]; 549 | nii.volume = volume; 550 | volumes.push(volume); 551 | } 552 | 553 | response.status = VolumeLoader.DONE; 554 | response.niftis = niftis; 555 | response.volume = volumes[0]; 556 | response.volumes = volumes; 557 | 558 | onend(response); //Final callback, it does contain volumes (and also raw nifti) 559 | } 560 | 561 | VolumeLoader.getVolumeAsVL1Buffer = function(volume){ 562 | var vl1HeaderElements = 9; 563 | var headerSize = 4*vl1HeaderElements; //4 bytes per number in header 564 | 565 | var buffer = new ArrayBuffer(volume._data.byteLength + headerSize); 566 | 567 | var view32 = new Uint32Array(buffer); 568 | var view32F = new Float32Array(buffer); 569 | view32[0] = 1; 570 | view32[1] = volume.width; 571 | view32[2] = volume.height; 572 | view32[3] = volume.depth; 573 | view32F[4] = volume.widthSpacing; 574 | view32F[5] = volume.heightSpacing; 575 | view32F[6] = volume.depthSpacing; 576 | view32[7] = volume.voxelChannels; 577 | view32[8] = volume.voxelBytes * 8; 578 | 579 | var view8 = new Uint8Array(buffer); 580 | var dataview8 = new Uint8Array(volume._data.buffer); 581 | view8.set(dataview8, headerSize); 582 | 583 | return view8; 584 | } 585 | 586 | VolumeLoader.getVolumeAsVL2Buffer = function(volume){ 587 | var vl1HeaderElements = 10; 588 | var headerSize = 4*vl1HeaderElements; //4 bytes per number in header 589 | 590 | var buffer = new ArrayBuffer(volume._data.byteLength + headerSize); 591 | 592 | var view32 = new Uint32Array(buffer); 593 | var view32F = new Float32Array(buffer); 594 | view32[0] = 2; 595 | view32[1] = volume.width; 596 | view32[2] = volume.height; 597 | view32[3] = volume.depth; 598 | view32F[4] = volume.widthSpacing; 599 | view32F[5] = volume.heightSpacing; 600 | view32F[6] = volume.depthSpacing; 601 | view32[7] = volume.voxelChannels; 602 | view32[8] = volume.voxelBytes * 8; 603 | view32[9] = volume.voxelType == "UI" ? 0 : volume.voxelType == "I" ? 1 : volume.voxelType == "F" ? 2 : 3; 604 | 605 | var view8 = new Uint8Array(buffer); 606 | var dataview8 = new Uint8Array(volume._data.buffer); 607 | view8.set(dataview8, headerSize); 608 | 609 | return view8; 610 | } 611 | 612 | VolumeLoader.downloadVL = function(volume){ 613 | var view8 = VolumeLoader.getVolumeAsVL2Buffer(volume); 614 | var blob = new Blob([view8]); 615 | var fakeUrl = URL.createObjectURL(blob); 616 | var element = document.createElement("a"); 617 | element.setAttribute('href', fakeUrl); 618 | element.setAttribute('download', "texture3d.vl" ); 619 | element.style.display = 'none'; 620 | document.body.appendChild(element); 621 | element.click(); 622 | document.body.removeChild(element); 623 | }; -------------------------------------------------------------------------------- /js/sceneTools.js: -------------------------------------------------------------------------------- 1 | 2 | /** --- Víctor Ubieto 2020 --- 3 | * This file contains some useful classes used in the application, these file also uses litegl methods and classes 4 | **/ 5 | 6 | "use strict" 7 | 8 | /** 9 | * ----- Mouse Class ----- 10 | * It controls the mouse state 11 | **/ 12 | 13 | function Mouse() 14 | { 15 | this._button = 0; 16 | this._pos_x = 0; 17 | this._pos_y = 0; 18 | this._delta_x = 0; 19 | this._delta_y = 0; 20 | this._drag_state = false; 21 | this._wheel_value = 0; 22 | this._wheel_state = false; 23 | 24 | this.initListeners(); 25 | } 26 | 27 | Mouse.prototype.initListeners = function() 28 | { 29 | var that = this; //to use in the callbacks 30 | 31 | gl.captureMouse(true); 32 | gl.onmousemove = function(e) 33 | { 34 | that._button = e.buttons; // 1 left, 2 right, 4 middle 35 | that._pos_x = e.canvasx; 36 | that._pos_y = e.canvasy; 37 | that._drag_state = e.dragging; 38 | that._delta_x = e.deltax; // - 1 left to 1 right 39 | that._delta_y = e.deltay; // - 1 top to 1 down 40 | } 41 | gl.onmouseup = function(e) 42 | { 43 | that._drag_state = false; 44 | that._delta_x = 0; 45 | that._delta_y = 0; 46 | } 47 | gl.onmousewheel = function(e) 48 | { 49 | that._wheel_value = e.wheel; 50 | that._wheel_state = true; 51 | } 52 | 53 | //gl.captureKeys(true); not used 54 | } 55 | 56 | /** 57 | * ----- Entity Class ----- 58 | * It controls the objects 59 | **/ 60 | 61 | function Entity( options ) // options: type (name), size (width) 62 | { 63 | this._model_matrix = mat4.create(); 64 | this._modelview_matrix = mat4.create(); 65 | 66 | this._mesh = null; 67 | this._mesh_type = options.type; 68 | if (options) 69 | { 70 | if (options.type) 71 | switch(this._mesh_type) 72 | { 73 | case "cube": 74 | this._mesh = GL.Mesh.cube({size: options.size || 2}); 75 | break; 76 | case "sphere": // actually this won't work well on volume rendering 77 | this._mesh = GL.Mesh.sphere({radius: options.size/2 || 1}); 78 | break; 79 | }; 80 | if (options.size) 81 | mat4.scale( this._model_matrix, this._model_matrix, [options.size, options.size, options.size] ); 82 | } 83 | 84 | } 85 | 86 | // Adjust camera to mesh bounding 87 | Entity.prototype.centerInView = function(camera) 88 | { 89 | camera._target = BBox.getCenter( this._mesh.bounding ); 90 | var r = BBox.getRadius( this._mesh.bounding ); 91 | camera._position = vec3.add( camera._position, camera._target, [0,r*0.5, r*3] ); 92 | } -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | 2 | /** --- Víctor Ubieto 2020 --- 3 | * This file contains useful functions that are used in the framework 4 | **/ 5 | 6 | // Pass to hexadecimal to rgb (in bright color the conversion gives some errors) 7 | // (https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb) 8 | function hexToRgb(hex) { 9 | var conversion = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 10 | var result = [0,0,0]; //init 11 | result[0] = parseInt(conversion[1], 16)/128; 12 | result[1] = parseInt(conversion[2], 16)/128; 13 | result[2] = parseInt(conversion[3], 16)/128; 14 | return result; 15 | } 16 | 17 | // Controls the dimensions of the window and the elements when modifying the size 18 | function resizeView() 19 | { 20 | var root = document.getElementById("editor"); 21 | var canvas = root.querySelector(".Graph"); 22 | canvas.height = root.offsetHeight; 23 | canvas.width = root.offsetWidth; 24 | 25 | root = document.getElementById("view-area"); 26 | canvas = root.querySelector(".View"); 27 | canvas.height = root.offsetHeight; 28 | canvas.width = root.offsetWidth; 29 | var rect = canvas.getBoundingClientRect(); 30 | gl.viewport(0, 0, rect.width, rect.height); 31 | } 32 | 33 | // Function that controls the values from the options panel 34 | function updateOptions(id) 35 | { 36 | switch (id) 37 | { 38 | case "in_bgcolor": 39 | var bgcolor = document.getElementById('in_bgcolor'); 40 | options.color_bg = hexToRgb(bgcolor.value); 41 | break; 42 | case "in_objcolor": 43 | var objcolor = document.getElementById('in_objcolor'); 44 | options.color_mesh = hexToRgb(objcolor.value); 45 | break; 46 | case "in_quality": 47 | var quality = document.getElementById('in_quality').value; 48 | options.quality = quality; 49 | break; 50 | case "in_brightness": 51 | var brightness = document.getElementById('in_brightness').value; 52 | options.brightness = brightness; 53 | break; 54 | } 55 | } 56 | 57 | // Download a File 58 | function downloadFile() 59 | { 60 | var body = `\\volume.vs 61 | ` + Previous_VS + ` 62 | 63 | \\volume.fs 64 | ` + Previous_FS; 65 | downloadString(body, "text/glsl", "shader_atlas.glsl"); 66 | } 67 | 68 | // Download a File (https://gist.github.com/danallison/3ec9d5314788b337b682) 69 | function downloadString(text, fileType, fileName) 70 | { 71 | var blob = new Blob([text], { type: fileType }); 72 | 73 | var a = document.createElement('a'); 74 | a.download = fileName; 75 | a.href = URL.createObjectURL(blob); 76 | a.dataset.downloadurl = [fileType, a.download, a.href].join(':'); 77 | a.style.display = "none"; 78 | document.body.appendChild(a); 79 | a.click(); 80 | document.body.removeChild(a); 81 | setTimeout(function() { URL.revokeObjectURL(a.href); }, 1500); 82 | } 83 | 84 | // Panel Functions (https://github.com/jagenjo/litegraph.js/blob/master/src/litegraph-editor.js) 85 | function onShowNodePanel(node) 86 | { 87 | window.SELECTED_NODE = node; 88 | var panel = document.querySelector("#node-panel"); 89 | if(panel) 90 | panel.close(); 91 | var ref_window = this.graphcanvas.getCanvasWindow(); 92 | panel = this.createPanel(node.title || "",{closable: true, window: ref_window }); 93 | panel.id = "node-panel"; 94 | panel.classList.add("settings"); 95 | var that = this; 96 | var graphcanvas = this.graphcanvas; 97 | 98 | function inner_refresh() 99 | { 100 | panel.content.innerHTML = ""; //clear 101 | panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); 102 | 103 | panel.addHTML("

Properties

"); 104 | 105 | for(var i in node.properties) 106 | { 107 | var value = node.properties[i]; 108 | var info = node.getPropertyInfo(i); 109 | var type = info.type || "string"; 110 | 111 | //in case the user wants control over the side panel widget 112 | if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(i,panel) ) 113 | continue; 114 | 115 | panel.addWidget( info.widget || info.type, i, value, info, function(name,value){ 116 | node.setProperty(name,value); 117 | graphcanvas.dirty_canvas = true; 118 | }); 119 | } 120 | 121 | panel.addSeparator(); 122 | 123 | panel.addButton("Delete",function(){ 124 | node.graph.remove(node); 125 | panel.close(); 126 | }).classList.add("delete"); 127 | } 128 | 129 | function inner_showCodePad( node, propname ) 130 | { 131 | panel.style.top = "calc( 50% - 250px)"; 132 | panel.style.left = "calc( 50% - 400px)"; 133 | panel.style.width = "800px"; 134 | panel.style.height = "500px"; 135 | 136 | if(window.CodeFlask) //disabled for now 137 | { 138 | panel.content.innerHTML = "
"; 139 | var flask = new CodeFlask( "div.code", { language: 'js' }); 140 | flask.updateCode(node.properties[propname]); 141 | flask.onUpdate( function(code) { 142 | node.setProperty(propname, code); 143 | }); 144 | } 145 | else 146 | { 147 | panel.content.innerHTML = ""; 148 | var textarea = panel.content.querySelector("textarea"); 149 | textarea.value = node.properties[propname]; 150 | textarea.addEventListener("keydown", function(e){ 151 | //console.log(e); 152 | if(e.code == "Enter" && e.ctrlKey ) 153 | { 154 | console.log("Assigned"); 155 | node.setProperty(propname, textarea.value); 156 | } 157 | }); 158 | textarea.style.height = "calc(100% - 40px)"; 159 | } 160 | var assign = that.createButton( "Assign", null, function(){ 161 | node.setProperty(propname, textarea.value); 162 | }); 163 | panel.content.appendChild(assign); 164 | var button = that.createButton( "Close", null, function(){ 165 | panel.style.height = ""; 166 | inner_refresh(); 167 | }); 168 | button.style.float = "right"; 169 | panel.content.appendChild(button); 170 | } 171 | 172 | inner_refresh(); 173 | 174 | // get the content 175 | var content = document.getElementById("graph-area"); 176 | 177 | content.appendChild( panel ); 178 | } 179 | 180 | // Panel Functions (https://github.com/jagenjo/litegraph.js/blob/master/src/litegraph-editor.js) 181 | function createPanel(title, options) 182 | { 183 | options = options || {}; 184 | 185 | var ref_window = options.window || window; 186 | var root = document.createElement("div"); 187 | root.className = "dialog"; 188 | root.innerHTML = "
"; 189 | root.header = root.querySelector(".dialog-header"); 190 | if(options.closable) 191 | { 192 | var close = document.createElement("span"); 193 | close.innerHTML = "✕"; 194 | close.classList.add("close"); 195 | close.addEventListener("click",function(){ 196 | root.close(); 197 | }); 198 | root.header.appendChild(close); 199 | } 200 | root.title_element = root.querySelector(".dialog-title"); 201 | root.title_element.innerText = title; 202 | root.content = root.querySelector(".dialog-content"); 203 | root.footer = root.querySelector(".dialog-footer"); 204 | root.close = function() 205 | { 206 | this.parentNode.removeChild(this); 207 | } 208 | 209 | root.addHTML = function(code, classname) 210 | { 211 | var elem = document.createElement("div"); 212 | if(classname) 213 | elem.className = classname; 214 | elem.innerHTML = code; 215 | root.content.appendChild(elem); 216 | return elem; 217 | } 218 | 219 | root.addButton = function( name, callback, options ) 220 | { 221 | var elem = document.createElement("button"); 222 | elem.innerText = name; 223 | elem.options = options; 224 | elem.addEventListener("click",callback); 225 | root.footer.appendChild(elem); 226 | return elem; 227 | } 228 | 229 | root.addSeparator = function() 230 | { 231 | var elem = document.createElement("div"); 232 | elem.className = "separator"; 233 | root.content.appendChild(elem); 234 | } 235 | 236 | root.addWidget = function( type, name, value, options, callback ) 237 | { 238 | options = options || {}; 239 | var str_value = String(value); 240 | if(type == "number") 241 | str_value = value.toFixed(3); 242 | 243 | var elem = document.createElement("div"); 244 | elem.className = "property"; 245 | elem.innerHTML = ""; 246 | elem.querySelector(".property_name").innerText = name; 247 | var value_element = elem.querySelector(".property_value"); 248 | value_element.innerText = str_value; 249 | elem.dataset["property"] = name; 250 | elem.dataset["type"] = options.type || type; 251 | elem.options = options; 252 | elem.value = value; 253 | 254 | //if( type == "code" ) 255 | // elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); }); 256 | if (type == "boolean") 257 | { 258 | elem.classList.add("boolean"); 259 | if(value) 260 | elem.classList.add("bool-on"); 261 | elem.addEventListener("click", function(){ 262 | //var v = node.properties[this.dataset["property"]]; 263 | //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; 264 | var propname = this.dataset["property"]; 265 | this.value = !this.value; 266 | this.classList.toggle("bool-on"); 267 | this.querySelector(".property_value").innerText = this.value ? "true" : "false"; 268 | innerChange(propname, this.value ); 269 | }); 270 | } 271 | else if (type == "string" || type == "number") 272 | { 273 | value_element.setAttribute("contenteditable",true); 274 | value_element.addEventListener("keydown", function(e){ 275 | if(e.code == "Enter") 276 | { 277 | e.preventDefault(); 278 | this.blur(); 279 | } 280 | }); 281 | value_element.addEventListener("blur", function(){ 282 | var v = this.innerText; 283 | var propname = this.parentNode.dataset["property"]; 284 | var proptype = this.parentNode.dataset["type"]; 285 | if( proptype == "number") 286 | v = Number(v); 287 | innerChange(propname, v); 288 | }); 289 | } 290 | else if (type == "enum") 291 | value_element.addEventListener("click", function(event){ 292 | var values = options.values || []; 293 | var propname = this.parentNode.dataset["property"]; 294 | var elem_that = this; 295 | var menu = new LiteGraph.ContextMenu(values,{ 296 | event: event, 297 | className: "dark", 298 | callback: inner_clicked 299 | }, 300 | ref_window); 301 | function inner_clicked(v, option, event) { 302 | //node.setProperty(propname,v); 303 | //graphcanvas.dirty_canvas = true; 304 | elem_that.innerText = v; 305 | innerChange(propname,v); 306 | return false; 307 | } 308 | }); 309 | 310 | root.content.appendChild(elem); 311 | 312 | function innerChange(name, value) 313 | { 314 | console.log("change",name,value); 315 | //that.dirty_canvas = true; 316 | if(options.callback) 317 | options.callback(name,value); 318 | if(callback) 319 | callback(name,value); 320 | } 321 | 322 | return elem; 323 | } 324 | 325 | return root; 326 | }; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 2 | /** --- Víctor Ubieto 2020 --- 3 | * Main file that controls the application 4 | **/ 5 | 6 | "use strict" 7 | 8 | // Global variables 9 | var shader_atlas = []; 10 | var shader; 11 | var gl; 12 | 13 | var camera = null; 14 | var mouse = null; 15 | var entity = null; 16 | 17 | const options = { 18 | quality: 64.0, 19 | color_bg: [0.82, 0.91, 0.98, 1.0], 20 | color_mesh: [1.0, 0.0, 1.0, 1.0], 21 | brightness: 1.0 22 | } 23 | 24 | const time = { 25 | last: 0, 26 | now: null, 27 | dt: null 28 | } 29 | 30 | // Executes the app // 31 | init(); 32 | 33 | function init() 34 | { 35 | // ----- INIT CANVAS FUNCTIONALITIES ----- 36 | //GraphCanvas 37 | var root = document.getElementById("editor"); 38 | initGraphCanvas(root); 39 | //WebGL View 40 | root = document.getElementById("view-area"); 41 | initWebGLView(root); 42 | //Init Buttons 43 | initListeners(); 44 | 45 | // ----- LOAD MATERIAL NEEDED ----- 46 | //Load the shaders from a file 47 | loadShaderAtlas(); 48 | //Load the nodes created 49 | addNodes(); 50 | 51 | // ----- INIT BASIC SCENE ----- 52 | //Creates some essential nodes on the initial canvas 53 | graphTemplate(); 54 | //Inits the scene to render 55 | createScene(); 56 | 57 | // ----- MAIN LOOP ----- 58 | onLoad(); 59 | } 60 | 61 | // ---------------------------------------- INIT ----------------------------------------------- // 62 | 63 | // Graph Canvas Creation 64 | function initGraphCanvas(container_id) 65 | { 66 | var canvas = container_id.querySelector(".Graph"); 67 | canvas.height = container_id.offsetHeight; 68 | canvas.width = container_id.offsetWidth; 69 | 70 | var graph = new LGraph(); 71 | var graphcanvas = new LGraphCanvas(canvas, graph); 72 | 73 | graphcanvas.background_image = "../graph_system/img/migrid.png"; 74 | 75 | graph.onAfterExecute = function() { 76 | graphcanvas.draw(true); 77 | }; 78 | 79 | window.graphcanvas = graphcanvas; 80 | window.graph = graph; 81 | 82 | graphcanvas.onShowNodePanel = onShowNodePanel.bind(this); 83 | } 84 | 85 | // WebGL Canvas Creation 86 | function initWebGLView(container_id) 87 | { 88 | var canvas = container_id.querySelector(".View"); 89 | canvas.height = container_id.offsetHeight; 90 | canvas.width = container_id.offsetWidth; 91 | gl = GL.create({canvas: canvas, version: 2}); //webgl2 for the 3d textures 92 | if( gl.webgl_version != 2 || !gl ){ 93 | alert("WebGL 2.0 not supported by your browser"); 94 | } 95 | gl.animate(); 96 | } 97 | 98 | // This function inits listeners (buttons, mouse, keys, ...) 99 | function initListeners() 100 | { 101 | // Set buttons 102 | var optButton = document.getElementById("options"); 103 | var viewShader = document.getElementById("viewShader"); 104 | var cleanGraph = document.getElementById("cleanGraph"); 105 | var aboutButton = document.getElementById("about"); 106 | 107 | optButton.addEventListener("click", function(){ 108 | w2popup.open({ 109 | width: 300, height: 300, 110 | url: 'readme.html', 111 | title: 'Visualization Options', 112 | body: `
113 |

114 |

Background Color

115 | 119 | 120 |

121 |

Mesh Color (surface rendering)

122 | 126 | 127 |

128 |

Quality:

129 | 130 |

131 | 140 |

Brightness:

141 | 142 | 151 |
`, 152 | onOpen : function () { 153 | console.log('opened'); 154 | } 155 | }); 156 | }, false); 157 | 158 | viewShader.addEventListener("click", function(){ 159 | w2popup.open({ 160 | width: 700, height: 500, 161 | title: 'Shaders in Use', 162 | body: '

Vertex Shader

' + 163 | '
' + Previous_VS + '
' + 164 | '

Fragment Shader

' + 165 | '
' + Previous_FS + '
', 166 | buttons: '', 167 | onOpen : function () { 168 | console.log('opened'); 169 | } 170 | }); 171 | }, false); 172 | 173 | cleanGraph.addEventListener("click", function(){ 174 | if (confirm("Do you want to clean the editor?")) { 175 | var length = graph._nodes_in_order.length; 176 | for (var i = 0; i < (length - 2); i++){ 177 | var node = graph._nodes_in_order[0]; 178 | node.graph.remove(node); 179 | } 180 | console.log("Existing graph deleted."); 181 | } else { 182 | console.log("Cleaning operation canceled."); 183 | } 184 | }, false); 185 | 186 | aboutButton.addEventListener("click", function(){ 187 | w2popup.load({ 188 | url: 'readme.html', 189 | showMax: true, 190 | width: 600, 191 | height: 500}); 192 | }, false); 193 | 194 | window.addEventListener("resize", resizeView.bind(this)); 195 | 196 | // Get mouse actions 197 | mouse = new Mouse(); 198 | } 199 | 200 | // This function reads the file that contains the shaders and store them 201 | function loadShaderAtlas() 202 | { 203 | GL.loadFileAtlas("shaders.glsl", function(files){ 204 | shader_atlas = files; //parsed file 205 | }); 206 | } 207 | 208 | // Basic Template that inits the web with the essential nodes 209 | function graphTemplate() 210 | { 211 | var node_color = LiteGraph.createNode("Input/Color"); 212 | node_color.pos = [150,75]; 213 | node_color.setValue([1.0,1.0,1.0]); 214 | graph.add(node_color); 215 | 216 | var node_math = LiteGraph.createNode("Operator/Math"); 217 | node_math.pos = [150,250]; 218 | node_math.properties.OP = "*"; 219 | graph.add(node_math); 220 | 221 | var node_tra1 = LiteGraph.createNode("Operator/Translate"); 222 | node_tra1.pos = [50,575]; 223 | node_tra1.setY(-1.0); 224 | graph.add(node_tra1); 225 | 226 | var node_noise = LiteGraph.createNode("Texture/Noise"); 227 | node_noise.pos = [50,400]; 228 | node_noise.setScale(2.0); 229 | node_noise.setDetail(2.0); 230 | graph.add(node_noise); 231 | 232 | var node_rot = LiteGraph.createNode("Operator/Rotate"); 233 | node_rot.pos = [400,575]; 234 | node_rot.setZ(-90.0); 235 | graph.add(node_rot); 236 | 237 | var node_grad = LiteGraph.createNode("Texture/Gradient"); 238 | node_grad.pos = [400,450]; 239 | graph.add(node_grad); 240 | 241 | var node_volume = LiteGraph.createNode("Shader/Volume"); 242 | node_volume.pos = [350,175]; 243 | graph.add(node_volume); 244 | 245 | var node_out = LiteGraph.createNode("Output/Material Output"); 246 | node_out.pos = [600,250]; 247 | graph.add(node_out); 248 | 249 | //Connections 250 | node_color.connect(0, node_volume, 0); 251 | node_math.connect(0, node_volume, 1); 252 | node_tra1.connect(0, node_noise, 0); 253 | node_noise.connect(1, node_math, 0); 254 | node_rot.connect(0, node_grad, 0); 255 | node_grad.connect(1, node_math, 1); 256 | node_volume.connect(0, node_out, 0); 257 | } 258 | 259 | function createScene() 260 | { 261 | //create camera 262 | camera = new RD.Camera({position: [0,10,10], aspect: gl.canvas.width / gl.canvas.height}); 263 | 264 | //create default mesh 265 | entity = new Entity({type: "cube", size: 2}); 266 | 267 | //adjust camera to mesh bounding 268 | entity.centerInView(camera); 269 | 270 | //Basic shader while the parser hasn't read the other shaders yet 271 | var fastVS = ` 272 | precision highp float; 273 | attribute vec3 a_vertex; 274 | attribute vec3 a_normal; 275 | attribute vec4 a_color; 276 | uniform mat4 u_mvp; 277 | uniform mat4 u_model; 278 | varying vec3 v_normal; 279 | varying vec4 v_color; 280 | void main() { 281 | v_normal = (u_model * vec4(a_normal,0.0)).xyz; 282 | gl_Position = u_mvp * vec4(a_vertex,1.0); 283 | } 284 | `; 285 | var fastFS = ` 286 | precision highp float; 287 | varying vec3 v_normal; 288 | uniform vec4 u_camera_position; 289 | uniform vec4 u_color; 290 | 291 | uniform vec3 u_vertex; 292 | void main() { 293 | vec4 final_color = u_color; 294 | gl_FragColor = vec4( final_color.xyz, 1.0 ); 295 | } 296 | `; 297 | 298 | //Basic shader 299 | shader = new Shader( fastVS, fastFS ); 300 | } 301 | 302 | // ---------------------------------------- RENDER ----------------------------------------------- // 303 | 304 | // Main Loop 305 | function onLoad() 306 | { 307 | window.graph.runStep(); 308 | 309 | time.last = time.now || 0; 310 | time.now = getTime(); 311 | time.dt = (time.now - time.last) * 0.001; 312 | update(time.dt); 313 | render(); 314 | 315 | requestAnimationFrame( onLoad.bind(this) ); 316 | } 317 | 318 | function render() 319 | { 320 | //clear 321 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 322 | gl.clearColor(options.color_bg[0], options.color_bg[1], options.color_bg[2], options.color_bg[3]); 323 | 324 | //generic gl flags and settings 325 | gl.disable(gl.DEPTH_TEST); 326 | gl.enable(gl.CULL_FACE); 327 | gl.cullFace(gl.BACK); 328 | 329 | var inv_model = mat4.create(); 330 | mat4.invert(inv_model, entity._model_matrix); 331 | 332 | //get local camera position (makes it easier in the shader) 333 | var aux_vec4 = vec4.fromValues(camera._position[0], camera._position[1], camera._position[2], 1); 334 | vec4.transformMat4(aux_vec4, aux_vec4, inv_model); 335 | var local_cam_pos = vec3.fromValues(aux_vec4[0]/aux_vec4[3], aux_vec4[1]/aux_vec4[3], aux_vec4[2]/aux_vec4[3]); 336 | 337 | if (shader != null) 338 | { 339 | //render mesh using the shader 340 | if(entity._mesh) 341 | shader.uniforms({ 342 | u_camera_position: camera._position, 343 | u_local_camera_position: local_cam_pos, 344 | u_model: entity._model_matrix, 345 | u_obj_size: entity._mesh.size/2.0 || entity._mesh.radius, //divided by 2 because it is centered in 0 346 | u_mvp: camera._viewprojection_matrix, 347 | u_color: options.color_mesh, 348 | u_quality: options.quality, 349 | u_brightness: options.brightness 350 | }).draw(entity._mesh); 351 | } 352 | } 353 | 354 | function update(dt) 355 | { 356 | updateCamera(dt); 357 | } 358 | 359 | function updateCamera(dt) 360 | { 361 | if (mouse._button == 1 || mouse._button == 4) //left,center: orbit 362 | { 363 | if (mouse._drag_state) 364 | { 365 | var yaw = -mouse._delta_x * dt * 0.7; 366 | var pitch = -mouse._delta_y * dt * 0.7; 367 | orbitCamera(yaw, pitch); 368 | 369 | mouse._drag_state = false; 370 | } 371 | } 372 | if (mouse._button == 2) //right: pan 373 | { 374 | if (mouse._drag_state) 375 | { 376 | camera.moveLocal([-mouse._delta_x * dt * 0.3, mouse._delta_y * dt * 0.3, 0], camera._fov/45); 377 | mouse._drag_state = false; 378 | } 379 | } 380 | if (mouse._wheel_state) //wheel: zoom 381 | { 382 | zoomCamera(dt); 383 | mouse._wheel_state = false; 384 | } 385 | 386 | //update camera 387 | mat4.lookAt(camera._view_matrix, camera._position, camera._target, [0,1,0]); 388 | mat4.perspective(camera._projection_matrix, camera._fov * DEG2RAD, camera._aspect, 0.1, 1000); 389 | 390 | //update modelview and projection matrices 391 | mat4.multiply(entity._modelview_matrix, camera._view_matrix, entity._model_matrix); 392 | mat4.multiply(camera._viewprojection_matrix, camera._projection_matrix, camera._view_matrix); 393 | } 394 | 395 | function orbitCamera(yaw, pitch) 396 | { 397 | camera.orbit(yaw, camera._up); 398 | 399 | var front = vec3.create(); 400 | vec3.subtract(front, camera._target, camera._position) 401 | vec3.normalize(front, front); 402 | var up = vec3.clone(camera._up); 403 | vec3.normalize(up, up); 404 | var problem_angle = vec3.dot(front, up); 405 | if(!((problem_angle > 0.99 && pitch > 0) || (problem_angle < -0.99 && pitch < 0))) 406 | { 407 | var right = vec3.create(); 408 | camera.getLocalVector([1.0, 0, 0], right); 409 | camera.orbit(pitch, right); 410 | } 411 | } 412 | 413 | function zoomCamera(dt) 414 | { 415 | camera._fov += -mouse._wheel_value * dt; 416 | camera._fov = Math.max(0.0, camera._fov); 417 | } -------------------------------------------------------------------------------- /readme.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Node-Based Shader Editor

Víctor Ubieto, 2020 4 |
5 |
This is the result of my final degree project, which consist on a node-based shader editor implemented on web. The framework is based mainly on the usage of this libraries: 6 |
7 |
- Litegl.js (https://github.com/jagenjo/litegl.js) It helps simplifying working with WebGL. 8 |
- Litegraph.js (https://github.com/jagenjo/litegraph.js) A library in Javascript to create graphs in the browser. 9 |
10 |
11 |

Functionalities

12 |
This is the list of the most important functionalities of the application. 13 |
14 |
- Manipulation of the Volume Render algorithm via nodes. 15 |
- Load and visualization of Dicom datasets. 16 |
- Manipulation of the datasets by editing the transfer function. 17 |
- Download of the Vertex Shader and Fragment Shader createds by the graph editor. 18 |
19 |
20 |

List of Nodes

21 |
This is the list of the nodes currently available. There are more comming soon, so stay tunned. 22 |
23 |
- Input: Number, Color, Coordinates. 24 |
- Texture: Gradient, Noise, Dicom, Transfer Function. 25 |
- Operator: Math, MixRGB, ColorRamp, Translate, Scale, Rotate, Separate, Combine. 26 |
- Shader: Volume. 27 |
- Output: Material Output. 28 |
29 |
30 |

Other libraries used

31 |
- W2ui.js (https://github.com/vitmalina/w2ui) 32 |
- JsColor.js (https://github.com/EastDesire/jscolor) 33 |
- Rendeer.js (https://github.com/jagenjo/rendeer.js) 34 |
- Volume-base.js (https://github.com/upf-gti/Volumetrics/blob/master/src/volume-base.js) 35 |
- Volume-loader.js (https://github.com/upf-gti/Volumetrics/blob/master/src/volume-loader.js) 36 |
- JQuery.js (https://github.com/jquery/jquery) 37 |
- Gl-Matrix.js (https://github.com/toji/gl-matrix) 38 |
- Daikon.js (https://github.com/rii-mango/Daikon) 39 |
40 |
41 |
-------------------------------------------------------------------------------- /shaders.glsl: -------------------------------------------------------------------------------- 1 | \basicVS 2 | 3 | #version 300 es 4 | precision highp float; 5 | 6 | in vec3 a_vertex; 7 | in vec3 a_normal; 8 | in vec4 a_color; 9 | 10 | out vec3 v_normal; 11 | out vec4 v_color; 12 | 13 | uniform mat4 u_mvp; 14 | uniform mat4 u_model; 15 | void main() { 16 | v_color = a_color; 17 | v_normal = (u_model * vec4(a_normal,0.0)).xyz; 18 | gl_Position = u_mvp * vec4(a_vertex,1.0); 19 | } 20 | 21 | 22 | \basicFS 23 | 24 | #version 300 es 25 | precision highp float; 26 | 27 | in vec3 v_normal; 28 | out vec4 FragColor; 29 | uniform vec4 u_color; 30 | 31 | void main() { 32 | vec4 final_color = u_color; 33 | FragColor = vec4( final_color.xyz, 1.0 ); 34 | } 35 | 36 | 37 | \volumeVS 38 | 39 | #version 300 es 40 | precision highp float; 41 | 42 | in vec3 a_vertex; 43 | in vec3 a_normal; 44 | in vec2 a_coord; 45 | 46 | out vec3 v_pos; 47 | out vec3 v_world_pos; 48 | out vec3 v_normal; 49 | out vec2 v_coord; 50 | 51 | uniform mat4 u_model; 52 | uniform mat4 u_mvp; 53 | 54 | void main() { 55 | v_pos = a_vertex; 56 | v_world_pos = (u_model * vec4( v_pos, 1.0) ).xyz; 57 | 58 | v_normal = (u_model * vec4( a_normal, 0.0) ).xyz; //a_normal 59 | v_coord = a_coord; 60 | 61 | gl_Position = u_mvp * vec4( v_world_pos, 1.0 ); 62 | } 63 | 64 | 65 | \FSUniforms 66 | 67 | #version 300 es 68 | precision highp float; 69 | precision highp sampler3D; 70 | precision highp isampler3D; 71 | precision highp usampler3D; 72 | 73 | in vec3 v_pos; 74 | in vec3 v_normal; 75 | in vec2 v_coord; 76 | 77 | out vec4 final_color; 78 | 79 | uniform vec3 u_camera_position; 80 | uniform vec3 u_local_camera_position; 81 | uniform vec3 u_cam_space; 82 | uniform vec4 u_color; 83 | uniform float u_time; 84 | uniform float u_quality; 85 | uniform float u_brightness; 86 | uniform float u_obj_size; 87 | 88 | \FSMain 89 | 90 | void main() { 91 | final_color = vec4(0.0); 92 | 93 | \FSReturn 94 | 95 | final_color = final_color * u_brightness; 96 | } --------------------------------------------------------------------------------