@niivue/niivue 0.44.2-dev.1 → 0.44.2-dev.2

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.
@@ -345,6 +345,9 @@ export class Niivue {
345
345
  drawUndoBitmaps: Uint8Array[] = [] // array of drawBitmaps for undo
346
346
  drawLut = cmapper.makeDrawLut('$itksnap') // the color lookup table for drawing
347
347
  drawOpacity = 0.8 // opacity of drawing (default)
348
+ clickToSegmentIsGrowing = false // flag to indicate if the clickToSegment flood fill growing is in progress with left mouse down + drag
349
+ clickToSegmentGrowingBitmap: Uint8Array | null = null // the bitmap of the growing flood fill
350
+ clickToSegmentXY = [0, 0] // the x,y location of the clickToSegment flood fill
348
351
  renderDrawAmbientOcclusion = 0.4
349
352
  colorbarHeight = 0 // height in pixels, set when colorbar is drawn
350
353
  drawPenLocation = [NaN, NaN, NaN]
@@ -1189,6 +1192,9 @@ export class Niivue {
1189
1192
  this.drawPenLocation = [NaN, NaN, NaN]
1190
1193
  this.drawPenAxCorSag = -1
1191
1194
  this.uiData.mousedown = true
1195
+ // reset drag positions used previously.
1196
+ this.setDragStart(0, 0)
1197
+ this.setDragEnd(0, 0)
1192
1198
  log.debug('mouse down')
1193
1199
  log.debug(e)
1194
1200
  // record tile where mouse clicked
@@ -1198,6 +1204,9 @@ export class Niivue {
1198
1204
  }
1199
1205
 
1200
1206
  const [x, y] = [pos.x * this.uiData.dpr!, pos.y * this.uiData.dpr!]
1207
+ if (this.opts.clickToSegment) {
1208
+ this.clickToSegmentXY = [x, y]
1209
+ }
1201
1210
  const label = this.getLabelAtPoint([x, y])
1202
1211
  if (label) {
1203
1212
  // check for user defined onclick handler
@@ -1418,8 +1427,18 @@ export class Niivue {
1418
1427
  if (this.drawPenFillPts.length > 0) {
1419
1428
  this.drawPenFilled()
1420
1429
  } else if (this.drawPenAxCorSag >= 0) {
1421
- this.drawAddUndoBitmap()
1430
+ if (this.opts.clickToSegment) {
1431
+ // copy clickToSegment bitmap to drawBitmap.
1432
+ // determine which bitmap has more non-zero values, indicating it is more recent
1433
+ const sumBitmap = this.sumBitmap(this.drawBitmap)
1434
+ const sumSeg = this.sumBitmap(this.clickToSegmentGrowingBitmap)
1435
+ if (sumSeg > sumBitmap) {
1436
+ this.updateBitmapFromClickToSegment()
1437
+ }
1438
+ }
1422
1439
  }
1440
+ // add the bitmap after the clickToSegment update
1441
+ this.drawAddUndoBitmap()
1423
1442
  this.drawPenLocation = [NaN, NaN, NaN]
1424
1443
  this.drawPenAxCorSag = -1
1425
1444
  if (isFunction(this.onMouseUp)) {
@@ -1784,6 +1803,40 @@ export class Niivue {
1784
1803
  if (this.thumbnailVisible) {
1785
1804
  return
1786
1805
  }
1806
+ // check that the user has actually created an ROI already.
1807
+ const dragStartSum = this.uiData.dragStart.reduce((a, b) => a + b, 0)
1808
+ const dragEndSum = this.uiData.dragEnd.reduce((a, b) => a + b, 0)
1809
+ const validDrag = dragStartSum > 0 && dragEndSum > 0
1810
+ // if dragMode is roiSelection, grow or shrink the selection box
1811
+ // by scrolling the mouse wheel. Grows by 1 pixel per scroll
1812
+ if (this.opts.dragMode === DRAG_MODE.roiSelection && validDrag) {
1813
+ const delta = e.deltaY > 0 ? 1 : -1
1814
+ // update the uiData.dragStart and uiData.dragEnd values to grow or shrink the selection box
1815
+ if (this.uiData.dragStart[0] < this.uiData.dragEnd[0]) {
1816
+ this.uiData.dragStart[0] -= delta
1817
+ this.uiData.dragEnd[0] += delta
1818
+ } else {
1819
+ this.uiData.dragStart[0] += delta
1820
+ this.uiData.dragEnd[0] -= delta
1821
+ }
1822
+ if (this.uiData.dragStart[1] < this.uiData.dragEnd[1]) {
1823
+ this.uiData.dragStart[1] -= delta
1824
+ this.uiData.dragEnd[1] += delta
1825
+ } else {
1826
+ this.uiData.dragStart[1] += delta
1827
+ this.uiData.dragEnd[1] -= delta
1828
+ }
1829
+ // draw the scene
1830
+ this.uiData.isDragging = true // set is dragging so the selection box is drawn for this drawScene call
1831
+ this.drawScene() // drawScene uses isDragging to determine if the selection box or ROI should be drawn
1832
+ this.uiData.isDragging = false // reset is dragging
1833
+ const tileIdx = this.tileIndex(this.uiData.dragStart[0], this.uiData.dragStart[1])
1834
+ this.generateMouseUpCallback(
1835
+ this.screenXY2TextureFrac(this.uiData.dragStart[0], this.uiData.dragStart[1], tileIdx),
1836
+ this.screenXY2TextureFrac(this.uiData.dragEnd[0], this.uiData.dragEnd[1], tileIdx)
1837
+ )
1838
+ return
1839
+ }
1787
1840
  const rect = this.canvas!.getBoundingClientRect()
1788
1841
  if (e.deltaY < 0) {
1789
1842
  this.sliceScroll2D(-0.01, e.clientX - rect.left, e.clientY - rect.top)
@@ -3382,6 +3435,12 @@ export class Niivue {
3382
3435
 
3383
3436
  // not included in public docs
3384
3437
  sliceScroll2D(posChange: number, x: number, y: number, isDelta = true): void {
3438
+ // check if the canvas has focus
3439
+ if (this.opts.scrollRequiresFocus && this.canvas !== document.activeElement) {
3440
+ log.warn('Canvas element does not have focus. Scroll events will not be processed.')
3441
+ return
3442
+ }
3443
+
3385
3444
  if (this.inGraphTile(x, y)) {
3386
3445
  let vol = this.volumes[0].frame4D
3387
3446
  if (posChange > 0) {
@@ -4125,6 +4184,7 @@ export class Niivue {
4125
4184
  } // something is horribly wrong!
4126
4185
  const vx = this.back.dims[1] * this.back.dims[2] * this.back.dims[3]
4127
4186
  this.drawBitmap = new Uint8Array(vx)
4187
+ this.clickToSegmentGrowingBitmap = new Uint8Array(vx)
4128
4188
  this.drawClearAllUndoBitmaps()
4129
4189
  this.drawAddUndoBitmap()
4130
4190
  this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, this.back.dims, true)
@@ -4660,7 +4720,8 @@ export class Niivue {
4660
4720
  forceMax = NaN,
4661
4721
  neighbors = 6,
4662
4722
  // option for only flood filling within max distance from seed voxel
4663
- maxDistanceMM = Number.POSITIVE_INFINITY
4723
+ maxDistanceMM = Number.POSITIVE_INFINITY,
4724
+ is2D = false
4664
4725
  ): void {
4665
4726
  if (!this.drawBitmap) {
4666
4727
  throw new Error('drawBitmap undefined')
@@ -4682,10 +4743,28 @@ export class Niivue {
4682
4743
  const nx = dims[0]
4683
4744
  const nxy = nx * dims[1]
4684
4745
  const nxyz = nxy * dims[2]
4685
- const img = this.drawBitmap.slice()
4746
+ let img = this.drawBitmap.slice()
4747
+ let drawBitmap = this.drawBitmap
4748
+ // use the growing bitmap if the clickToSegmentIsGrowing flag is set.
4749
+ // this allows previewing the flood fill, and the results are copied
4750
+ // to the drawBitmap when the user releases the mouse button.
4751
+ if (this.clickToSegmentIsGrowing) {
4752
+ img = this.clickToSegmentGrowingBitmap.slice()
4753
+ drawBitmap = this.clickToSegmentGrowingBitmap
4754
+ }
4755
+
4686
4756
  if (img.length !== nxy * dims[2]) {
4687
4757
  return
4688
4758
  }
4759
+ // mask2D: axial slices constrained in Z, coronal in Y and sagittal in X
4760
+ let constrainXYZ = -1
4761
+ if (is2D && this.drawPenAxCorSag === SLICE_TYPE.AXIAL) {
4762
+ constrainXYZ = 2
4763
+ } else if (is2D && this.drawPenAxCorSag === SLICE_TYPE.CORONAL) {
4764
+ constrainXYZ = 1
4765
+ } else if (is2D && this.drawPenAxCorSag === SLICE_TYPE.SAGITTAL) {
4766
+ constrainXYZ = 0
4767
+ }
4689
4768
  function vx2xyz(vx: number): number[] {
4690
4769
  // provided address in 1D array, return XYZ coordinate
4691
4770
  const Z = Math.floor(vx / nxy) // slice
@@ -4705,7 +4784,11 @@ export class Niivue {
4705
4784
  const maxDistanceMM2 = maxDistanceMM ** 2
4706
4785
  // function to check if new point to be checked is less than maxDistanceMM
4707
4786
  function isWithinDistance(vx: number): boolean {
4708
- const xyzMM = vx2mm(vx2xyz(vx))
4787
+ const xzyVox = vx2xyz(vx)
4788
+ if (constrainXYZ >= 0 && xzyVox[constrainXYZ] !== seedXYZ[constrainXYZ]) {
4789
+ return false
4790
+ }
4791
+ const xyzMM = vx2mm(xzyVox)
4709
4792
  const dist2 = (xyzMM[0] - seedMM[0]) ** 2 + (xyzMM[1] - seedMM[1]) ** 2 + (xyzMM[2] - seedMM[2]) ** 2
4710
4793
  return dist2 <= maxDistanceMM2
4711
4794
  }
@@ -4721,7 +4804,7 @@ export class Niivue {
4721
4804
  }
4722
4805
  for (let i = 1; i < nxyz; i++) {
4723
4806
  img[i] = 0
4724
- if (this.drawBitmap[i] === seedColor) {
4807
+ if (drawBitmap[i] === seedColor) {
4725
4808
  // check if voxel index i is within maxDistanceMM from seed voxel
4726
4809
  if (!isWithinDistance(i)) {
4727
4810
  // move to next voxel if not within distance,
@@ -4775,11 +4858,22 @@ export class Niivue {
4775
4858
  for (let i = 1; i < nxyz; i++) {
4776
4859
  if (img[i] === 2) {
4777
4860
  // if part of cluster
4778
- this.drawBitmap[i] = newColor
4861
+ drawBitmap[i] = newColor
4779
4862
  }
4780
4863
  }
4781
- this.drawAddUndoBitmap()
4782
- this.refreshDrawing(false)
4864
+ if (!this.clickToSegmentIsGrowing) {
4865
+ this.drawBitmap = drawBitmap.slice()
4866
+ if (!this.opts.clickToSegment) {
4867
+ // only update the undo array if clickToSegment is not enabled.
4868
+ // This avoids adding the same drawing twice, since the mouse up
4869
+ // event will also add the drawing to the undo array.
4870
+ this.drawAddUndoBitmap()
4871
+ }
4872
+ this.refreshDrawing(true, this.clickToSegmentIsGrowing)
4873
+ } else {
4874
+ this.clickToSegmentGrowingBitmap = drawBitmap
4875
+ this.refreshDrawing(true, this.clickToSegmentIsGrowing)
4876
+ }
4783
4877
  }
4784
4878
 
4785
4879
  // not included in public docs
@@ -4966,6 +5060,7 @@ export class Niivue {
4966
5060
  this.drawClearAllUndoBitmaps()
4967
5061
  this.rgbaTex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true)
4968
5062
  this.drawBitmap = null
5063
+ this.clickToSegmentGrowingBitmap = null
4969
5064
  this.drawScene()
4970
5065
  }
4971
5066
 
@@ -4975,7 +5070,7 @@ export class Niivue {
4975
5070
  * @example niivue.refreshDrawing();
4976
5071
  * @see {@link https://niivue.github.io/niivue/features/cactus.html | live demo usage}
4977
5072
  */
4978
- refreshDrawing(isForceRedraw = true): void {
5073
+ refreshDrawing(isForceRedraw = true, useClickToSegmentBitmap = false): void {
4979
5074
  if (!this.back?.dims) {
4980
5075
  throw new Error('back.dims undefined')
4981
5076
  }
@@ -5005,7 +5100,7 @@ export class Niivue {
5005
5100
  dims[3],
5006
5101
  this.gl.RED,
5007
5102
  this.gl.UNSIGNED_BYTE,
5008
- this.drawBitmap
5103
+ useClickToSegmentBitmap ? this.clickToSegmentGrowingBitmap : this.drawBitmap
5009
5104
  )
5010
5105
  if (isForceRedraw) {
5011
5106
  this.drawScene()
@@ -5733,43 +5828,99 @@ export class Niivue {
5733
5828
  mask.fill(0)
5734
5829
  console.log('startVox', startVox)
5735
5830
  console.log('endVox', endVox)
5736
- // const startVox = roiInfo.startVox
5737
- // const endVox = roiInfo.endVox
5738
- const xrange = this.calculateMinMaxVoxIdx([startVox[0], endVox[0]])
5739
- const yrange = this.calculateMinMaxVoxIdx([startVox[1], endVox[1]])
5740
- const zrange = this.calculateMinMaxVoxIdx([startVox[2], endVox[2]])
5741
5831
 
5742
- // for our constant dimension we add one so that the for loop runs at least once
5743
- if (startVox[0] - endVox[0] === 0) {
5744
- xrange[1] = startVox[0] + 1
5745
- } else if (startVox[1] - endVox[1] === 0) {
5746
- yrange[1] = startVox[1] + 1
5747
- } else if (startVox[2] - endVox[2] === 0) {
5748
- zrange[1] = startVox[2] + 1
5832
+ // identify the constant dimension (the plane where the ellipse is drawn)
5833
+ let constantDim = -1
5834
+ if (startVox[0] === endVox[0]) {
5835
+ constantDim = 0 // x is constant
5836
+ } else if (startVox[1] === endVox[1]) {
5837
+ constantDim = 1 // y is constant
5838
+ } else if (startVox[2] === endVox[2]) {
5839
+ constantDim = 2 // z is constant
5840
+ } else {
5841
+ console.error('Error: No constant dimension found.')
5842
+ return
5749
5843
  }
5750
5844
 
5845
+ // get the varying dimensions
5846
+ const dims = [0, 1, 2]
5847
+ const varDims = dims.filter((dim) => dim !== constantDim)
5848
+
5849
+ // compute the center of the ellipse in voxel coordinates
5850
+ const centerVox = []
5851
+ centerVox[constantDim] = startVox[constantDim]
5852
+ centerVox[varDims[0]] = (startVox[varDims[0]] + endVox[varDims[0]]) / 2
5853
+ centerVox[varDims[1]] = (startVox[varDims[1]] + endVox[varDims[1]]) / 2
5854
+
5855
+ // compute the radii along each varying dimension
5856
+ const radiusX = Math.abs(endVox[varDims[0]] - startVox[varDims[0]]) / 2
5857
+ const radiusY = Math.abs(endVox[varDims[1]] - startVox[varDims[1]]) / 2
5858
+
5859
+ // dimensions of the image
5751
5860
  const xdim = hdr.dims[1]
5752
5861
  const ydim = hdr.dims[2]
5753
- for (let z = zrange[0]; z < zrange[1]; z++) {
5754
- const zi = z * xdim * ydim
5755
- for (let y = yrange[0]; y < yrange[1]; y++) {
5756
- const yi = y * xdim
5757
- for (let x = xrange[0]; x < xrange[1]; x++) {
5758
- const index = zi + yi + x
5862
+ // const zdim = hdr.dims[3]
5863
+
5864
+ // define the ranges for the varying dimensions
5865
+ const minVarDim0 = Math.max(0, Math.floor(centerVox[varDims[0]] - radiusX))
5866
+ const maxVarDim0 = Math.min(hdr.dims[varDims[0] + 1] - 1, Math.ceil(centerVox[varDims[0]] + radiusX))
5867
+
5868
+ const minVarDim1 = Math.max(0, Math.floor(centerVox[varDims[1]] - radiusY))
5869
+ const maxVarDim1 = Math.min(hdr.dims[varDims[1] + 1] - 1, Math.ceil(centerVox[varDims[1]] + radiusY))
5870
+
5871
+ // the constant dimension value
5872
+ const constDimVal = centerVox[constantDim]
5873
+ if (constDimVal < 0 || constDimVal >= hdr.dims[constantDim + 1]) {
5874
+ console.error('Error: Constant dimension value is out of bounds.')
5875
+ return
5876
+ }
5877
+
5878
+ // iterate over the varying dimensions and apply the elliptical mask
5879
+ for (let i = minVarDim0; i <= maxVarDim0; i++) {
5880
+ for (let j = minVarDim1; j <= maxVarDim1; j++) {
5881
+ // set the voxel coordinates
5882
+ const voxel = []
5883
+ voxel[constantDim] = constDimVal // Fixed dimension
5884
+ voxel[varDims[0]] = i
5885
+ voxel[varDims[1]] = j
5886
+ // calculate the normalized distances from the center
5887
+ const di = (voxel[varDims[0]] - centerVox[varDims[0]]) / radiusX
5888
+ const dj = (voxel[varDims[1]] - centerVox[varDims[1]]) / radiusY
5889
+ // calculate the squared distance in ellipse space
5890
+ const distSq = di * di + dj * dj
5891
+ // check if the voxel is within the ellipse
5892
+ if (distSq <= 1) {
5893
+ // calculate the index in the mask array
5894
+ const x = voxel[0]
5895
+ const y = voxel[1]
5896
+ const z = voxel[2]
5897
+ const index = z * xdim * ydim + y * xdim + x
5759
5898
  mask[index] = 1
5760
5899
  }
5761
5900
  }
5762
5901
  }
5763
- // calculate area 2d area from startVox and endVox coordinates if roiIsMask is true (since mask is created from a 2d slice only)
5764
- // area is in mm^2. The below calculations work regardless of the orientation of the image since
5765
- // Niivue converts all images to RAS
5766
- const xmm = Math.abs(pixDimsRAS[1] * (endVox[0] - startVox[0]))
5767
- const ymm = Math.abs(pixDimsRAS[2] * (endVox[1] - startVox[1]))
5768
- const zmm = Math.abs(pixDimsRAS[3] * (endVox[2] - startVox[2]))
5769
- // one of the above will be zero since roiIsMask==true, so we filter out the non-zero value
5770
- // and then multiply the other two to get the area
5771
- const mms = [xmm, ymm, zmm]
5772
- area = mms.filter((m) => m !== 0).reduce((a, b) => a * b, 1)
5902
+ // calculate the area based on the number of voxels in the mask
5903
+ const voxelArea = pixDimsRAS[varDims[0] + 1] * pixDimsRAS[varDims[1] + 1] // adjusted for 1-indexing
5904
+ const numMaskedVoxels = mask.reduce((count, value) => count + (value === 1 ? 1 : 0), 0)
5905
+ area = numMaskedVoxels * voxelArea
5906
+
5907
+ // perhaps better to calculate the area using the ellipse area formula
5908
+ const radiusX_mm = radiusX * pixDimsRAS[varDims[0] + 1]
5909
+ const radiusY_mm = radiusY * pixDimsRAS[varDims[1] + 1]
5910
+ const areaEllipse = Math.PI * radiusX_mm * radiusY_mm
5911
+ area = areaEllipse
5912
+ // for debuging: show mask -- loop over drawing and set drawing to 1 if mask is 1
5913
+ // this.setDrawingEnabled(true)
5914
+ // this.drawOpacity = 0.3
5915
+ // for (let i = 0; i < nv; i++) {
5916
+ // if (mask[i] === 1) {
5917
+ // this.drawBitmap![i] = 1
5918
+ // } else {
5919
+ // this.drawBitmap![i] = 0
5920
+ // }
5921
+ // }
5922
+ // this.refreshDrawing(false)
5923
+ // this.setDrawingEnabled(false)
5773
5924
  }
5774
5925
  // Welfords method
5775
5926
  // https://www.embeddedrelated.com/showarticle/785.php
@@ -7502,6 +7653,31 @@ export class Niivue {
7502
7653
  return pos[0] > 0 && pos[1] > 0 && pos[0] <= 1 && pos[1] <= 1
7503
7654
  }
7504
7655
 
7656
+ // update drawBitmap if it differs from clickTosegmentBitmap
7657
+ updateBitmapFromClickToSegment(): void {
7658
+ if (this.clickToSegmentGrowingBitmap === null) {
7659
+ return
7660
+ }
7661
+ if (this.drawBitmap === null) {
7662
+ return
7663
+ }
7664
+ if (this.clickToSegmentGrowingBitmap.length !== this.drawBitmap.length) {
7665
+ return
7666
+ }
7667
+ const nvx = this.drawBitmap.length
7668
+ for (let i = 0; i < nvx; i++) {
7669
+ this.drawBitmap[i] = this.clickToSegmentGrowingBitmap[i]
7670
+ }
7671
+ }
7672
+
7673
+ sumBitmap(img: Uint8Array): number {
7674
+ let sum = 0
7675
+ for (let i = 0; i < img.length; i++) {
7676
+ sum += img[i]
7677
+ }
7678
+ return sum
7679
+ }
7680
+
7505
7681
  // not included in public docs
7506
7682
  // handle mouse click event on canvas
7507
7683
  mouseClick(x: number, y: number, posChange = 0, isDelta = true): void {
@@ -7565,7 +7741,7 @@ export class Niivue {
7565
7741
  if (axCorSag > SLICE_TYPE.SAGITTAL) {
7566
7742
  continue
7567
7743
  }
7568
- const texFrac = this.screenXY2TextureFrac(x, y, i, false)
7744
+ let texFrac = this.screenXY2TextureFrac(x, y, i, false)
7569
7745
  if (texFrac[0] < 0) {
7570
7746
  continue
7571
7747
  } // click not on slice i
@@ -7596,21 +7772,37 @@ export class Niivue {
7596
7772
  }
7597
7773
  if (this.opts.drawingEnabled) {
7598
7774
  // drawing done in voxels
7599
- const pt = this.frac2vox(this.scene.crosshairPos) as [number, number, number]
7775
+ let pt = this.frac2vox(this.scene.crosshairPos) as [number, number, number]
7600
7776
  // if click-to-segment enabled
7601
7777
  if (this.opts.clickToSegment) {
7602
- // get voxel value of pt
7603
- const voxelIntensity = this.back.getValue(pt[0], pt[1], pt[2])
7604
- if (this.opts.clickToSegmentPercent > 0 && this.opts.clickToSegmentPercent <= 1) {
7605
- this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + this.opts.clickToSegmentPercent)
7606
- this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - this.opts.clickToSegmentPercent)
7778
+ texFrac = this.screenXY2TextureFrac(this.clickToSegmentXY[0], this.clickToSegmentXY[1], i, false)
7779
+ pt = this.frac2vox(texFrac) as [number, number, number]
7780
+ let diff = 0
7781
+ let threshold = this.opts.clickToSegmentPercent + diff
7782
+ if (this.uiData.mousedown) {
7783
+ // get percent difference between current x, y, and clickToSegmentXY
7784
+ const xDiff = (this.clickToSegmentXY[0] - x) / this.gl.canvas.width
7785
+ const yDiff = (this.clickToSegmentXY[1] - y) / this.gl.canvas.height
7786
+ diff = Math.max(Math.abs(xDiff), Math.abs(yDiff))
7787
+ // use the sign of the larger difference to determine the direction
7788
+ diff *= Math.sign(Math.abs(xDiff) > Math.abs(yDiff) ? xDiff : yDiff)
7789
+ threshold = this.opts.clickToSegmentPercent + diff
7607
7790
  }
7791
+ // get voxel value of pt
7792
+ let voxelIntensity = this.back.getValue(pt[0], pt[1], pt[2])
7608
7793
  // if clickToSegmentAutoIntensity, then calculate if we need to flood fill
7609
7794
  // in a bright or dark region based on the intensity of the clicked voxel,
7610
7795
  // and where it falls in the range of cal_min and cal_max.
7611
7796
  // !important! If this option is true, then it will ignore the boolean value of
7612
7797
  // clickToSegmentBright supplied by the user
7613
7798
  if (this.opts.clickToSegmentAutoIntensity) {
7799
+ if (threshold !== 0) {
7800
+ if (voxelIntensity === 0) {
7801
+ voxelIntensity = 0.01
7802
+ }
7803
+ this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + threshold)
7804
+ this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - threshold)
7805
+ }
7614
7806
  // if voxel intensity is greater than the midpoint of cal_min and cal_max,
7615
7807
  // then flood fill in a bright region
7616
7808
  if (voxelIntensity > (this.back.cal_min + this.back.cal_max) * 0.5) {
@@ -7641,9 +7833,16 @@ export class Niivue {
7641
7833
  }
7642
7834
  // draw the point at the cursor using the penSize to set the
7643
7835
  // seed for the flood fill
7644
- // !important! clickToSegment performs two drawing operations,
7645
- // therefore, if you want to undo the clickToSegment, you need to call undo twice
7646
7836
  this.drawPt(pt[0], pt[1], pt[2], this.opts.penValue)
7837
+
7838
+ if (diff !== 0) {
7839
+ this.clickToSegmentIsGrowing = true
7840
+ this.clickToSegmentGrowingBitmap = this.drawBitmap.slice()
7841
+ } else {
7842
+ this.clickToSegmentIsGrowing = false
7843
+ }
7844
+
7845
+ // do flood fill
7647
7846
  this.drawFloodFill(
7648
7847
  [pt[0], pt[1], pt[2]],
7649
7848
  0,
@@ -7651,11 +7850,18 @@ export class Niivue {
7651
7850
  this.opts.clickToSegmentIntensityMin,
7652
7851
  this.opts.clickToSegmentIntensityMax,
7653
7852
  this.opts.floodFillNeighbors,
7654
- this.opts.clickToSegmentMaxDistanceMM
7853
+ this.opts.clickToSegmentMaxDistanceMM,
7854
+ this.opts.clickToSegmentIs2D
7655
7855
  )
7856
+ // draw the scene so we see the changes
7656
7857
  this.drawScene()
7657
7858
  this.createOnLocationChange(axCorSag)
7658
- // get the volume of the segmented region
7859
+ if (this.clickToSegmentIsGrowing) {
7860
+ // don't get descriptive stats if the user
7861
+ // is still growing the segmented region
7862
+ return
7863
+ }
7864
+ // get the volume of the segmented region if the user is not growing the region
7659
7865
  const info = this.getDescriptives({
7660
7866
  layer: 0,
7661
7867
  masks: [],
@@ -7928,6 +8134,7 @@ export class Niivue {
7928
8134
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4)
7929
8135
  this.gl.bindVertexArray(this.unusedVAO) // switch off to avoid tampering with settings
7930
8136
  } else {
8137
+ this.drawCircle(leftTopWidthHeight, lineColor, 0.1)
7931
8138
  // this.opts.selectionBoxIsOutline == true
7932
8139
  this.rectOutlineShader.use(this.gl)
7933
8140
  this.gl.enable(this.gl.BLEND)
@@ -7976,6 +8183,11 @@ export class Niivue {
7976
8183
  // not included in public docs
7977
8184
  // draw a rectangle at desired location
7978
8185
  drawSelectionBox(leftTopWidthHeight: number[]): void {
8186
+ if (this.opts.dragMode === DRAG_MODE.roiSelection) {
8187
+ this.drawCircle(leftTopWidthHeight, this.opts.selectionBoxColor, 0.1)
8188
+ return
8189
+ }
8190
+ // else draw a rectangle
7979
8191
  this.drawRect(leftTopWidthHeight, this.opts.selectionBoxColor)
7980
8192
  }
7981
8193
 
package/src/niivue.css CHANGED
@@ -35,7 +35,6 @@ canvas:focus {
35
35
  }
36
36
  div {
37
37
  display: table-row;
38
- background-color: blue;
39
38
  }
40
39
  .dropdown {
41
40
  float: left;
package/src/nvdocument.ts CHANGED
@@ -150,9 +150,11 @@ export type NVConfigOptions = {
150
150
  clickToSegmentIntensityMin: number // also covers NaN
151
151
  clickToSegmentPercent: number
152
152
  clickToSegmentMaxDistanceMM: number // max distance in mm to consider for click to segment flood fill
153
+ clickToSegmentIs2D: boolean
153
154
  // selection box outline thickness
154
155
  selectionBoxLineThickness: number
155
156
  selectionBoxIsOutline: boolean
157
+ scrollRequiresFocus: boolean
156
158
  }
157
159
 
158
160
  export const DEFAULT_OPTIONS: NVConfigOptions = {
@@ -234,8 +236,10 @@ export const DEFAULT_OPTIONS: NVConfigOptions = {
234
236
  // for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values.
235
237
  clickToSegmentPercent: 0,
236
238
  clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY, // default value is infinity for backwards compatibility with flood fill routine.
239
+ clickToSegmentIs2D: false,
237
240
  selectionBoxLineThickness: 4,
238
- selectionBoxIsOutline: false
241
+ selectionBoxIsOutline: false,
242
+ scrollRequiresFocus: false // determines if the cavas need to be focused to scroll
239
243
  }
240
244
 
241
245
  type SceneData = {