larvitar 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,15 +4,13 @@
4
4
 
5
5
  # Larvitar
6
6
 
7
- ## Dicom Image Toolkit for CornestoneJS
7
+ ## Dicom Image Toolkit for CornerstoneJS
8
8
 
9
- ### Current version: 1.3.3
9
+ ### Current version: 1.4.0
10
10
 
11
- ### Latest Stable version: 1.3.3
11
+ ### Latest Published Release: 1.4.0
12
12
 
13
- ### Latest Published Release: 1.3.3
14
-
15
- This library provides common DICOM functionalities to be used in web-applications: it's wrapper that simplifies the use of cornestone-js environment.
13
+ This library provides common DICOM functionalities to be used in web-applications: it's wrapper that simplifies the use of cornerstone-js environment.
16
14
  Orthogonal multiplanar reformat is included as well as custom loader/exporter for nrrd files and [Vuex](https://vuex.vuejs.org/) custom integration.
17
15
 
18
16
  - `index` main file
@@ -22,7 +20,7 @@ Orthogonal multiplanar reformat is included as well as custom loader/exporter fo
22
20
  - `imageIo` import a dicom image in .nrrd format and build contiguous array for exporting data as volume
23
21
  - `imageLayers` provide support for multi-layer cornerstone fusion renderer
24
22
  - `imageLoading` initialize loader and custom loaders
25
- - `imageParsing` parse dicom files and return a cornestone data structure ready to be used for rendering
23
+ - `imageParsing` parse dicom files and return a cornerstone data structure ready to be used for rendering
26
24
  - `imagePresets` provides default image CT presets and set functionality
27
25
  - `imageRendering` provides rendering functionalities
28
26
  - `imageReslice` provides reslice functionalities
@@ -90,3 +88,9 @@ Full documentation and examples are available at http://www.dvisionlab.com/Larvi
90
88
  # Build package
91
89
 
92
90
  `yarn build-lib`
91
+
92
+ # Development
93
+
94
+ Use `yarn dev-lib` to have `rollup` hot-reload (live recompiling the library).
95
+ In order to test functionalities you can modify the library import path in an example (see the `docs/examples` folder) to use the recompiled bundle in `dist/`, then serve the .html file with VSCode extension [LiveServer](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) or other similar tools.
96
+ Once you are done, upgrade the version, build the library and copy it to the `docs/examples` folder. This file must be included into the commit, while docs will be compiled by the Github action.
@@ -78,11 +78,10 @@ export const readFile = function (entry) {
78
78
  * @instance
79
79
  * @function parseDataSet
80
80
  * @param {Object} dataSet - dicom parser dataSet object
81
- * @param {Array} metadata - Initialized metadata object
81
+ * @param {Object} metadata - Initialized metadata object
82
82
  * @param {Array} customFilter - Optional filter: {tags:[], frameId: 0}
83
83
  */
84
- // This function iterates through dataSet recursively and adds new HTML strings
85
- // to the output array passed into it
84
+ // This function iterates through dataSet recursively and store tag values into metadata object
86
85
  export const parseDataSet = function (dataSet, metadata, customFilter) {
87
86
  // customFilter= {tags:[], frameId:xxx}
88
87
  // the dataSet.elements object contains properties for each element parsed. The name of the property
@@ -113,7 +112,18 @@ export const parseDataSet = function (dataSet, metadata, customFilter) {
113
112
  }
114
113
  } else {
115
114
  let tagValue = parseTag(dataSet, propertyName, element);
116
- metadata[propertyName] = tagValue;
115
+
116
+ // identify duplicated tags (keep the first occurency and store the others in another tag eg x00280010_uuid)
117
+ if (metadata[propertyName] !== undefined) {
118
+ console.debug(
119
+ `Identified duplicated tag "${propertyName}", values are:`,
120
+ metadata[propertyName],
121
+ tagValue
122
+ );
123
+ metadata[propertyName + "_" + uuidv4()] = tagValue;
124
+ } else {
125
+ metadata[propertyName] = tagValue;
126
+ }
117
127
  }
118
128
  }
119
129
  } catch (err) {
@@ -1,6 +1,9 @@
1
1
  /** @module imaging/imageTools
2
2
  * @desc This file provides functionalities for
3
3
  * interacting with cornerstone tools
4
+ * DEPRECATION WARNING: these are legacy functions
5
+ * that will be removed soon. Use the corresponding
6
+ * functions in /tools/main.js instead.
4
7
  */
5
8
 
6
9
  // external libraries
@@ -277,6 +280,7 @@ export const clearMeasurements = function () {
277
280
  * @param {Object} options - The custom options. @default from tools/default.js
278
281
  * @param {String} activeViewport - The active viewport (if "all", viewports array will be used)
279
282
  * @param {Array} viewports - The hmtl element id to be used for tool initialization.
283
+ * @deprecated (OBSOLETE)
280
284
  */
281
285
  export const setToolActive = function (
282
286
  toolName,
@@ -304,10 +308,11 @@ export const setToolActive = function (
304
308
 
305
309
  /**
306
310
  * Set Tool "disabled" on all elements (ie, not rendered) & refresh cornerstone elements
307
- * @function setToolActive
311
+ * @function setToolDisabled
308
312
  * @param {String} toolName - The tool name.
309
313
  * @param {String} activeViewport - The active viewport (if "all", viewports array will be used)
310
314
  * @param {Array} viewports - The hmtl element id to be used for tool initialization.
315
+ * @deprecated (OBSOLETE)
311
316
  */
312
317
  export const setToolDisabled = function (toolName, activeViewport, viewports) {
313
318
  cornerstoneTools.setToolDisabled(toolName);
@@ -332,6 +337,7 @@ export const setToolDisabled = function (toolName, activeViewport, viewports) {
332
337
  * @param {String} toolName - The tool name.
333
338
  * @param {String} activeViewport - The active viewport (if "all", viewports array will be used)
334
339
  * @param {Array} viewports - The hmtl element id to be used for tool initialization.
340
+ * @deprecated (OBSOLETE)
335
341
  */
336
342
  export const setToolEnabled = function (toolName, activeViewport, viewports) {
337
343
  cornerstoneTools.setToolEnabled(toolName);
@@ -356,6 +362,7 @@ export const setToolEnabled = function (toolName, activeViewport, viewports) {
356
362
  * @param {String} toolName - The tool name.
357
363
  * @param {String} activeViewport - The active viewport (if "all", viewports array will be used)
358
364
  * @param {Array} viewports - The hmtl element id to be used for tool initialization.
365
+ * @deprecated (OBSOLETE)
359
366
  */
360
367
  export const setToolPassive = function (toolName, activeViewport, viewports) {
361
368
  cornerstoneTools.setToolPassive(toolName);
@@ -22,27 +22,42 @@ import { updateViewportData } from "../imageRendering";
22
22
  */
23
23
 
24
24
  /**
25
- * Setup mouse handler modifiers and keyboard shortcuts
26
- * NOTE: at the moment only mouse right button is affected
25
+ * Global event callbacks
26
+ */
27
+ let onKeyDownFn = null;
28
+ let onKeyUpFn = null;
29
+
30
+ /**
31
+ * Setup mouse handler modifiers and keyboard shortcuts:
32
+ * register a tool on right button and another one
33
+ * when pressing a modifier (ctrl/shift/alt) + right button
34
+ * The activation take place on all active viewports
27
35
  * Improvements could be:
28
36
  * - "restore previous active tool" instead of passed "default" tool
29
37
  * - manage left button (an idea could be to cycle over object keys for both buttons)
30
38
  * - possibility to change modifier keys
31
39
  * @param {Object} config - see tools/default
32
- * @param {Array} viewports - The hmtl element ids to be used for tool activation.
33
40
  */
34
41
 
35
- export function addMouseKeyHandlers(config, viewports) {
42
+ export function addMouseKeyHandlers(config) {
36
43
  if (!config) {
37
44
  config = DEFAULT_MOUSE_KEYS;
38
45
  }
39
46
 
47
+ if (onKeyDownFn) {
48
+ document.removeEventListener("keydown", onKeyDownFn);
49
+ }
50
+ if (onKeyUpFn) {
51
+ document.removeEventListener("keyup", onKeyUpFn);
52
+ }
53
+
40
54
  // Prevent context menu on right click
41
55
  document.addEventListener("contextmenu", evt => {
42
56
  evt.preventDefault();
43
57
  return false;
44
58
  });
45
59
 
60
+ // Define behaviour on key down: activate registered tool
46
61
  function onKeyDown(evt) {
47
62
  // keyboard shortcuts (activate on left mouse button)
48
63
  let codes = config.keyboard_shortcuts
@@ -56,11 +71,7 @@ export function addMouseKeyHandlers(config, viewports) {
56
71
  .filter(key => keyCodes[key] == evt.keyCode)
57
72
  .pop();
58
73
  if (config.debug) console.log("active", config.keyboard_shortcuts[key]);
59
- setToolActive(
60
- config.keyboard_shortcuts[key],
61
- { mouseButtonMask: 1 },
62
- viewports
63
- );
74
+ setToolActive(config.keyboard_shortcuts[key], { mouseButtonMask: 1 });
64
75
  document.addEventListener("keydown", onKeyDown, { once: true });
65
76
  }
66
77
  // right drag + shift
@@ -70,11 +81,7 @@ export function addMouseKeyHandlers(config, viewports) {
70
81
  evt.keyCode == keyCodes.KEY_SHIFT
71
82
  ) {
72
83
  if (config.debug) console.log("active", config.mouse_button_right.shift);
73
- setToolActive(
74
- config.mouse_button_right.shift,
75
- { mouseButtonMask: 2 },
76
- viewports
77
- );
84
+ setToolActive(config.mouse_button_right.shift, { mouseButtonMask: 2 });
78
85
  document.addEventListener("keyup", onKeyUp, { once: true });
79
86
  }
80
87
  // right drag + ctrl
@@ -84,11 +91,7 @@ export function addMouseKeyHandlers(config, viewports) {
84
91
  evt.keyCode == keyCodes.KEY_CONTROL
85
92
  ) {
86
93
  if (config.debug) console.log("active", config.mouse_button_right.ctrl);
87
- setToolActive(
88
- config.mouse_button_right.ctrl,
89
- { mouseButtonMask: 2 },
90
- viewports
91
- );
94
+ setToolActive(config.mouse_button_right.ctrl, { mouseButtonMask: 2 });
92
95
  document.addEventListener("keyup", onKeyUp, { once: true });
93
96
  }
94
97
  // leave default
@@ -98,36 +101,36 @@ export function addMouseKeyHandlers(config, viewports) {
98
101
  }
99
102
  }
100
103
 
104
+ // Define behaviour on key up: restore original tool
101
105
  function onKeyUp(e) {
102
106
  if (config.debug)
103
107
  console.log("active default", config.mouse_button_right.default);
104
- setToolActive(
105
- config.mouse_button_right.default,
106
- { mouseButtonMask: 2 },
107
- viewports
108
- );
108
+ setToolActive(config.mouse_button_right.default, { mouseButtonMask: 2 });
109
109
  document.addEventListener("keydown", onKeyDown, { once: true });
110
110
  }
111
111
 
112
112
  // activate default, if any
113
-
114
113
  if (config.mouse_button_right && config.mouse_button_right.default) {
115
- setToolActive(
116
- config.mouse_button_right.default,
117
- { mouseButtonMask: 2 },
118
- viewports
119
- );
114
+ setToolActive(config.mouse_button_right.default, { mouseButtonMask: 2 });
120
115
  }
121
116
 
122
117
  if (config.mouse_button_left && config.mouse_button_left.default) {
123
- setToolActive(
124
- config.mouse_button_left.default,
125
- { mouseButtonMask: 1 },
126
- viewports
127
- );
118
+ setToolActive(config.mouse_button_left.default, { mouseButtonMask: 1 });
128
119
  }
129
120
 
130
121
  document.addEventListener("keydown", onKeyDown, { once: true });
122
+
123
+ onKeyDownFn = onKeyDown;
124
+ onKeyUpFn = onKeyUp;
125
+ }
126
+
127
+ /**
128
+ *
129
+ */
130
+ export function removeMouseKeyHandlers() {
131
+ document.removeEventListener("keydown", onKeyDownFn);
132
+ onKeyDownFn = null;
133
+ onKeyUpFn = null;
131
134
  }
132
135
 
133
136
  /**
@@ -18,6 +18,7 @@ import { updateStackToolState } from "../imageTools";
18
18
 
19
19
  // custom code
20
20
  import { setLabelmap3DForElement } from "./setLabelMap3D";
21
+ import { each } from "hammerjs";
21
22
  // override function
22
23
  setters.labelmap3DForElement = setLabelmap3DForElement;
23
24
 
@@ -419,22 +420,37 @@ export function clearSegmentationState() {
419
420
 
420
421
  /**
421
422
  * Enable brushing
422
- * @param {Number} dimension - The initial brush radius
423
- * @param {Array} thresholds - The threshold values (min and max)
423
+ * NOTE: if options contains `thresholds`, ThresholdsBrush is activated, otherwise BrushTool is activated.
424
+ * Anyway, the activated tool name is returned
425
+ * @param {Object} options - An object containing configuration values (eg radius, thresholds, etc...)
424
426
  */
425
- export function enableBrushTool(dimension, thresholds) {
426
- segModule.configuration.radius = dimension;
427
- segModule.configuration.thresholds = thresholds;
428
- setToolActive("ThresholdsBrush");
427
+ export function enableBrushTool(viewports, options) {
428
+ console.log("enable", options);
429
+ setBrushProps(options);
430
+ const brushType = "thresholds" in options ? "ThresholdsBrush" : "Brush";
431
+ setToolActive(brushType, viewports);
432
+ return brushType;
429
433
  }
430
434
 
431
435
  /**
432
436
  * Disable brushing
433
- * @param {String} toolToActivate - The name of the tool to activate after removing the brush
437
+ * This function disables both brush tools, if found active on `viewports`
438
+ * @param {String} toolToActivate - The name of the tool to activate after removing the brush @optional
434
439
  */
435
- export function disableBrushTool(toolToActivate) {
436
- setToolDisabled("Brush");
437
- setToolActive(toolToActivate);
440
+ export function disableBrushTool(viewports, toolToActivate) {
441
+ each(viewports, viewport => {
442
+ const el = document.getElementById(viewport);
443
+ if (cornerstoneTools.isToolActiveForElement(el, "ThresholdsBrush")) {
444
+ setToolDisabled("ThresholdsBrush", [viewport]);
445
+ }
446
+ if (cornerstoneTools.isToolActiveForElement(el, "Brush")) {
447
+ setToolDisabled("Brush", [viewport]);
448
+ }
449
+ });
450
+
451
+ if (toolToActivate) {
452
+ setToolActive(toolToActivate);
453
+ }
438
454
  }
439
455
 
440
456
  /**
@@ -29,7 +29,7 @@ TODO
29
29
 
30
30
  # Larvitar segmentation API
31
31
 
32
- TODO
32
+ To enable brush tools, user can call directly the `setToolActive` / `setToolDisabled` api, in this case he has to handle brush type (thresholds or not), props (radius etc) and tool switching. Otherwise, Larvitar implements the utility functions `enableBrushTool` and `disableBrushTool` that internally handle brush type and props with a single call.
33
33
 
34
34
  # Customization
35
35
 
package/index.js CHANGED
@@ -205,6 +205,7 @@ import { saveAnnotations, loadAnnotations } from "./imaging/tools/io";
205
205
 
206
206
  import {
207
207
  addMouseKeyHandlers,
208
+ removeMouseKeyHandlers,
208
209
  toggleMouseToolsListeners
209
210
  } from "./imaging/tools/interaction";
210
211
 
@@ -367,6 +368,7 @@ export {
367
368
  loadAnnotations,
368
369
  // tools/interaction
369
370
  addMouseKeyHandlers,
371
+ removeMouseKeyHandlers,
370
372
  // tools/segmentation
371
373
  initSegmentationModule,
372
374
  addSegmentationMask,
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "medical",
7
7
  "cornerstone"
8
8
  ],
9
- "version": "1.3.3",
9
+ "version": "1.4.0",
10
10
  "description": "javascript library for parsing, loading, rendering and interacting with DICOM images",
11
11
  "repository": {
12
12
  "url": "https://github.com/dvisionlab/Larvitar.git",
@@ -54,4 +54,4 @@
54
54
  "module": "dist/larvitar.js",
55
55
  "browser": "dist/larvitar.js",
56
56
  "type": "module"
57
- }
57
+ }