Skip to content

Commit 59f437d

Browse files
committed
reintroduce faster nii2mesh option
1 parent 78fe6c2 commit 59f437d

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed

index.html

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,25 @@
7878
</select>
7979
</label>
8080
</p>
81+
<p>
82+
<label>
83+
Quality:
84+
<select id="qualitySelect" title="choose better or faster meshing">
85+
<option value="0">faster (can create defects)</option>
86+
<option value="1" selected>better</option>
87+
</select>
88+
</label>
89+
</p>
90+
<p id="largestClusterGroup">
91+
<label>&nbsp;Largest cluster only</label><input type="checkbox" id="largestCheck" unchecked/>
92+
</p>
93+
<p id="bubbleGroup">
94+
<label>&nbsp;Fill bubbles</label><input type="checkbox" id="bubbleCheck" unchecked/>
95+
</p>
96+
<p id="closeGroup">
97+
<label>Closing (mm)</label>
98+
<input id="closeMM" type="number" min="0" value="4" max="100">
99+
</p>
81100
<p>
82101
<label for="smoothSlide">Smoothing</label>
83102
<input
@@ -91,8 +110,8 @@
91110
/>
92111
</p>
93112
<p>
94-
<label>Simplify Percent (10..150)</label>
95-
<input title="triangle reduction reduces the file size and computation time but can reduce mesh quality, smaller values result in a smaller mesh" id="shrinkPct" type="number" min="10" value="25" max="150">
113+
<label>Simplify Percent (10..100)</label>
114+
<input title="triangle reduction reduces the file size and computation time but can reduce mesh quality, smaller values result in a smaller mesh" id="shrinkPct" type="number" min="10" value="25" max="100">
96115
</p>
97116
<button id="cancelBtn" formmethod="dialog">Cancel</button>
98117
<button autofocus id="applyBtn" value="default">Apply</button>

main.js

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ setMeshFiltersPipelinesUrl(pipelinesBaseUrl)
2424
async function main() {
2525
const niimath = new Niimath()
2626
await niimath.init()
27+
niimath.setOutputDataType('input') // call before setting image since this is passed to the image constructor
2728
aboutBtn.onclick = function () {
2829
const url = "https://github.com/niivue/brain2print"
2930
window.open(url, "_blank")
@@ -153,7 +154,7 @@ async function main() {
153154
let cmap = await fetchJSON(modelEntry.colormapPath)
154155
overlayVolume.setColormapLabel(cmap)
155156
// 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
157158
} else {
158159
let colormap = opts.atlasSelectedColorTable.toLowerCase()
159160
const cmaps = nv1.colormaps()
@@ -204,7 +205,64 @@ async function main() {
204205
remeshDialog.show()
205206
}
206207
}
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+
}
207218
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() {
208266
const volIdx = nv1.volumes.length - 1
209267
let hdr = nv1.volumes[volIdx].hdr
210268
let img = nv1.volumes[volIdx].img
@@ -214,17 +272,13 @@ async function main() {
214272
const niiBuffer = await nv1.saveImage({volumeByIndex: nv1.volumes.length - 1}).buffer
215273
const niiBlob = new Blob([niiBuffer], { type: 'application/octet-stream' })
216274
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)
220275
niimath.setOutputDataType('input') // call before setting image since this is passed to the image constructor
221276
let image = niimath.image(niiFile)
277+
image = image.gz(0)
278+
image = image.ras()
222279
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')
228282
const outVol = await NVImage.loadFromFile({
229283
file: outFile,
230284
name: outFile.name
@@ -236,7 +290,6 @@ async function main() {
236290
loadingCircle.classList.remove("hidden")
237291
meshProcessingMsg.classList.remove("hidden")
238292
meshProcessingMsg.textContent = "Generating mesh from segmentation"
239-
240293
const itkImage = nii2iwi(hdr, img, false)
241294
itkImage.size = itkImage.size.map(Number)
242295
const { mesh } = await antiAliasCuberille(itkImage, { noClosing: true })
@@ -254,6 +307,7 @@ async function main() {
254307
meshProcessingMsg.textContent = "Smoothing and remeshing"
255308
const smooth = parseInt(smoothSlide.value)
256309
const shrink = parseFloat(shrinkPct.value)
310+
console.log(`smoothing iterations ${smooth} shrink percent ${shrink}`)
257311
const { outputMesh: smoothedMesh } = await smoothRemesh(largestOnly, { newtonIterations: smooth, numberPoints: shrink })
258312
const { outputMesh: smoothedRepairedMesh } = await repair(smoothedMesh, { maximumHoleArea: 50.0 })
259313
const niiMesh = iwm2meshCore(smoothedRepairedMesh)
@@ -302,13 +356,14 @@ async function main() {
302356
option.value = inferenceModelsList[i].id.toString()
303357
modelSelect.appendChild(option)
304358
}
359+
qualitySelect.onchange()
305360
nv1.onImageLoaded = doLoadImage
306361
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
308363
console.log('brain2print 20241218')
309364
// 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()
312367
}
313368

314369
main()

0 commit comments

Comments
 (0)