├── js └── index.js ├── .gitignore ├── rust ├── .cargo │ └── config.toml ├── Cargo.toml ├── src │ ├── lib.rs │ └── imaging.rs └── Cargo.lock ├── style.css ├── static ├── style.css └── index.html ├── README.md ├── package.json ├── tests └── app.rs └── webpack.config.js /js/index.js: -------------------------------------------------------------------------------- 1 | import("../pkg/index.js").catch(console.error); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | /rust/target 4 | /pkg 5 | /wasm-pack.log 6 | -------------------------------------------------------------------------------- /rust/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | 4 | [target.wasm32-unknown-unknown] 5 | rustflags = ["-C", "target-feature=+atomics,+bulk-memory"] 6 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-family: sans-serif; 4 | 5 | background-color: #333; 6 | color: #f7f7f7; 7 | 8 | margin: 16px; 9 | } 10 | 11 | .drop_zone { 12 | border: 3px dashed #ddd; 13 | min-height: 4rem; 14 | padding: 16px; 15 | text-align: center; 16 | } 17 | 18 | a { 19 | color: #44f0f2 20 | } 21 | 22 | a:visited { 23 | color: #fc44fc 24 | } -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-family: sans-serif; 4 | 5 | background-color: #333; 6 | color: #f7f7f7; 7 | 8 | margin: 4px; 9 | } 10 | 11 | h1 { 12 | font-size: 1.5em; 13 | margin: 0.2rem; 14 | } 15 | 16 | p { 17 | margin: 0 0.25rem; 18 | } 19 | 20 | .drop_zone { 21 | border: 3px dashed #ccc; 22 | min-height: 4rem; 23 | padding: 8px; 24 | text-align: center; 25 | } 26 | 27 | .info { 28 | font-size: 0.85rem; 29 | } 30 | 31 | .error { 32 | color: #f00; 33 | font-size: 1rem; 34 | min-height: 1.25rem; 35 | } 36 | 37 | a { 38 | color: #44eeef 39 | } 40 | 41 | a:hover { 42 | color: #e0e0ee 43 | } 44 | 45 | a:visited { 46 | color: #ef44ef 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My simple DICOM viewer 2 | 3 | This is an experimental DICOM Web viewer, written in Rust. 4 | 5 | The viewer uses [DICOM-rs] to deliver a viewer proof of concept, 6 | using WebAssembly. 7 | 8 | **Note:** This viewer does not intend to be suitable for clinical purposes. 9 | 10 | [DICOM-rs]: https://github.com/Enet4/dicom-rs 11 | 12 | ## How to install 13 | 14 | ```sh 15 | npm install 16 | ``` 17 | 18 | ## How to run in debug mode 19 | 20 | ```sh 21 | # Builds the project and opens it in a new browser tab. Auto-reloads when the project changes. 22 | npm start 23 | ``` 24 | 25 | ## How to build in release mode 26 | 27 | ```sh 28 | # Builds the project and places it into the `dist` folder. 29 | npm run build 30 | ``` 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-dicom-viewer", 3 | "publish": false, 4 | "version": "0.1.0", 5 | "author": "Eduardo Pinho ", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Enet4/simple-dicom-viewer" 9 | }, 10 | "engines": { 11 | "node": ">=16", 12 | "npm": ">= 7" 13 | }, 14 | "main": "pkg/index.js", 15 | "scripts": { 16 | "build": "rimraf dist pkg && webpack", 17 | "start": "rimraf dist pkg && webpack serve --open", 18 | "test": "cargo test && wasm-pack test --headless" 19 | }, 20 | "devDependencies": { 21 | "@wasm-tool/wasm-pack-plugin": "^1.1.0", 22 | "copy-webpack-plugin": "^11.0.0", 23 | "rimraf": "^5.0.1", 24 | "webpack": "^5.75.0", 25 | "webpack-cli": "^5.0.1", 26 | "webpack-dev-server": "^4.9.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/app.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen_test::{wasm_bindgen_test_configure, wasm_bindgen_test}; 2 | use wasm_bindgen::JsValue; 3 | use wasm_bindgen_futures::JsFuture; 4 | 5 | wasm_bindgen_test_configure!(run_in_browser); 6 | 7 | // This runs a unit test in the browser, so it can use browser APIs. 8 | #[wasm_bindgen_test] 9 | fn web_test() { 10 | assert_eq!(1, 1); 11 | } 12 | 13 | 14 | // This runs a unit test in the browser, and in addition it supports asynchronous Future APIs. 15 | #[wasm_bindgen_test] 16 | async fn async_test() { 17 | // Creates a JavaScript Promise which will asynchronously resolve with the value 42. 18 | let promise = js_sys::Promise::resolve(&JsValue::from(42_u32)); 19 | 20 | // Convert that promise into a future and make the test wait on it. 21 | let x = JsFuture::from(promise).await.unwrap(); 22 | assert_eq!(x, 42); 23 | } 24 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple DICOM web viewer 7 | 8 | 9 | 10 |

Simple DICOM web viewer

11 |

12 |

13 |

14 |
15 |

Drag a DICOM file to this drop zone

