@@ -24,6 +24,7 @@ setMeshFiltersPipelinesUrl(pipelinesBaseUrl)
24
24
async function main ( ) {
25
25
const niimath = new Niimath ( )
26
26
await niimath . init ( )
27
+ niimath . setOutputDataType ( 'input' ) // call before setting image since this is passed to the image constructor
27
28
aboutBtn . onclick = function ( ) {
28
29
const url = "https://github.com/niivue/brain2print"
29
30
window . open ( url , "_blank" )
@@ -153,7 +154,7 @@ async function main() {
153
154
let cmap = await fetchJSON ( modelEntry . colormapPath )
154
155
overlayVolume . setColormapLabel ( cmap )
155
156
// n.b. most models create indexed labels, but those without colormap mask scalar input
156
- overlayVolume . hdr . intent_code = 1002 ; // NIFTI_INTENT_LABEL
157
+ overlayVolume . hdr . intent_code = 1002 // NIFTI_INTENT_LABEL
157
158
} else {
158
159
let colormap = opts . atlasSelectedColorTable . toLowerCase ( )
159
160
const cmaps = nv1 . colormaps ( )
@@ -204,7 +205,64 @@ async function main() {
204
205
remeshDialog . show ( )
205
206
}
206
207
}
208
+ qualitySelect . onchange = function ( ) {
209
+ const isBetterQuality = Boolean ( Number ( qualitySelect . value ) )
210
+ const opacity = 1.0 - ( 0.5 * Number ( isBetterQuality ) )
211
+ largestCheck . disabled = isBetterQuality
212
+ largestClusterGroup . style . opacity = opacity
213
+ bubbleCheck . disabled = isBetterQuality
214
+ bubbleGroup . style . opacity = opacity
215
+ closeMM . disabled = isBetterQuality
216
+ closeGroup . style . opacity = opacity
217
+ }
207
218
applyBtn . onclick = async function ( ) {
219
+ const isBetterQuality = Boolean ( Number ( qualitySelect . value ) )
220
+ const startTime = performance . now ( )
221
+ if ( isBetterQuality )
222
+ await applyQuality ( )
223
+ else
224
+ await applyFaster ( )
225
+ console . log ( `Execution time: ${ Math . round ( performance . now ( ) - startTime ) } ms` )
226
+ }
227
+ async function applyFaster ( ) {
228
+ const niiBuffer = await nv1 . saveImage ( { volumeByIndex : nv1 . volumes . length - 1 } ) . buffer
229
+ const niiFile = new File ( [ niiBuffer ] , 'image.nii' )
230
+ let processor = niimath . image ( niiFile )
231
+ loadingCircle . classList . remove ( 'hidden' )
232
+ //mesh with specified isosurface
233
+ const isoValue = 0.5
234
+ //const largestCheckValue = largestCheck.checked
235
+ let reduce = Math . min ( Math . max ( Number ( shrinkPct . value ) / 100 , 0.01 ) , 1 )
236
+ let hollowSz = Number ( hollowSelect . value )
237
+ let closeSz = Number ( closeMM . value )
238
+ const pixDim = Math . min ( Math . min ( nv1 . volumes [ 0 ] . hdr . pixDims [ 1 ] , nv1 . volumes [ 0 ] . hdr . pixDims [ 2 ] ) , nv1 . volumes [ 0 ] . hdr . pixDims [ 3 ] )
239
+ if ( ( pixDim < 0.2 ) && ( ( hollowSz !== 0 ) || ( closeSz !== 0 ) ) ) {
240
+ hollowSz *= pixDim
241
+ closeSz *= pixDim
242
+ console . log ( 'Very small pixels, scaling hollow and close values by ' , pixDim )
243
+ }
244
+ if ( hollowSz < 0 ) {
245
+ processor = processor . hollow ( 0.5 , hollowSz )
246
+ }
247
+ if ( ( isFinite ( closeSz ) ) && ( closeSz > 0 ) ) {
248
+ processor = processor . close ( isoValue , closeSz , 2 * closeSz )
249
+ }
250
+ processor = processor . mesh ( {
251
+ i : isoValue ,
252
+ l : largestCheck . checked ? 1 : 0 ,
253
+ r : reduce ,
254
+ b : bubbleCheck . checked ? 1 : 0
255
+ } )
256
+ console . log ( 'niimath operation' , processor . commands )
257
+ const retBlob = await processor . run ( 'test.mz3' )
258
+ const arrayBuffer = await retBlob . arrayBuffer ( )
259
+ loadingCircle . classList . add ( 'hidden' )
260
+ if ( nv1 . meshes . length > 0 )
261
+ nv1 . removeMesh ( nv1 . meshes [ 0 ] )
262
+ await nv1 . loadFromArrayBuffer ( arrayBuffer , 'test.mz3' )
263
+ nv1 . reverseFaces ( 0 )
264
+ }
265
+ async function applyQuality ( ) {
208
266
const volIdx = nv1 . volumes . length - 1
209
267
let hdr = nv1 . volumes [ volIdx ] . hdr
210
268
let img = nv1 . volumes [ volIdx ] . img
@@ -214,17 +272,13 @@ async function main() {
214
272
const niiBuffer = await nv1 . saveImage ( { volumeByIndex : nv1 . volumes . length - 1 } ) . buffer
215
273
const niiBlob = new Blob ( [ niiBuffer ] , { type : 'application/octet-stream' } )
216
274
const niiFile = new File ( [ niiBlob ] , 'input.nii' )
217
- // with niimath wasm ZLIB builds, isGz seems to be the default output type:
218
- // see: https://github.com/rordenlab/niimath/blob/9f3a301be72c331b90ef5baecb7a0232e9b47ba4/src/core.c#L201
219
- // also added new option to set outputDataType in niimath in version 0.3.0 (published 20 Dec 2024)
220
275
niimath . setOutputDataType ( 'input' ) // call before setting image since this is passed to the image constructor
221
276
let image = niimath . image ( niiFile )
277
+ image = image . gz ( 0 )
278
+ image = image . ras ( )
222
279
image = image . hollow ( 0.5 , hollowInt )
223
- // must use .gz extension because niimath will create .nii.gz by default, so
224
- // wasm file system commands will look for this, not .nii.
225
- // Error 44 will happen otherwise (file not found error)
226
- const outBlob = await image . run ( 'output.nii.gz' )
227
- let outFile = new File ( [ outBlob ] , 'hollow.nii.gz' )
280
+ const outBlob = await image . run ( 'output.nii' )
281
+ let outFile = new File ( [ outBlob ] , 'hollow.nii' )
228
282
const outVol = await NVImage . loadFromFile ( {
229
283
file : outFile ,
230
284
name : outFile . name
@@ -236,7 +290,6 @@ async function main() {
236
290
loadingCircle . classList . remove ( "hidden" )
237
291
meshProcessingMsg . classList . remove ( "hidden" )
238
292
meshProcessingMsg . textContent = "Generating mesh from segmentation"
239
-
240
293
const itkImage = nii2iwi ( hdr , img , false )
241
294
itkImage . size = itkImage . size . map ( Number )
242
295
const { mesh } = await antiAliasCuberille ( itkImage , { noClosing : true } )
@@ -254,6 +307,7 @@ async function main() {
254
307
meshProcessingMsg . textContent = "Smoothing and remeshing"
255
308
const smooth = parseInt ( smoothSlide . value )
256
309
const shrink = parseFloat ( shrinkPct . value )
310
+ console . log ( `smoothing iterations ${ smooth } shrink percent ${ shrink } ` )
257
311
const { outputMesh : smoothedMesh } = await smoothRemesh ( largestOnly , { newtonIterations : smooth , numberPoints : shrink } )
258
312
const { outputMesh : smoothedRepairedMesh } = await repair ( smoothedMesh , { maximumHoleArea : 50.0 } )
259
313
const niiMesh = iwm2meshCore ( smoothedRepairedMesh )
@@ -302,13 +356,14 @@ async function main() {
302
356
option . value = inferenceModelsList [ i ] . id . toString ( )
303
357
modelSelect . appendChild ( option )
304
358
}
359
+ qualitySelect . onchange ( )
305
360
nv1 . onImageLoaded = doLoadImage
306
361
modelSelect . selectedIndex = - 1
307
- workerCheck . checked = await isChrome ( ) ; //TODO: Safari does not yet support WebGL TFJS webworkers, test FireFox
362
+ workerCheck . checked = await isChrome ( ) //TODO: Safari does not yet support WebGL TFJS webworkers, test FireFox
308
363
console . log ( 'brain2print 20241218' )
309
364
// uncomment next two lines to automatically run segmentation when web page is loaded
310
- // modelSelect.selectedIndex = 11
311
- // modelSelect.onchange()
365
+ // modelSelect.selectedIndex = 11
366
+ // modelSelect.onchange()
312
367
}
313
368
314
369
main ( )
0 commit comments