larvitar 1.1.2 → 1.2.3
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/.github/workflows/build-docs.yml +1 -1
- package/.github/workflows/deploy.yml +2 -11
- package/README.md +3 -3
- package/imaging/imageParsing.js +1 -0
- package/imaging/imageStore.js +16 -0
- package/imaging/loaders/commonLoader.js +6 -2
- package/imaging/tools/segmentation.js +12 -2
- package/imaging/tools/segmentations.md +6 -0
- package/imaging/tools/setLabelMap3D.js +248 -0
- package/modules/vuex/larvitar.js +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This is a basic workflow to help you get started with Actions
|
|
2
2
|
|
|
3
|
-
name: CI
|
|
3
|
+
name: CI-Release
|
|
4
4
|
|
|
5
5
|
# Controls when the action will run. Triggers the workflow on push or pull request
|
|
6
6
|
# events but only for the master branch
|
|
@@ -30,17 +30,8 @@ jobs:
|
|
|
30
30
|
cd ${{ github.workspace }}
|
|
31
31
|
rm -rf docs
|
|
32
32
|
|
|
33
|
-
# Install npm-cli-login
|
|
34
|
-
- name: Install npm-cli-login
|
|
35
|
-
run: |
|
|
36
|
-
npm install -g npm-cli-login
|
|
37
|
-
|
|
38
|
-
# Login to npm
|
|
39
|
-
- name: Login to npm
|
|
40
|
-
run: |
|
|
41
|
-
npm-cli-login -u '${{ secrets.NPM_USERNAME }}' -p '${{ secrets.NPM_PASSWORD }}' -e '${{ secrets.NPM_EMAIL }}'
|
|
42
|
-
|
|
43
33
|
# Publish to npm
|
|
44
34
|
- name: Publish to npm
|
|
45
35
|
run: |
|
|
36
|
+
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
|
46
37
|
npm publish
|
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
## Dicom Image Toolkit for CornestoneJS
|
|
8
8
|
|
|
9
|
-
### Current version: 1.
|
|
9
|
+
### Current version: 1.2.3
|
|
10
10
|
|
|
11
|
-
### Latest Stable version: 1.
|
|
11
|
+
### Latest Stable version: 1.2.3
|
|
12
12
|
|
|
13
|
-
### Latest Published Release: 1.
|
|
13
|
+
### Latest Published Release: 1.2.3
|
|
14
14
|
|
|
15
15
|
This library provides common DICOM functionalities to be used in web-applications: it's wrapper that simplifies the use of cornestone-js environment.
|
|
16
16
|
Orthogonal multiplanar reformat is included as well as custom loader/exporter for nrrd files and [Vuex](https://vuex.vuejs.org/) custom integration.
|
package/imaging/imageParsing.js
CHANGED
|
@@ -278,6 +278,7 @@ let parseFile = function (file) {
|
|
|
278
278
|
imageObject.metadata.frameTime = metadata["x00181063"];
|
|
279
279
|
imageObject.metadata.frameDelay = metadata["x00181066"];
|
|
280
280
|
}
|
|
281
|
+
imageObject.metadata.isMultiframe = isMultiframe;
|
|
281
282
|
imageObject.metadata.windowCenter = metadata["x00281050"];
|
|
282
283
|
imageObject.metadata.windowWidth = metadata["x00281051"];
|
|
283
284
|
imageObject.metadata.minPixelValue = metadata["x00280106"];
|
package/imaging/imageStore.js
CHANGED
|
@@ -171,6 +171,22 @@ class Larvitar_Store {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Removes all the series from the store
|
|
176
|
+
* @function resetSeriesIds
|
|
177
|
+
*/
|
|
178
|
+
resetSeriesIds(seriesId) {
|
|
179
|
+
if (this.VUEX_STORE) {
|
|
180
|
+
let dispatch = "resetSeriesIds";
|
|
181
|
+
let route = this.vuex_module
|
|
182
|
+
? this.vuex_module + "/" + dispatch
|
|
183
|
+
: dispatch;
|
|
184
|
+
this.vuex_store.dispatch(route, seriesId);
|
|
185
|
+
} else {
|
|
186
|
+
delete this.state.series[seriesId];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
174
190
|
/**
|
|
175
191
|
* Set a value into store
|
|
176
192
|
* @function set
|
|
@@ -38,10 +38,14 @@ export const updateLarvitarManager = function (imageObject, customId) {
|
|
|
38
38
|
if (larvitarManager === null) {
|
|
39
39
|
larvitarManager = {};
|
|
40
40
|
}
|
|
41
|
+
|
|
41
42
|
let seriesId = customId || imageObject.seriesUID;
|
|
42
43
|
let data = { ...imageObject };
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
if (data.metadata.isMultiframe) {
|
|
46
|
+
seriesId = customId || imageObject.metadata.seriesUID;
|
|
47
|
+
updateLoadedStack(data, larvitarManager, customId);
|
|
48
|
+
buildMultiFrameImage(seriesId, larvitarManager[seriesId]);
|
|
45
49
|
} else {
|
|
46
50
|
updateLoadedStack(data, larvitarManager, customId);
|
|
47
51
|
}
|
|
@@ -14,6 +14,11 @@ const { getters, setters } = segModule;
|
|
|
14
14
|
import { setToolActive, setToolDisabled } from "./main";
|
|
15
15
|
import { isElement } from "../imageUtils";
|
|
16
16
|
|
|
17
|
+
// custom code
|
|
18
|
+
import { setLabelmap3DForElement } from "./setLabelMap3D";
|
|
19
|
+
// override function
|
|
20
|
+
setters.labelmap3DForElement = setLabelmap3DForElement;
|
|
21
|
+
|
|
17
22
|
// General segmentation cs tools module configuration
|
|
18
23
|
const config = {
|
|
19
24
|
arrayType: 0,
|
|
@@ -172,7 +177,7 @@ export function initSegmentationModule(customConfig) {
|
|
|
172
177
|
* @returns {Promise} - Return a promise which will resolve when segmentation mask is added
|
|
173
178
|
*/
|
|
174
179
|
export function addSegmentationMask(props, data, elementId) {
|
|
175
|
-
let promise = new Promise(resolve => {
|
|
180
|
+
let promise = new Promise(async resolve => {
|
|
176
181
|
let element = isElement(elementId)
|
|
177
182
|
? elementId
|
|
178
183
|
: document.getElementById(elementId);
|
|
@@ -180,7 +185,12 @@ export function addSegmentationMask(props, data, elementId) {
|
|
|
180
185
|
console.error("invalid html element: " + elementId);
|
|
181
186
|
return;
|
|
182
187
|
}
|
|
183
|
-
|
|
188
|
+
|
|
189
|
+
const res = await setters.labelmap3DForElement(
|
|
190
|
+
element,
|
|
191
|
+
data.buffer,
|
|
192
|
+
props.labelId
|
|
193
|
+
);
|
|
184
194
|
// if user set a color property, use that color for all segments on the labelmap
|
|
185
195
|
let lut = props.color
|
|
186
196
|
? generateUniformLUT(props.color, props.opacity)
|
|
@@ -30,3 +30,9 @@ TODO
|
|
|
30
30
|
# Larvitar segmentation API
|
|
31
31
|
|
|
32
32
|
TODO
|
|
33
|
+
|
|
34
|
+
# Customization
|
|
35
|
+
|
|
36
|
+
Some function in larvitar overrides the default behaviour of cornerstone tools, here is a list of them:
|
|
37
|
+
|
|
38
|
+
- `setters.labelmap3DForElement` is overridden to have a non-blocking behaviour, the custom code is in ./setLabelMap3D.js, same as original code in cs tools repo.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a custom version of setLabelMap3D from cs tools source code
|
|
3
|
+
* This let us implement a non-blocking version of the for loop that loads 3d labelmaps on a volume
|
|
4
|
+
* @ronzim
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// import getElement from "./getElement";
|
|
8
|
+
// import { getToolState } from "../../../stateManagement/toolState.js";
|
|
9
|
+
// import state from "./state";
|
|
10
|
+
// import getSegmentsOnPixelData from "./getSegmentsOnPixeldata";
|
|
11
|
+
// import { triggerLabelmapModifiedEvent } from "../../../util/segmentation";
|
|
12
|
+
// import ARRAY_TYPES from "./arrayTypes";
|
|
13
|
+
// import { getModule } from "../../index.js";
|
|
14
|
+
|
|
15
|
+
const ARRAY_TYPES = {
|
|
16
|
+
UINT_16_ARRAY: 0,
|
|
17
|
+
FLOAT_32_ARRAY: 1
|
|
18
|
+
};
|
|
19
|
+
const { UINT_16_ARRAY, FLOAT_32_ARRAY } = ARRAY_TYPES;
|
|
20
|
+
|
|
21
|
+
import cornerstoneTools from "cornerstone-tools/dist/cornerstoneTools.js";
|
|
22
|
+
const { triggerLabelmapModifiedEvent } = cornerstoneTools.importInternal(
|
|
23
|
+
"util/segmentationUtils"
|
|
24
|
+
);
|
|
25
|
+
const getModule = cornerstoneTools.getModule;
|
|
26
|
+
const getToolState = cornerstoneTools.getToolState;
|
|
27
|
+
const storeGetters = cornerstoneTools.store.getters;
|
|
28
|
+
const state = getModule("segmentation").state;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* These function are reported since they are not exposed by cs tools
|
|
32
|
+
*
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// from getSegmentsOnPixelData.js
|
|
36
|
+
function getSegmentsOnPixelData(pixelData) {
|
|
37
|
+
return [...new Set(pixelData)];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// from getElement.js
|
|
41
|
+
function getElement(elementOrEnabledElementUID) {
|
|
42
|
+
if (elementOrEnabledElementUID instanceof HTMLElement) {
|
|
43
|
+
return elementOrEnabledElementUID;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return storeGetters.enabledElementByUID(elementOrEnabledElementUID);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Takes a 16-bit encoded `ArrayBuffer` and stores it as a `Labelmap3D` for the
|
|
51
|
+
* `BrushStackState` associated with the element.
|
|
52
|
+
*
|
|
53
|
+
* @param {HTMLElement|string} elementOrEnabledElementUID The cornerstone
|
|
54
|
+
* enabled element or its UUID.
|
|
55
|
+
* @param {ArrayBuffer} buffer
|
|
56
|
+
* @param {number} labelmapIndex The index to store the labelmap under.
|
|
57
|
+
* @param {Object[]} metadata = [] Any metadata about the segments.
|
|
58
|
+
* @param {number[][]} [segmentsOnLabelmapArray] An array of array of segments on each imageIdIndex.
|
|
59
|
+
* If not present, is calculated.
|
|
60
|
+
* @param {colorLUTIndex} [colorLUTIndex = 0] The index of the colorLUT to use to render the segmentation.
|
|
61
|
+
* @returns {null}
|
|
62
|
+
*/
|
|
63
|
+
async function setLabelmap3DForElement(
|
|
64
|
+
elementOrEnabledElementUID,
|
|
65
|
+
buffer,
|
|
66
|
+
labelmapIndex,
|
|
67
|
+
metadata = [],
|
|
68
|
+
segmentsOnLabelmapArray,
|
|
69
|
+
colorLUTIndex = 0
|
|
70
|
+
) {
|
|
71
|
+
const element = getElement(elementOrEnabledElementUID);
|
|
72
|
+
|
|
73
|
+
if (!element) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stackState = getToolState(element, "stack");
|
|
78
|
+
const numberOfFrames = stackState.data[0].imageIds.length;
|
|
79
|
+
const firstImageId = stackState.data[0].imageIds[0];
|
|
80
|
+
|
|
81
|
+
const res = await setLabelmap3DByFirstImageId(
|
|
82
|
+
firstImageId,
|
|
83
|
+
buffer,
|
|
84
|
+
labelmapIndex,
|
|
85
|
+
metadata,
|
|
86
|
+
numberOfFrames,
|
|
87
|
+
segmentsOnLabelmapArray,
|
|
88
|
+
colorLUTIndex
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
triggerLabelmapModifiedEvent(element, labelmapIndex);
|
|
92
|
+
|
|
93
|
+
return res;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Takes an 16-bit encoded `ArrayBuffer` and stores it as a `Labelmap3D` for
|
|
98
|
+
* the `BrushStackState` associated with the firstImageId.
|
|
99
|
+
*
|
|
100
|
+
* @param {HTMLElement|string} firstImageId The firstImageId of the series to
|
|
101
|
+
* store the segmentation on.
|
|
102
|
+
* @param {ArrayBuffer} buffer
|
|
103
|
+
* @param {number} labelmapIndex The index to store the labelmap under.
|
|
104
|
+
* @param {Object[]} metadata = [] Any metadata about the segments.
|
|
105
|
+
* @param {number} numberOfFrames The number of frames, required to set up the
|
|
106
|
+
* relevant labelmap2D views.
|
|
107
|
+
* @param {number[][]} [segmentsOnLabelmapArray] An array of array of segments on each imageIdIndex.
|
|
108
|
+
* If not present, is calculated.
|
|
109
|
+
* @param {colorLUTIndex} [colorLUTIndex = 0] The index of the colorLUT to use to render the segmentation.
|
|
110
|
+
* @returns {null}
|
|
111
|
+
*/
|
|
112
|
+
function setLabelmap3DByFirstImageId(
|
|
113
|
+
firstImageId,
|
|
114
|
+
buffer,
|
|
115
|
+
labelmapIndex,
|
|
116
|
+
metadata = [],
|
|
117
|
+
numberOfFrames,
|
|
118
|
+
segmentsOnLabelmapArray,
|
|
119
|
+
colorLUTIndex = 0
|
|
120
|
+
) {
|
|
121
|
+
const { configuration } = getModule("segmentation");
|
|
122
|
+
|
|
123
|
+
let brushStackState = state.series[firstImageId];
|
|
124
|
+
|
|
125
|
+
if (!brushStackState) {
|
|
126
|
+
state.series[firstImageId] = {
|
|
127
|
+
activeLabelmapIndex: labelmapIndex,
|
|
128
|
+
labelmaps3D: []
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
brushStackState = state.series[firstImageId];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
brushStackState.labelmaps3D[labelmapIndex] = {
|
|
135
|
+
buffer,
|
|
136
|
+
labelmaps2D: [],
|
|
137
|
+
metadata,
|
|
138
|
+
activeSegmentIndex: 1,
|
|
139
|
+
colorLUTIndex,
|
|
140
|
+
segmentsHidden: [],
|
|
141
|
+
undo: [],
|
|
142
|
+
redo: []
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const labelmaps2D = brushStackState.labelmaps3D[labelmapIndex].labelmaps2D;
|
|
146
|
+
const slicelengthInBytes = buffer.byteLength / numberOfFrames;
|
|
147
|
+
|
|
148
|
+
/* non-blocking implementation by @ronzim */
|
|
149
|
+
|
|
150
|
+
return new Promise(resolve => {
|
|
151
|
+
function setSingleSlice(i, numberOfFrames) {
|
|
152
|
+
var pixelData = void 0;
|
|
153
|
+
|
|
154
|
+
switch (configuration.arrayType) {
|
|
155
|
+
case UINT_16_ARRAY:
|
|
156
|
+
pixelData = new Uint16Array(
|
|
157
|
+
buffer,
|
|
158
|
+
slicelengthInBytes * i, // 2 bytes/voxel
|
|
159
|
+
slicelengthInBytes / 2
|
|
160
|
+
);
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case FLOAT_32_ARRAY:
|
|
164
|
+
pixelData = new Float32Array(
|
|
165
|
+
buffer,
|
|
166
|
+
slicelengthInBytes * i,
|
|
167
|
+
slicelengthInBytes / 4
|
|
168
|
+
);
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
default:
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Unsupported Array Type ".concat(configuration.arrayType)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
var segmentsOnLabelmap = segmentsOnLabelmapArray
|
|
178
|
+
? segmentsOnLabelmapArray[i]
|
|
179
|
+
: getSegmentsOnPixelData(pixelData);
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
segmentsOnLabelmap &&
|
|
183
|
+
segmentsOnLabelmap.some(function (segment) {
|
|
184
|
+
return segment;
|
|
185
|
+
})
|
|
186
|
+
) {
|
|
187
|
+
labelmaps2D[i] = {
|
|
188
|
+
pixelData: pixelData,
|
|
189
|
+
segmentsOnLabelmap: segmentsOnLabelmap
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
++i;
|
|
194
|
+
|
|
195
|
+
if (i < numberOfFrames) {
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
setSingleSlice(i, numberOfFrames);
|
|
198
|
+
}, 0);
|
|
199
|
+
} else {
|
|
200
|
+
resolve("OK");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
setSingleSlice(0, numberOfFrames);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/* original implementation
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < numberOfFrames; i++) {
|
|
210
|
+
let pixelData;
|
|
211
|
+
|
|
212
|
+
switch (configuration.arrayType) {
|
|
213
|
+
case UINT_16_ARRAY:
|
|
214
|
+
pixelData = new Uint16Array(
|
|
215
|
+
buffer,
|
|
216
|
+
slicelengthInBytes * i, // 2 bytes/voxel
|
|
217
|
+
slicelengthInBytes / 2
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case FLOAT_32_ARRAY:
|
|
223
|
+
pixelData = new Float32Array(
|
|
224
|
+
buffer,
|
|
225
|
+
slicelengthInBytes * i,
|
|
226
|
+
slicelengthInBytes / 4
|
|
227
|
+
);
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
default:
|
|
231
|
+
throw new Error(`Unsupported Array Type ${configuration.arrayType}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const segmentsOnLabelmap = segmentsOnLabelmapArray
|
|
235
|
+
? segmentsOnLabelmapArray[i]
|
|
236
|
+
: getSegmentsOnPixelData(pixelData);
|
|
237
|
+
|
|
238
|
+
if (segmentsOnLabelmap && segmentsOnLabelmap.some(segment => segment)) {
|
|
239
|
+
labelmaps2D[i] = {
|
|
240
|
+
pixelData,
|
|
241
|
+
segmentsOnLabelmap
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
*/
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { setLabelmap3DByFirstImageId, setLabelmap3DForElement };
|
package/modules/vuex/larvitar.js
CHANGED
package/package.json
CHANGED