16 |
17 | 18 | 19 |
20 |
21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const CopyPlugin = require("copy-webpack-plugin"); 3 | const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); 4 | 5 | const dist = path.resolve(__dirname, "dist"); 6 | 7 | module.exports = { 8 | mode: "production", 9 | entry: { 10 | index: "./js/index.js" 11 | }, 12 | output: { 13 | path: dist, 14 | filename: "[name].js" 15 | }, 16 | devServer: { 17 | static: "./dist", 18 | client: { 19 | overlay: { 20 | errors: true, 21 | warnings: false, 22 | }, 23 | } 24 | }, 25 | watchOptions: { 26 | aggregateTimeout: 500, 27 | poll: 250, 28 | }, 29 | plugins: [ 30 | new CopyPlugin({ 31 | patterns: [ 32 | "static" 33 | ], 34 | }), 35 | new WasmPackPlugin({ 36 | crateDirectory: path.resolve(__dirname, "rust"), 37 | args: '--verbose', 38 | outDir: path.resolve(__dirname, "pkg"), 39 | //forceMode: "development", 40 | 41 | }), 42 | ], 43 | experiments: { 44 | syncWebAssembly: true, 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | # You must change these to your own details. 2 | [package] 3 | name = "simple-dicom-viewer" 4 | description = "A simple DICOM Web Viewer!" 5 | version = "0.1.0" 6 | publish = false 7 | authors = ["Eduardo Pinho "] 8 | categories = ["wasm"] 9 | keywords = ["dicom", "viewer"] 10 | readme = "README.md" 11 | edition = "2021" 12 | 13 | [lib] 14 | crate-type = ["cdylib"] 15 | 16 | [profile.release] 17 | # less code to include into binary 18 | panic = 'abort' 19 | # optimization over all codebase (better optimization, slower build) 20 | codegen-units = 1 21 | # optimization for size (more aggressive) 22 | opt-level = 'z' 23 | # link time optimization using using whole-program analysis 24 | lto = true 25 | 26 | [features] 27 | 28 | [dependencies] 29 | # The `wasm-bindgen` crate provides the bare minimum functionality needed 30 | # to interact with JavaScript. 31 | wasm-bindgen = "0.2.80" 32 | 33 | # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size 34 | # compared to the default allocator's ~10K. However, it is slower than the default 35 | # allocator, so it's not enabled by default. 36 | wee_alloc = { version = "0.4.2", optional = true } 37 | gloo-console = "0.2.1" 38 | gloo-file = "0.2.1" 39 | 40 | # error handling 41 | snafu = "0.7.1" 42 | 43 | # DICOM-rs 44 | [dependencies.dicom] 45 | git = "https://github.com/Enet4/dicom-rs" 46 | branch = "master" 47 | default-features = false 48 | 49 | # The `web-sys` crate allows you to interact with the various browser APIs, 50 | # like the DOM. 51 | [dependencies.web-sys] 52 | version = "0.3.57" 53 | features = [ 54 | "CanvasRenderingContext2d", 55 | "DataTransfer", 56 | "Document", 57 | "DragEvent", 58 | "FileReader", 59 | "HtmlCanvasElement", 60 | "ImageData", 61 | "ProgressEvent", 62 | "Window" 63 | ] 64 | 65 | # The `console_error_panic_hook` crate provides better debugging of panics by 66 | # logging them with `console.error`. This is great for development, but requires 67 | # all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled 68 | # in debug mode. 69 | [target."cfg(debug_assertions)".dependencies] 70 | console_error_panic_hook = "0.1.7" 71 | 72 | # These crates are used for running unit tests. 73 | [dev-dependencies] 74 | wasm-bindgen-test = "0.3.30" 75 | futures = "0.3.21" 76 | js-sys = "0.3.22" 77 | wasm-bindgen-futures = "0.4.30" 78 | 79 | # optimize dicom crate for better performance in debug builds 80 | [profile.dev.package.dicom] 81 | opt-level = 1 82 | 83 | [package.metadata.wasm-pack.profile.dev] 84 | wasm-opt = false 85 | 86 | [package.metadata.wasm-pack.profile.profiling] 87 | wasm-opt = false 88 | 89 | [package.metadata.wasm-pack.profile.release] 90 | wasm-opt = false 91 | #wasm-opt = ["-Oz", "--enable-mutable-globals"] 92 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | use web_sys::ImageData; 3 | use web_sys::MouseEvent; 4 | 5 | use std::cell::Cell; 6 | use std::cell::RefCell; 7 | use std::rc::Rc; 8 | 9 | use dicom::object::DefaultDicomObject; 10 | use gloo_file::Blob; 11 | use wasm_bindgen::JsCast; 12 | use web_sys::HtmlElement; 13 | use web_sys::{self, CanvasRenderingContext2d, HtmlCanvasElement}; 14 | 15 | pub mod imaging; 16 | 17 | use imaging::{ 18 | byte_data_to_dicom_obj, obj_to_imagedata, update_pixel_data_lut_with, window_level_of, 19 | WindowLevel, 20 | }; 21 | 22 | fn clear(context: &CanvasRenderingContext2d) -> Result<(), JsValue> { 23 | context.set_fill_style(&JsValue::from_str("#000")); 24 | context.fill_rect(0., 0., 640., 640.); 25 | Ok(()) 26 | } 27 | 28 | fn reset(context: &CanvasRenderingContext2d) -> Result<(), JsValue> { 29 | let width = 640; 30 | let height = 640; 31 | 32 | context.set_fill_style(&JsValue::from_str("#222")); 33 | context.fill_rect(0., 0., 640., 640.); 34 | 35 | context.set_line_width(2.); 36 | context.set_stroke_style(&"#fff".into()); 37 | 38 | context.begin_path(); 39 | 40 | let ref_x = width as f64 / 2.; 41 | let ref_y = height as f64 / 2.; 42 | 43 | // Draw an outer circle. 44 | context.arc(ref_x, ref_y, 128., 0., std::f64::consts::PI * 2.)?; 45 | 46 | // a vertical line 47 | context.move_to(ref_x, ref_y - 60.); 48 | context.line_to(ref_x, ref_y + 60.); 49 | 50 | // to the left 51 | context.move_to(ref_x, ref_y - 60.); 52 | context.line_to(ref_x - 55., ref_y); 53 | 54 | // to the right 55 | context.move_to(ref_x, ref_y - 60.); 56 | context.line_to(ref_x + 55., ref_y); 57 | 58 | context.stroke(); 59 | 60 | Ok(()) 61 | } 62 | 63 | fn set_error_messsage(msg: &str) { 64 | let window = web_sys::window().expect("no global `window` exists"); 65 | let document = window.document().expect("should have a document on window"); 66 | let error_message = document.get_element_by_id("error-message").unwrap(); 67 | error_message.set_inner_html(msg); 68 | } 69 | 70 | fn render_image_to_canvas( 71 | imagedata: ImageData, 72 | canvas: &HtmlCanvasElement, 73 | canvas_context: &CanvasRenderingContext2d, 74 | out_canvas: &HtmlCanvasElement, 75 | out_canvas_context: &CanvasRenderingContext2d, 76 | ) -> Result<(), JsValue> { 77 | clear(out_canvas_context)?; 78 | 79 | let w = imagedata.width(); 80 | let h = imagedata.height(); 81 | 82 | // send to inner canvas with original size 83 | canvas.set_width(w); 84 | canvas.set_height(h); 85 | canvas_context.put_image_data(&imagedata, 0., 0.)?; 86 | 87 | // scale to fit output canvas 88 | let scale = if w > h { 89 | out_canvas.width() as f64 / w as f64 90 | } else { 91 | out_canvas.height() as f64 / h as f64 92 | }; 93 | 94 | // set scaling transformation 95 | out_canvas_context.set_transform(scale, 0., 0., scale, 0., 0.)?; 96 | 97 | // draw contents of inner canvas to outer canvas 98 | out_canvas_context.draw_image_with_html_canvas_element(canvas, 0., 0.)?; 99 | 100 | Ok(()) 101 | } 102 | 103 | fn render_obj_to_canvas(state: &RefCell) { 104 | let mut state = state.borrow_mut(); 105 | let State { 106 | dicom_obj, 107 | lut, 108 | window_level: _, 109 | canvas, 110 | canvas_context, 111 | out_canvas, 112 | out_canvas_context, 113 | y_samples, 114 | } = &mut *state; 115 | 116 | let obj = if let Some(obj) = &dicom_obj { 117 | obj 118 | } else { 119 | gloo_console::warn!("No DICOM object loaded"); 120 | return; 121 | }; 122 | 123 | match obj_to_imagedata(obj, y_samples, lut) { 124 | Ok(imagedata) => { 125 | render_image_to_canvas( 126 | imagedata, 127 | canvas, 128 | canvas_context, 129 | out_canvas, 130 | out_canvas_context, 131 | ) 132 | .map(|_| { 133 | set_error_messsage(""); 134 | }) 135 | .unwrap_or_else(|e| { 136 | gloo_console::error!("Error rendering image data:", e); 137 | set_error_messsage("Sorry, could not render the image data to the screen. :("); 138 | }); 139 | } 140 | Err(e) => { 141 | let msg = format!("Failed to render DICOM object: {}", e); 142 | gloo_console::error!(&msg); 143 | set_error_messsage(&msg); 144 | } 145 | } 146 | } 147 | 148 | /// Set up the file drop zone 149 | fn set_drop_zone(state: Rc>, element: &HtmlElement) { 150 | let ondrop_callback = Closure::wrap(Box::new(move |event: web_sys::DragEvent| { 151 | event.prevent_default(); 152 | 153 | let data_transfer = event.data_transfer().expect("no data transfer available"); 154 | let file_list = data_transfer.files().expect("no files available"); 155 | let file = file_list.get(0).expect("file list is empty"); 156 | 157 | let state = Rc::clone(&state); 158 | let blob: Blob = file.into(); 159 | let file_reader = gloo_file::callbacks::read_as_bytes(&blob, move |outcome| { 160 | let data = outcome.expect("failed to get data"); 161 | 162 | let dicom_obj = match byte_data_to_dicom_obj(&data) { 163 | Ok(obj) => obj, 164 | Err(e) => { 165 | let error_msg = format!("Failed to parse DICOM object: {}", e); 166 | gloo_console::error!(&error_msg); 167 | set_error_messsage(&error_msg); 168 | return; 169 | } 170 | }; 171 | 172 | { 173 | let mut state = state.borrow_mut(); 174 | 175 | // look for window level 176 | state.window_level = window_level_of(&dicom_obj).unwrap_or(None); 177 | 178 | state.dicom_obj = Some(dicom_obj); 179 | state.lut = None; 180 | } 181 | 182 | render_obj_to_canvas(&state); 183 | }); 184 | 185 | std::mem::forget(file_reader); 186 | }) as Box); 187 | 188 | let ondragover_callback = Closure::wrap(Box::new(move |event: web_sys::DragEvent| { 189 | event.prevent_default(); 190 | }) as Box); 191 | 192 | element.set_ondragover(Some(ondragover_callback.as_ref().unchecked_ref())); 193 | element.set_ondrop(Some(ondrop_callback.as_ref().unchecked_ref())); 194 | 195 | ondrop_callback.forget(); 196 | ondragover_callback.forget(); 197 | } 198 | 199 | fn set_window_level_tool(state: Rc>, canvas: &HtmlCanvasElement) { 200 | let element = canvas; 201 | 202 | let is_dragging_mouse = Rc::new(Cell::new(false)); 203 | 204 | let dragging = Rc::clone(&is_dragging_mouse); 205 | 206 | // on mouse down, dragging = true 207 | let onmousedown_callback = Closure::wrap(Box::new(move |_: MouseEvent| { 208 | dragging.set(true); 209 | }) as Box); 210 | 211 | // on mouse movement, update window levels if dragging 212 | let dragging = Rc::clone(&is_dragging_mouse); 213 | let onmousemove_callback = Closure::wrap(Box::new(move |ev: MouseEvent| { 214 | if dragging.get() { 215 | let ww = ev.movement_x() as f64; 216 | let wc = ev.movement_y() as f64 * 2.; 217 | change_window_level(&state, ww, wc); 218 | } 219 | }) as Box); 220 | 221 | // on mouse up, dragging = false 222 | let dragging = Rc::clone(&is_dragging_mouse); 223 | let onmouseup_callback = Closure::wrap(Box::new(move |_: MouseEvent| { 224 | dragging.set(false); 225 | }) as Box); 226 | 227 | element 228 | .add_event_listener_with_callback( 229 | "mousedown", 230 | onmousedown_callback.as_ref().unchecked_ref(), 231 | ) 232 | .unwrap(); 233 | element 234 | .add_event_listener_with_callback( 235 | "mousemove", 236 | onmousemove_callback.as_ref().unchecked_ref(), 237 | ) 238 | .unwrap(); 239 | element 240 | .add_event_listener_with_callback("mouseup", onmouseup_callback.as_ref().unchecked_ref()) 241 | .unwrap(); 242 | element 243 | .add_event_listener_with_callback("mouseleave", onmouseup_callback.as_ref().unchecked_ref()) 244 | .unwrap(); 245 | 246 | onmousedown_callback.forget(); 247 | onmousemove_callback.forget(); 248 | onmouseup_callback.forget(); 249 | } 250 | 251 | fn change_window_level(state: &RefCell, rel_ww: f64, rel_wc: f64) { 252 | { 253 | let mut state = state.borrow_mut(); 254 | let State { 255 | dicom_obj, 256 | window_level, 257 | lut, 258 | .. 259 | } = &mut *state; 260 | 261 | let obj = if let Some(obj) = &dicom_obj { 262 | obj 263 | } else { 264 | // ignore, no DICOM object loaded 265 | return; 266 | }; 267 | 268 | // get the current window level 269 | let window_level = if let Some(window_level) = window_level { 270 | window_level 271 | } else { 272 | // ignore, no window level available 273 | return; 274 | }; 275 | 276 | let new_ww = (window_level.width + rel_ww).max(1.); 277 | let new_wc = window_level.center + rel_wc; 278 | 279 | // update the window level 280 | *window_level = WindowLevel { 281 | width: new_ww, 282 | center: new_wc, 283 | }; 284 | gloo_console::debug!("[WL] updated to", new_ww, ",", new_wc); 285 | 286 | if let Some(lut) = lut { 287 | // update the LUT 288 | match update_pixel_data_lut_with(lut, obj, *window_level) { 289 | Ok(lut) => lut, 290 | Err(e) => { 291 | gloo_console::error!("Failed to update LUT:", e); 292 | return; 293 | } 294 | } 295 | }; 296 | } 297 | 298 | // update canvas 299 | render_obj_to_canvas(state); 300 | } 301 | 302 | /// The application's global state 303 | #[derive(Debug)] 304 | pub struct State { 305 | dicom_obj: Option, 306 | lut: Option>, 307 | window_level: Option, 308 | canvas: HtmlCanvasElement, 309 | canvas_context: CanvasRenderingContext2d, 310 | out_canvas: HtmlCanvasElement, 311 | out_canvas_context: CanvasRenderingContext2d, 312 | /// memory buffer for the output image data 313 | /// (so that it does not have to be reallocated) 314 | y_samples: Vec, 315 | } 316 | 317 | // This is like the `main` function for our Rust webapp. 318 | #[wasm_bindgen(start)] 319 | pub fn main_js() -> Result<(), JsValue> { 320 | // This provides better error messages in debug mode. 321 | // It's disabled in release mode so it doesn't bloat up the file size. 322 | #[cfg(debug_assertions)] 323 | console_error_panic_hook::set_once(); 324 | 325 | let window = web_sys::window().expect("no global `window` exists"); 326 | let document = window.document().expect("should have a document on window"); 327 | 328 | // fetch canvas 329 | 330 | let canvas = document.get_element_by_id("view_inner").unwrap(); 331 | 332 | let canvas: HtmlCanvasElement = canvas.dyn_into::().unwrap(); 333 | 334 | let context = canvas 335 | .get_context("2d") 336 | .expect("Could not retrieve 2D context from canvas") 337 | .expect("2D context is missing") 338 | .dyn_into::() 339 | .unwrap(); 340 | 341 | let out_canvas = document.get_element_by_id("view").unwrap(); 342 | 343 | let out_canvas: HtmlCanvasElement = out_canvas.dyn_into::().unwrap(); 344 | 345 | let out_context = out_canvas 346 | .get_context("2d") 347 | .expect("Could not retrieve 2D context from canvas") 348 | .expect("2D context is missing") 349 | .dyn_into::() 350 | .unwrap(); 351 | 352 | // clear canvas 353 | reset(&out_context).unwrap(); 354 | 355 | // create the application state 356 | let state = Rc::new(RefCell::new(State { 357 | dicom_obj: None, 358 | lut: None, 359 | window_level: None, 360 | canvas, 361 | canvas_context: context, 362 | out_canvas: out_canvas.clone(), 363 | out_canvas_context: out_context, 364 | y_samples: Vec::new(), 365 | })); 366 | 367 | // get drop_zone 368 | let drop_zone = document 369 | .get_element_by_id("drop_zone") 370 | .expect("drop_zone should exist") 371 | .dyn_into() 372 | .expect("drop_zone should be an HTML element"); 373 | 374 | set_drop_zone(Rc::clone(&state), &drop_zone); 375 | 376 | set_window_level_tool(Rc::clone(&state), &out_canvas); 377 | 378 | Ok(()) 379 | } 380 | -------------------------------------------------------------------------------- /rust/src/imaging.rs: -------------------------------------------------------------------------------- 1 | //! Helper module for working with DICOM and imaging data. 2 | 3 | use std::borrow::Cow; 4 | 5 | use dicom::{ 6 | dictionary_std::tags, 7 | object::{file::ReadPreamble, DefaultDicomObject, OpenFileOptions}, core::DicomValue, 8 | }; 9 | use snafu::prelude::*; 10 | use wasm_bindgen::{Clamped, JsValue}; 11 | use web_sys::ImageData; 12 | 13 | #[derive(Debug, Snafu)] 14 | pub enum Error { 15 | #[snafu(whatever, display("{}", message))] 16 | Other { 17 | message: String, 18 | #[snafu(source(from(Box, Some)))] 19 | source: Option>, 20 | }, 21 | #[snafu(display("{:?}", value))] 22 | Js { value: JsValue }, 23 | } 24 | 25 | impl From for JsValue { 26 | fn from(e: Error) -> Self { 27 | match e { 28 | Error::Other { message, .. } => JsValue::from_str(&message), 29 | Error::Js { value } => value, 30 | } 31 | } 32 | } 33 | 34 | pub type Result = std::result::Result; 35 | 36 | /// A set of visualization window level parameters 37 | #[derive(Debug, Copy, Clone, PartialEq)] 38 | pub struct WindowLevel { 39 | pub width: f64, 40 | pub center: f64, 41 | } 42 | 43 | #[inline] 44 | pub fn byte_data_to_dicom_obj(byte_data: &[u8]) -> Result { 45 | OpenFileOptions::new() 46 | .read_preamble(ReadPreamble::Always) 47 | .from_reader(byte_data) 48 | .whatever_context("Failed to read DICOM data") 49 | } 50 | 51 | pub fn window_level_of(obj: &DefaultDicomObject) -> Result> { 52 | let ww = obj 53 | .element_opt(tags::WINDOW_WIDTH) 54 | .whatever_context("Could not get attribute WindowWidth")?; 55 | 56 | let wc = obj 57 | .element_opt(tags::WINDOW_CENTER) 58 | .whatever_context("Could not get attribute WindowCenter")?; 59 | 60 | match (ww, wc) { 61 | (Some(ww), Some(wc)) => { 62 | let ww = ww 63 | .to_float64() 64 | .whatever_context("Could not read WindowWidth as a number")?; 65 | let wc = wc 66 | .to_float64() 67 | .whatever_context("Could not read WindowCenter as a number")?; 68 | 69 | Ok(Some(WindowLevel { 70 | width: ww, 71 | center: wc, 72 | })) 73 | } 74 | _ => Ok(None), 75 | } 76 | } 77 | 78 | pub fn obj_to_imagedata(obj: &DefaultDicomObject, y_samples: &mut Vec, lut: &mut Option>) -> Result { 79 | let photometric_interpretation = obj 80 | .element(tags::PHOTOMETRIC_INTERPRETATION) 81 | .whatever_context("Could not fetch PhotometricInterpretation")? 82 | .to_str() 83 | .whatever_context("Could not read PhotometricInterpretation as a string")?; 84 | 85 | let width = obj 86 | .element(tags::COLUMNS) 87 | .whatever_context("Could not fetch Columns")? 88 | .to_int::() 89 | .whatever_context("Columns is not an integer")?; 90 | let height = obj 91 | .element(tags::ROWS) 92 | .whatever_context("Could not fetch Rows")? 93 | .to_int::() 94 | .whatever_context("Rows is not an integer")?; 95 | 96 | match photometric_interpretation.as_ref() { 97 | "MONOCHROME1" => { 98 | if lut.is_none() { 99 | gloo_console::debug!("Creating monochrome2 LUT"); 100 | *lut = Some(simple_pixel_data_lut(obj)?); 101 | } 102 | 103 | let lut = lut.as_ref().unwrap().as_ref(); 104 | convert_monochrome_to_y_values(y_samples, obj, Monochrome::Monochrome1, lut)?; 105 | } 106 | "MONOCHROME2" => { 107 | if lut.is_none() { 108 | gloo_console::debug!("Creating monochrome2 LUT"); 109 | *lut = Some(simple_pixel_data_lut(obj)?); 110 | } 111 | 112 | let lut = lut.as_ref().unwrap().as_ref(); 113 | convert_monochrome_to_y_values(y_samples, obj, Monochrome::Monochrome2, lut)?; 114 | } 115 | "RGB" => return convert_rgb_to_imagedata(obj, width, height), 116 | pi => whatever!("Unsupported photometric interpretation {}, sorry. :(", pi), 117 | } 118 | 119 | ImageData::new_with_u8_clamped_array_and_sh(Clamped(y_samples), width, height) 120 | .map_err(|value| Error::Js { value }) 121 | } 122 | 123 | /// create a simple LUT which maps a 16-bit image 124 | pub fn simple_pixel_data_lut(obj: &DefaultDicomObject) -> Result> { 125 | let window_level = window_level_of(obj)?.whatever_context("The given image does not provide window levels :(")?; 126 | simple_pixel_data_lut_with(obj, window_level) 127 | } 128 | /// create a simple LUT which maps a 16-bit image 129 | /// using the given window level 130 | pub fn simple_pixel_data_lut_with( 131 | obj: &DefaultDicomObject, 132 | window_level: WindowLevel, 133 | ) -> Result> { 134 | let bits_stored = obj 135 | .element(tags::BITS_STORED) 136 | .whatever_context("Could not fetch BitsStored")? 137 | .to_int::() 138 | .whatever_context("BitsStored is not a number")?; 139 | 140 | let mut lut = vec![0; 1 << bits_stored]; 141 | 142 | update_pixel_data_lut_with(&mut lut, obj, window_level)?; 143 | 144 | Ok(lut) 145 | } 146 | 147 | /// create a simple LUT which maps a 16-bit image 148 | /// using the given window level parameters. 149 | /// 150 | /// `lut` must have the correct size for the given object 151 | /// (`1 << bits_stored`). 152 | pub fn update_pixel_data_lut_with( 153 | lut: &mut [u8], 154 | obj: &DefaultDicomObject, 155 | window_level: WindowLevel, 156 | ) -> Result<()> { 157 | debug_assert!(lut.len() >= 256); 158 | 159 | let rescale_slope = if let Some(elem) = obj 160 | .element_opt(tags::RESCALE_SLOPE) 161 | .whatever_context("Could not fetch RescaleSlope")? 162 | { 163 | elem.to_float64() 164 | .whatever_context("RescaleSlope is not a number")? 165 | } else { 166 | 1.0 167 | }; 168 | 169 | let rescale_intercept = if let Some(elem) = obj 170 | .element_opt(tags::RESCALE_INTERCEPT) 171 | .whatever_context("Could not fetch RescaleSlope")? 172 | { 173 | elem.to_float64() 174 | .whatever_context("RescaleSlope is not a number")? 175 | } else { 176 | 0.0 177 | }; 178 | 179 | let voi_lut_function = if let Some(elem) = obj 180 | .element_opt(tags::VOILUT_FUNCTION) 181 | .whatever_context("Could not fetch VOILUTFunction")? 182 | { 183 | elem.to_str() 184 | .whatever_context("VOILUTFunction is not a string")? 185 | .to_string() 186 | } else { 187 | "LINEAR".to_string() 188 | }; 189 | 190 | if voi_lut_function != "LINEAR" 191 | && voi_lut_function != "LINEAR_EXACT" 192 | && voi_lut_function != "SIGMOID" 193 | { 194 | whatever!("Unsupported VOI LUT function {}", &voi_lut_function); 195 | } 196 | 197 | for (i, y) in lut.iter_mut().enumerate() { 198 | let x = i as f64; 199 | // rescale 200 | let x = x * rescale_slope + rescale_intercept; 201 | // window 202 | let x = apply_window_level(x, &voi_lut_function, window_level); 203 | *y = x as u8; 204 | } 205 | 206 | Ok(()) 207 | } 208 | 209 | fn apply_window_level(x: f64, voi_lut_function: &str, window_level: WindowLevel) -> f64 { 210 | let WindowLevel { 211 | width: ww, 212 | center: wc, 213 | } = window_level; 214 | 215 | match voi_lut_function { 216 | "LINEAR_EXACT" => window_level_linear_exact(x, ww, wc), 217 | "SIGMOID" => window_level_sigmoid(x, ww, wc), 218 | "LINEAR" => window_level_linear(x, ww, wc), 219 | _ => panic!("Unsupported VOI LUT function {}", voi_lut_function), 220 | } 221 | } 222 | 223 | fn window_level_linear(x: f64, ww: f64, wc: f64) -> f64 { 224 | debug_assert!(ww >= 1.); 225 | 226 | // C.11.2.1.2.1 227 | let min = wc - (ww - 1.) / 2.; 228 | let max = wc - 0.5 + (ww - 1.) / 2.; 229 | 230 | if x <= min { 231 | // if (x <= c - (w-1) / 2), then y = ymin 232 | 0. 233 | } else if x > max { 234 | // else if (x > c - 0.5 + (w-1) /2), then y = ymax 235 | 255. 236 | } else { 237 | // else y = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax- ymin) + ymin 238 | ((x - (wc - 0.5)) / (ww - 1.) + 0.5) * 255. 239 | } 240 | } 241 | 242 | fn window_level_linear_exact(value: f64, ww: f64, wc: f64) -> f64 { 243 | debug_assert!(ww >= 0.); 244 | 245 | // C.11.2.1.3.2 246 | 247 | let min = wc - ww / 2.; 248 | let max = wc + ww / 2.; 249 | 250 | if value <= min { 251 | // if (x <= c - w/2), then y = ymin 252 | 0. 253 | } else if value > max { 254 | // else if (x > c + w/2), then y = ymax 255 | 255. 256 | } else { 257 | // else y = ((x - c) / w + 0.5) * (ymax - ymin) + ymin 258 | ((value - wc) / ww + 0.5) * 255. 259 | } 260 | } 261 | 262 | fn window_level_sigmoid(value: f64, ww: f64, wc: f64) -> f64 { 263 | assert!(ww >= 1.); 264 | 265 | // C.11.2.1.3.1 266 | 267 | 255. / (1. + f64::exp(-4. * (value - wc) / ww)) 268 | } 269 | 270 | #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] 271 | pub enum Monochrome { 272 | Monochrome1, 273 | Monochrome2, 274 | } 275 | 276 | pub fn convert_monochrome_to_y_values( 277 | y_values: &mut Vec, 278 | obj: &DefaultDicomObject, 279 | monochrome: Monochrome, 280 | lut: &[u8], 281 | ) -> Result<()> { 282 | 283 | let bits_allocated = obj 284 | .element(tags::BITS_ALLOCATED) 285 | .whatever_context("Could not fetch BitsAllocated")? 286 | .to_int::() 287 | .whatever_context("BitsAllocated is not a number")?; 288 | 289 | 290 | match bits_allocated { 291 | 8 => { 292 | let samples = obj 293 | .element(tags::PIXEL_DATA) 294 | .whatever_context("Could not fetch PixelData")?; 295 | 296 | if matches!(samples.value(), DicomValue::PixelSequence { .. }) { 297 | whatever!("Encapsulated pixel data encoding is not supported at the moment, sorry. :("); 298 | } 299 | 300 | let samples = samples 301 | .to_bytes() 302 | .whatever_context("Could not read PixelData as a sequence of 8-bit integers")?; 303 | 304 | if samples.len() * 4 != y_values.len() { 305 | y_values.resize(samples.len() * 4, 255); 306 | } 307 | 308 | for (y, x) in y_values.chunks_mut(4).zip(samples.iter().copied()) { 309 | let x = lut[x as usize]; 310 | 311 | let x = if monochrome == Monochrome::Monochrome1 { 312 | 0xFF - x 313 | } else { 314 | x 315 | }; 316 | 317 | y[3] = 255; 318 | y[0] = x; 319 | y[1] = x; 320 | y[2] = x; 321 | } 322 | } 323 | 16 => { 324 | let samples = obj 325 | .element(tags::PIXEL_DATA) 326 | .whatever_context("Could not fetch PixelData")?; 327 | 328 | if matches!(samples.value(), DicomValue::PixelSequence { .. }) { 329 | whatever!("Encapsulated pixel data encoding is not supported at the moment, sorry. :("); 330 | } 331 | 332 | let samples: Cow<[u16]> = samples 333 | .uint16_slice() 334 | .map(Cow::from) 335 | .or_else(|_| { 336 | samples 337 | .to_multi_int::() 338 | .map(Cow::Owned) 339 | }) 340 | .whatever_context("Could not read PixelData as a sequence of 16-bit integers")?; 341 | 342 | if samples.len() * 4 != y_values.len() { 343 | y_values.resize(samples.len() * 4, 255); 344 | } 345 | 346 | let x_mask = lut.len() - 1; 347 | 348 | for (y, x) in y_values.chunks_mut(4).zip(samples.iter().copied()) { 349 | let x = lut[x as usize & x_mask]; 350 | 351 | let x = if monochrome == Monochrome::Monochrome1 { 352 | 0xFF - x 353 | } else { 354 | x 355 | }; 356 | 357 | y[3] = 255; 358 | y[0] = x; 359 | y[1] = x; 360 | y[2] = x; 361 | } 362 | } 363 | _ => { 364 | whatever!("Unsupported BitsAllocated {} :(", bits_allocated); 365 | } 366 | }; 367 | 368 | Ok(()) 369 | } 370 | 371 | pub fn convert_rgb_to_imagedata( 372 | obj: &DefaultDicomObject, 373 | width: u32, 374 | height: u32, 375 | ) -> Result { 376 | let samples_per_pixel = obj 377 | .element(tags::SAMPLES_PER_PIXEL) 378 | .whatever_context("Could not fetch SamplesPerPixel")? 379 | .to_int::() 380 | .whatever_context("SamplesPerPixel is not an integer")?; 381 | 382 | if samples_per_pixel != 3 { 383 | whatever!("Expected 3 samples per pixel, got {}", samples_per_pixel); 384 | } 385 | 386 | let samples = obj 387 | .element(tags::PIXEL_DATA) 388 | .whatever_context("Could not fetch PixelData")? 389 | .to_bytes() 390 | .whatever_context("Could not read the bytes of PixelData")?; 391 | 392 | let data: Vec = samples 393 | .chunks(3) 394 | .map(|chunk| <[u8; 3]>::try_from(chunk).unwrap()) 395 | .flat_map(|[r, g, b]| [r, g, b, 0xFF]) 396 | .collect(); 397 | 398 | ImageData::new_with_u8_clamped_array_and_sh(Clamped(&data), width, height) 399 | .map_err(|value| Error::Js { value }) 400 | } 401 | -------------------------------------------------------------------------------- /rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "android-tzdata" 7 | version = "0.1.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.14" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 16 | dependencies = [ 17 | "hermit-abi 0.1.19", 18 | "libc", 19 | "winapi", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.1.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 27 | 28 | [[package]] 29 | name = "bumpalo" 30 | version = "3.13.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 33 | 34 | [[package]] 35 | name = "byteorder" 36 | version = "1.4.3" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 39 | 40 | [[package]] 41 | name = "byteordered" 42 | version = "0.6.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "bbf2cd9424f5ff404aba1959c835cbc448ee8b689b870a9981c76c0fd46280e6" 45 | dependencies = [ 46 | "byteorder", 47 | ] 48 | 49 | [[package]] 50 | name = "cfg-if" 51 | version = "0.1.10" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 54 | 55 | [[package]] 56 | name = "cfg-if" 57 | version = "1.0.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 60 | 61 | [[package]] 62 | name = "chrono" 63 | version = "0.4.26" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" 66 | dependencies = [ 67 | "android-tzdata", 68 | "num-traits", 69 | ] 70 | 71 | [[package]] 72 | name = "console_error_panic_hook" 73 | version = "0.1.7" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 76 | dependencies = [ 77 | "cfg-if 1.0.0", 78 | "wasm-bindgen", 79 | ] 80 | 81 | [[package]] 82 | name = "crossbeam-channel" 83 | version = "0.5.8" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 86 | dependencies = [ 87 | "cfg-if 1.0.0", 88 | "crossbeam-utils", 89 | ] 90 | 91 | [[package]] 92 | name = "crossbeam-deque" 93 | version = "0.8.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 96 | dependencies = [ 97 | "cfg-if 1.0.0", 98 | "crossbeam-epoch", 99 | "crossbeam-utils", 100 | ] 101 | 102 | [[package]] 103 | name = "crossbeam-epoch" 104 | version = "0.9.15" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 107 | dependencies = [ 108 | "autocfg", 109 | "cfg-if 1.0.0", 110 | "crossbeam-utils", 111 | "memoffset", 112 | "scopeguard", 113 | ] 114 | 115 | [[package]] 116 | name = "crossbeam-utils" 117 | version = "0.8.16" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 120 | dependencies = [ 121 | "cfg-if 1.0.0", 122 | ] 123 | 124 | [[package]] 125 | name = "dicom" 126 | version = "0.5.4" 127 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 128 | dependencies = [ 129 | "dicom-core", 130 | "dicom-dictionary-std", 131 | "dicom-dump", 132 | "dicom-encoding", 133 | "dicom-object", 134 | "dicom-parser", 135 | "dicom-transfer-syntax-registry", 136 | ] 137 | 138 | [[package]] 139 | name = "dicom-core" 140 | version = "0.5.4" 141 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 142 | dependencies = [ 143 | "chrono", 144 | "itertools", 145 | "num-traits", 146 | "safe-transmute", 147 | "smallvec", 148 | "snafu", 149 | ] 150 | 151 | [[package]] 152 | name = "dicom-dictionary-std" 153 | version = "0.5.0" 154 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 155 | dependencies = [ 156 | "dicom-core", 157 | "once_cell", 158 | ] 159 | 160 | [[package]] 161 | name = "dicom-dump" 162 | version = "0.5.4" 163 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 164 | dependencies = [ 165 | "dicom-core", 166 | "dicom-dictionary-std", 167 | "dicom-encoding", 168 | "dicom-object", 169 | "dicom-transfer-syntax-registry", 170 | "itertools", 171 | "owo-colors", 172 | "snafu", 173 | "term_size", 174 | ] 175 | 176 | [[package]] 177 | name = "dicom-encoding" 178 | version = "0.5.3" 179 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 180 | dependencies = [ 181 | "byteordered", 182 | "dicom-core", 183 | "dicom-dictionary-std", 184 | "encoding", 185 | "snafu", 186 | ] 187 | 188 | [[package]] 189 | name = "dicom-object" 190 | version = "0.5.4" 191 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 192 | dependencies = [ 193 | "byteordered", 194 | "dicom-core", 195 | "dicom-dictionary-std", 196 | "dicom-encoding", 197 | "dicom-parser", 198 | "dicom-transfer-syntax-registry", 199 | "itertools", 200 | "smallvec", 201 | "snafu", 202 | "tracing", 203 | ] 204 | 205 | [[package]] 206 | name = "dicom-parser" 207 | version = "0.5.3" 208 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 209 | dependencies = [ 210 | "chrono", 211 | "dicom-core", 212 | "dicom-dictionary-std", 213 | "dicom-encoding", 214 | "smallvec", 215 | "snafu", 216 | "tracing", 217 | ] 218 | 219 | [[package]] 220 | name = "dicom-transfer-syntax-registry" 221 | version = "0.5.1" 222 | source = "git+https://github.com/Enet4/dicom-rs?branch=master#df50e6c776d0037875d949d28fd7f916df7b6be6" 223 | dependencies = [ 224 | "byteordered", 225 | "dicom-core", 226 | "dicom-encoding", 227 | "jpeg-decoder", 228 | "lazy_static", 229 | "tracing", 230 | ] 231 | 232 | [[package]] 233 | name = "doc-comment" 234 | version = "0.3.3" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 237 | 238 | [[package]] 239 | name = "either" 240 | version = "1.8.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 243 | 244 | [[package]] 245 | name = "encoding" 246 | version = "0.2.33" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 249 | dependencies = [ 250 | "encoding-index-japanese", 251 | "encoding-index-korean", 252 | "encoding-index-simpchinese", 253 | "encoding-index-singlebyte", 254 | "encoding-index-tradchinese", 255 | ] 256 | 257 | [[package]] 258 | name = "encoding-index-japanese" 259 | version = "1.20141219.5" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 262 | dependencies = [ 263 | "encoding_index_tests", 264 | ] 265 | 266 | [[package]] 267 | name = "encoding-index-korean" 268 | version = "1.20141219.5" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 271 | dependencies = [ 272 | "encoding_index_tests", 273 | ] 274 | 275 | [[package]] 276 | name = "encoding-index-simpchinese" 277 | version = "1.20141219.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 280 | dependencies = [ 281 | "encoding_index_tests", 282 | ] 283 | 284 | [[package]] 285 | name = "encoding-index-singlebyte" 286 | version = "1.20141219.5" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 289 | dependencies = [ 290 | "encoding_index_tests", 291 | ] 292 | 293 | [[package]] 294 | name = "encoding-index-tradchinese" 295 | version = "1.20141219.5" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 298 | dependencies = [ 299 | "encoding_index_tests", 300 | ] 301 | 302 | [[package]] 303 | name = "encoding_index_tests" 304 | version = "0.1.4" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 307 | 308 | [[package]] 309 | name = "futures" 310 | version = "0.3.28" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 313 | dependencies = [ 314 | "futures-channel", 315 | "futures-core", 316 | "futures-executor", 317 | "futures-io", 318 | "futures-sink", 319 | "futures-task", 320 | "futures-util", 321 | ] 322 | 323 | [[package]] 324 | name = "futures-channel" 325 | version = "0.3.28" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 328 | dependencies = [ 329 | "futures-core", 330 | "futures-sink", 331 | ] 332 | 333 | [[package]] 334 | name = "futures-core" 335 | version = "0.3.28" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 338 | 339 | [[package]] 340 | name = "futures-executor" 341 | version = "0.3.28" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 344 | dependencies = [ 345 | "futures-core", 346 | "futures-task", 347 | "futures-util", 348 | ] 349 | 350 | [[package]] 351 | name = "futures-io" 352 | version = "0.3.28" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 355 | 356 | [[package]] 357 | name = "futures-macro" 358 | version = "0.3.28" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "syn 2.0.18", 365 | ] 366 | 367 | [[package]] 368 | name = "futures-sink" 369 | version = "0.3.28" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 372 | 373 | [[package]] 374 | name = "futures-task" 375 | version = "0.3.28" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 378 | 379 | [[package]] 380 | name = "futures-util" 381 | version = "0.3.28" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 384 | dependencies = [ 385 | "futures-channel", 386 | "futures-core", 387 | "futures-io", 388 | "futures-macro", 389 | "futures-sink", 390 | "futures-task", 391 | "memchr", 392 | "pin-project-lite", 393 | "pin-utils", 394 | "slab", 395 | ] 396 | 397 | [[package]] 398 | name = "gloo-console" 399 | version = "0.2.3" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" 402 | dependencies = [ 403 | "gloo-utils", 404 | "js-sys", 405 | "serde", 406 | "wasm-bindgen", 407 | "web-sys", 408 | ] 409 | 410 | [[package]] 411 | name = "gloo-events" 412 | version = "0.1.2" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" 415 | dependencies = [ 416 | "wasm-bindgen", 417 | "web-sys", 418 | ] 419 | 420 | [[package]] 421 | name = "gloo-file" 422 | version = "0.2.3" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" 425 | dependencies = [ 426 | "gloo-events", 427 | "js-sys", 428 | "wasm-bindgen", 429 | "web-sys", 430 | ] 431 | 432 | [[package]] 433 | name = "gloo-utils" 434 | version = "0.1.7" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" 437 | dependencies = [ 438 | "js-sys", 439 | "serde", 440 | "serde_json", 441 | "wasm-bindgen", 442 | "web-sys", 443 | ] 444 | 445 | [[package]] 446 | name = "heck" 447 | version = "0.4.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 450 | 451 | [[package]] 452 | name = "hermit-abi" 453 | version = "0.1.19" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 456 | dependencies = [ 457 | "libc", 458 | ] 459 | 460 | [[package]] 461 | name = "hermit-abi" 462 | version = "0.2.6" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 465 | dependencies = [ 466 | "libc", 467 | ] 468 | 469 | [[package]] 470 | name = "is_ci" 471 | version = "1.1.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" 474 | 475 | [[package]] 476 | name = "itertools" 477 | version = "0.10.5" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 480 | dependencies = [ 481 | "either", 482 | ] 483 | 484 | [[package]] 485 | name = "itoa" 486 | version = "1.0.6" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 489 | 490 | [[package]] 491 | name = "jpeg-decoder" 492 | version = "0.3.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" 495 | dependencies = [ 496 | "rayon", 497 | ] 498 | 499 | [[package]] 500 | name = "js-sys" 501 | version = "0.3.64" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 504 | dependencies = [ 505 | "wasm-bindgen", 506 | ] 507 | 508 | [[package]] 509 | name = "lazy_static" 510 | version = "1.4.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 513 | 514 | [[package]] 515 | name = "libc" 516 | version = "0.2.146" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" 519 | 520 | [[package]] 521 | name = "log" 522 | version = "0.4.19" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 525 | 526 | [[package]] 527 | name = "memchr" 528 | version = "2.5.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 531 | 532 | [[package]] 533 | name = "memoffset" 534 | version = "0.9.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 537 | dependencies = [ 538 | "autocfg", 539 | ] 540 | 541 | [[package]] 542 | name = "memory_units" 543 | version = "0.4.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 546 | 547 | [[package]] 548 | name = "num-traits" 549 | version = "0.2.15" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 552 | dependencies = [ 553 | "autocfg", 554 | ] 555 | 556 | [[package]] 557 | name = "num_cpus" 558 | version = "1.15.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 561 | dependencies = [ 562 | "hermit-abi 0.2.6", 563 | "libc", 564 | ] 565 | 566 | [[package]] 567 | name = "once_cell" 568 | version = "1.18.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 571 | 572 | [[package]] 573 | name = "owo-colors" 574 | version = "3.5.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 577 | dependencies = [ 578 | "supports-color", 579 | ] 580 | 581 | [[package]] 582 | name = "pin-project-lite" 583 | version = "0.2.9" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 586 | 587 | [[package]] 588 | name = "pin-utils" 589 | version = "0.1.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 592 | 593 | [[package]] 594 | name = "proc-macro2" 595 | version = "1.0.60" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" 598 | dependencies = [ 599 | "unicode-ident", 600 | ] 601 | 602 | [[package]] 603 | name = "quote" 604 | version = "1.0.28" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 607 | dependencies = [ 608 | "proc-macro2", 609 | ] 610 | 611 | [[package]] 612 | name = "rayon" 613 | version = "1.7.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 616 | dependencies = [ 617 | "either", 618 | "rayon-core", 619 | ] 620 | 621 | [[package]] 622 | name = "rayon-core" 623 | version = "1.11.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 626 | dependencies = [ 627 | "crossbeam-channel", 628 | "crossbeam-deque", 629 | "crossbeam-utils", 630 | "num_cpus", 631 | ] 632 | 633 | [[package]] 634 | name = "ryu" 635 | version = "1.0.13" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 638 | 639 | [[package]] 640 | name = "safe-transmute" 641 | version = "0.11.2" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "98a01dab6acf992653be49205bdd549f32f17cb2803e8eacf1560bf97259aae8" 644 | 645 | [[package]] 646 | name = "scoped-tls" 647 | version = "1.0.1" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 650 | 651 | [[package]] 652 | name = "scopeguard" 653 | version = "1.1.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 656 | 657 | [[package]] 658 | name = "serde" 659 | version = "1.0.164" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" 662 | dependencies = [ 663 | "serde_derive", 664 | ] 665 | 666 | [[package]] 667 | name = "serde_derive" 668 | version = "1.0.164" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" 671 | dependencies = [ 672 | "proc-macro2", 673 | "quote", 674 | "syn 2.0.18", 675 | ] 676 | 677 | [[package]] 678 | name = "serde_json" 679 | version = "1.0.97" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" 682 | dependencies = [ 683 | "itoa", 684 | "ryu", 685 | "serde", 686 | ] 687 | 688 | [[package]] 689 | name = "simple-dicom-viewer" 690 | version = "0.1.0" 691 | dependencies = [ 692 | "console_error_panic_hook", 693 | "dicom", 694 | "futures", 695 | "gloo-console", 696 | "gloo-file", 697 | "js-sys", 698 | "snafu", 699 | "wasm-bindgen", 700 | "wasm-bindgen-futures", 701 | "wasm-bindgen-test", 702 | "web-sys", 703 | "wee_alloc", 704 | ] 705 | 706 | [[package]] 707 | name = "slab" 708 | version = "0.4.8" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 711 | dependencies = [ 712 | "autocfg", 713 | ] 714 | 715 | [[package]] 716 | name = "smallvec" 717 | version = "1.10.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 720 | 721 | [[package]] 722 | name = "snafu" 723 | version = "0.7.4" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045" 726 | dependencies = [ 727 | "doc-comment", 728 | "snafu-derive", 729 | ] 730 | 731 | [[package]] 732 | name = "snafu-derive" 733 | version = "0.7.4" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" 736 | dependencies = [ 737 | "heck", 738 | "proc-macro2", 739 | "quote", 740 | "syn 1.0.109", 741 | ] 742 | 743 | [[package]] 744 | name = "supports-color" 745 | version = "1.3.1" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f" 748 | dependencies = [ 749 | "atty", 750 | "is_ci", 751 | ] 752 | 753 | [[package]] 754 | name = "syn" 755 | version = "1.0.109" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 758 | dependencies = [ 759 | "proc-macro2", 760 | "quote", 761 | "unicode-ident", 762 | ] 763 | 764 | [[package]] 765 | name = "syn" 766 | version = "2.0.18" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" 769 | dependencies = [ 770 | "proc-macro2", 771 | "quote", 772 | "unicode-ident", 773 | ] 774 | 775 | [[package]] 776 | name = "term_size" 777 | version = "0.3.2" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" 780 | dependencies = [ 781 | "libc", 782 | "winapi", 783 | ] 784 | 785 | [[package]] 786 | name = "tracing" 787 | version = "0.1.37" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 790 | dependencies = [ 791 | "cfg-if 1.0.0", 792 | "pin-project-lite", 793 | "tracing-attributes", 794 | "tracing-core", 795 | ] 796 | 797 | [[package]] 798 | name = "tracing-attributes" 799 | version = "0.1.25" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "8803eee176538f94ae9a14b55b2804eb7e1441f8210b1c31290b3bccdccff73b" 802 | dependencies = [ 803 | "proc-macro2", 804 | "quote", 805 | "syn 2.0.18", 806 | ] 807 | 808 | [[package]] 809 | name = "tracing-core" 810 | version = "0.1.31" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 813 | dependencies = [ 814 | "once_cell", 815 | ] 816 | 817 | [[package]] 818 | name = "unicode-ident" 819 | version = "1.0.9" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 822 | 823 | [[package]] 824 | name = "wasm-bindgen" 825 | version = "0.2.87" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 828 | dependencies = [ 829 | "cfg-if 1.0.0", 830 | "wasm-bindgen-macro", 831 | ] 832 | 833 | [[package]] 834 | name = "wasm-bindgen-backend" 835 | version = "0.2.87" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 838 | dependencies = [ 839 | "bumpalo", 840 | "log", 841 | "once_cell", 842 | "proc-macro2", 843 | "quote", 844 | "syn 2.0.18", 845 | "wasm-bindgen-shared", 846 | ] 847 | 848 | [[package]] 849 | name = "wasm-bindgen-futures" 850 | version = "0.4.37" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" 853 | dependencies = [ 854 | "cfg-if 1.0.0", 855 | "js-sys", 856 | "wasm-bindgen", 857 | "web-sys", 858 | ] 859 | 860 | [[package]] 861 | name = "wasm-bindgen-macro" 862 | version = "0.2.87" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 865 | dependencies = [ 866 | "quote", 867 | "wasm-bindgen-macro-support", 868 | ] 869 | 870 | [[package]] 871 | name = "wasm-bindgen-macro-support" 872 | version = "0.2.87" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 875 | dependencies = [ 876 | "proc-macro2", 877 | "quote", 878 | "syn 2.0.18", 879 | "wasm-bindgen-backend", 880 | "wasm-bindgen-shared", 881 | ] 882 | 883 | [[package]] 884 | name = "wasm-bindgen-shared" 885 | version = "0.2.87" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 888 | 889 | [[package]] 890 | name = "wasm-bindgen-test" 891 | version = "0.3.37" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" 894 | dependencies = [ 895 | "console_error_panic_hook", 896 | "js-sys", 897 | "scoped-tls", 898 | "wasm-bindgen", 899 | "wasm-bindgen-futures", 900 | "wasm-bindgen-test-macro", 901 | ] 902 | 903 | [[package]] 904 | name = "wasm-bindgen-test-macro" 905 | version = "0.3.37" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" 908 | dependencies = [ 909 | "proc-macro2", 910 | "quote", 911 | ] 912 | 913 | [[package]] 914 | name = "web-sys" 915 | version = "0.3.64" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" 918 | dependencies = [ 919 | "js-sys", 920 | "wasm-bindgen", 921 | ] 922 | 923 | [[package]] 924 | name = "wee_alloc" 925 | version = "0.4.5" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 928 | dependencies = [ 929 | "cfg-if 0.1.10", 930 | "libc", 931 | "memory_units", 932 | "winapi", 933 | ] 934 | 935 | [[package]] 936 | name = "winapi" 937 | version = "0.3.9" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 940 | dependencies = [ 941 | "winapi-i686-pc-windows-gnu", 942 | "winapi-x86_64-pc-windows-gnu", 943 | ] 944 | 945 | [[package]] 946 | name = "winapi-i686-pc-windows-gnu" 947 | version = "0.4.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 950 | 951 | [[package]] 952 | name = "winapi-x86_64-pc-windows-gnu" 953 | version = "0.4.0" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 956 | --------------------------------------------------------------------------------