Skip to content

Commit

Permalink
feat: Add TMTV calculation for segmentations
Browse files Browse the repository at this point in the history
  • Loading branch information
sedghi authored and swederik committed Mar 22, 2022
1 parent 88424eb commit 556bdcd
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 7 deletions.
20 changes: 13 additions & 7 deletions packages/cornerstone-tools/src/util/math/vec3/isEqual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ import { Point3 } from '../../../types'
* @returns {boolean} True if the two values are within the tolerance levels.
*/
export default function isEqual(
v1: Point3,
v2: Point3,
v1: Point3 | Float32Array,
v2: Point3 | Float32Array,
tolerance = 1e-5
): boolean {
return (
Math.abs(v1[0] - v2[0]) < tolerance &&
Math.abs(v1[1] - v2[1]) < tolerance &&
Math.abs(v1[2] - v2[2]) < tolerance
)
if (v1.length !== v2.length) {
return false
}

for (let i = 0; i < v1.length; i++) {
if (Math.abs(v1[i] - v2[i]) > tolerance) {
return false
}
}

return true
}
54 changes: 54 additions & 0 deletions packages/cornerstone-tools/src/util/segmentation/calculateTMTV.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { IImageVolume } from '@precisionmetrics/cornerstone-render/src/types'
import isEqual from '../math/vec3/isEqual'

/**
* Given a list of labelmaps (with the possitibility of overlapping regions),
* and a referenceVolume, it calculates the total metabolic turnover volume (TMTV)
* by flattening and rasterizing each segment into a single labelmap and summing
* the total number of volume voxels. It should be noted that for this calculation
* we do not double count voxels that are part of multiple labelmaps.
* @param {} labelmaps
* @param {number} segmentIndex
* @returns {number} TMTV
*/
function calculateTMTV(
labelmaps: Array<IImageVolume>,
segmentIndex = 1
): number {
labelmaps.forEach(({ direction, dimensions, origin }) => {
if (
!isEqual(dimensions, labelmaps[0].dimensions) ||
!isEqual(direction, labelmaps[0].direction) ||
!isEqual(origin, labelmaps[0].origin)
) {
throw new Error('labelmaps must have the same size and shape')
}
})

const labelmap = labelmaps[0]

const arrayType = labelmap.scalarData.constructor
const outputData = new arrayType(labelmap.scalarData.length)

labelmaps.forEach((labelmap) => {
const { scalarData } = labelmap
for (let i = 0; i < scalarData.length; i++) {
if (scalarData[i] === segmentIndex) {
outputData[i] = segmentIndex
}
}
})

// count non-zero values inside the outputData, this would
// consider the overlapping regions to be only counted once
const tmtv = outputData.reduce((acc, curr) => {
if (curr > 0) {
return acc + 1
}
return acc
}, 0)

return tmtv
}

export default calculateTMTV
3 changes: 3 additions & 0 deletions packages/cornerstone-tools/src/util/segmentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
import getBoundingBoxAroundShape from './getBoundingBoxAroundShape'
import thresholdVolumeByRange from './thresholdVolumeByRange'
import thresholdVolumeByRoiStats from './thresholdVolumeByRoiStats'
import calculateTMTV from './calculateTMTV'

export {
getBoundingBoxAroundShape,
// fillOutsideBoundingBox,
thresholdVolumeByRange,
thresholdVolumeByRoiStats,
calculateTMTV,
}

export default {
getBoundingBoxAroundShape,
// fillOutsideBoundingBox,
thresholdVolumeByRange,
thresholdVolumeByRoiStats,
calculateTMTV,
}
28 changes: 28 additions & 0 deletions packages/demo/src/ExampleSegmentationRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class SegmentationExample extends Component {
thresholdMax: 100,
numSlicesForThreshold: 1,
selectedStrategy: '',
tmtv: null,
}

constructor(props) {
Expand Down Expand Up @@ -536,6 +537,24 @@ class SegmentationExample extends Component {
this.setState({ activeSegmentIndex: newIndex, segmentLocked })
}

calculateTMTV = () => {
const sceneUID = this.state.sceneForSegmentation
const scene = this.renderingEngine.getScene(sceneUID)
const { element } = scene.getViewports()[0]
const labelmapUIDs = SegmentationModule.getLabelmapUIDsForElement(element)

const labelmaps = labelmapUIDs.map((uid) => cache.getVolume(uid))
const segmentationIndex = 1
const tmtv = csToolsUtils.segmentation.calculateTMTV(
labelmaps,
segmentationIndex
)
this.setState((prevState) => ({
...prevState,
tmtv,
}))
}

executeThresholding = (mode) => {
const ptVolume = cache.getVolume(ptVolumeUID)
const labelmapVolume = cache.getVolume(this.state.selectedLabelmapUID)
Expand Down Expand Up @@ -644,6 +663,15 @@ class SegmentationExample extends Component {
>
Execute Max Thresholding on Selected Annotation
</button>
<button
style={{ marginLeft: '5px' }}
onClick={() => this.calculateTMTV()}
>
Calculate TMTV
</button>
{this.state.tmtv !== null && (
<span>{` TMTV: ${this.state.tmtv} voxels`}</span>
)}
</>
)
}
Expand Down

0 comments on commit 556bdcd

Please sign in to comment.