Cardiac Agatston Scoring module

I require help regarding use of 3d slicer module “CardiacAgatstonScoring”.This module isnt showing in any version except 4.4 but in 4.4 when i click Apply(for calculating calcium score)it doesnt work

Operating system:
Slicer version:
Expected behavior:
Actual behavior:

That module appears to be available in the Extension Manager in both Slicer 4.8 on Windows and a recent nightly that I had open on Mac. So please try either Slicer 4.8 or the most recent nightly.

  • Before using on your own data, the first step is to try the test data and tutorial here:

    2014 Summer Prioject Week:AgatstonScoring - NAMIC Wiki

  • Someone might suggest a quick fix if you post the Error Log – click the gray (or probably red) circle with X at the bottom-right part of the window:

    image

  • you could also try filing an issue with the information above (error log, etc.), however, this extension has not been updated for several years and may no longer be maintained.

I had a look at the module and it is quite outdated and had some errors when I tried to launch its thresholding function. It would be nice if somebody could fix it up, switch to use Segment Editor, etc.

However, you can compute Agatston score by a few simple steps, without the CardiacAgatstonScoring module:

  • Load your CT
  • Switch to Segment Editor module
  • Add a new segment
  • Use Threshold effect to segment calcifications. Set lower threshold value to 130 (for 120 keV images) or 167 (for 80 keV images). Click Apply.
  • Add a segment for each vessel segment (LM, LAD, LCX, RCA)
  • To assign each disconnected segmented structure to one of the vessel segments. Select Select Islands effect and choose Add selected island option. Then, to assign an island to a vessel segment: select the corresponding segment in the list (for example “RCA”) and click the island in the slice viewer that you would like to add to that segment.
  • Use Segment Statistics module to compute volume, mean an max intensity, etc. for each segment.
  • Use File / Save to save statistics result to .csv file that you can load into Excel for computing the Agatston score.
2 Likes

Thanks for the Response!!
I have to calculate Total Agatston score of RCA only.After performing the
steps you have mentioned above I have Statistics such as volume,total voxel
number, mean an max intensity(of whole RCA not of single slice).
Do I just have to multily the max intensity number with Area to
calculate Agatston
score?or will calculate (slice by slice) agatston score and add up at end?

There are several variants of the metric. If you need to compute the metric slice by slice then you can use Mask volume effect (available in Segment Editor module after you install SegmentEditorExtraEffects extension) to create a volume where all voxels are blanked out except the calcifications in the selected vessel and compute the total score using this script:

Just open the Python console and copy-paste this script (change CTChest masked to the actual name of your masked volume).

Hi@Andras Lasso,
I am trying to calculate agatston score in a single vessel(RCA).Do I need to segment plaque slice by slice.And write the above script to determine agatston score of a single plaque lession(per slice).And at the end add agatston scores of all slices manually to calculate the total agatston score of a single vessel. ??

I’ve updated the post above with a link to the current version of the script.

You need to segment the plaque in that vessel, using any of the tools in Segment editor. Probably the best is to use Paint effect with a large brush, Sphere option enabled (so that you don’t have to paint on each and every slice), Masking / Editable intensity range option enabled (range set to 100-3000; so that only calcifications are highlighted). After segmentation is completed, export the segmentation to a labelmap (using Segmentations module Export/Import… section).

The script computes score for each slice and then reports the sum.

Thanks@ Andras Lasso,
I just have a query related to the updated script by yourself in the above discussion.
In this script highest /maximum intensity factor was set to 100(all pixels above 100 CT density/HU will be consider for calcium scoring.isnt it?).
But according to the literature and (by3D slicer tutorial regarding agatston scoring),it is 130 HU for 120 KvP and 167 HU for 167 kvP.Right?Do we need to correct that.
secondly,area in mm2 should be the area covered by 3 consecuive voxels .is this algo calculating area based on this principle?
Thanks,

Feel free to modify the threshold as you see fit. If you can give reference to a paper then I’ll update the example script accordingly.

What do you mean by “3 consecutive voxels”? Is there a minimum blob size within a slice? Or the same pixel position must be above the threshold on 3 consecutive slices?

Would you be interested in converting the example script to a Slicer module?

Thanks for your response!!
“If you can give reference to a paper then I’ll update the example script accordingly.”

sure.Here are the links of the papers.
Paper1
Paper2
According to these papers"To qualify as a calcified plaque using CAC scoring, the plaque calcium density, measured in Hounsfield units, must be 130 Hu or higher."

"What do you mean by “3 consecutive voxels”?

According to paper2 “Agatston score, requiring 3 contiguous voxels of >=130 Hounsfield units to be classified as calcified plaque”.Means…for example if we have one CT slice with a calcified spot occupying 4 consecutive pixels,if just 2 out of that 5 pixels are >=130 HU than we cant classify it as calcified plaque/agatston score.

“Would you be interested in converting the example script to a Slicer module?”
No.I just want to understand how the algorithm is working.And if I apply this algorithm to calculate calcium score of my dataset than is it calculating the score according to the principles/my conditions.So basically I want to modify it based on the informaion i have mentioned above(>=130HU and 3 consecutive pixels).

I’ve checked Paper1 referenced above. They wrote the requirement was contiguous voxels (consecutive would require sequence of voxels, which cannot be interpreted on an image slice), so this is clear now. My other concern was defining minimum size as number of voxels, because voxel size depends on the image acquisition protocol. However, they wrote that they used minimum surface area of 1mm^2 (which happened to be 3 voxels for their imaging protocol). We can use the 1mm^2 value.

I’ve updated the score computation to use minimum threshold value for island separation, discard small islands, and compute score per island - see latest revision of the script.

1 Like

Is there an extension that can be used directly to assess the coronary calcium score/agatsone score?

We have not put the Cardiac Agatston Scoring script into an extension yet, so you need to copy-paste the code into Slicer’s Python console. If you can confirm that the script is useful and works well then I can add it as an extension to allow installing and running the analysis by a few clicks.

Hi

I am trying to download this extension from Cardiac Agatston Measures by BRAINSia
I cannot seem to install it I keep getting error message.
Is this still available?

I am trying to get an agatston score for the abdominal aorta only.

Thanks

You don’t need to install the script, just copy-paste it into Slicer’s Python console.

Hi Andras,

Thanks for the response.

I have followed instructions as above–> Segment the plaque in aorta, using Paint effect and Masking. I then exported using the segmentation module to a label map.
I then used the script provided. I got a total score of 0 which based on the CT aorta is incorrect. It’s based on the example CTA abdo panoramic provided.

This is what I got:

def computeAgatstonScore(volumeNode, minimumIntensityThreshold=130, minimumIslandSizeInMm2=1.0, verbose=False):
  import numpy as np
  import math
  import SimpleITK as sitk
  voxelArray = slicer.util.arrayFromVolume(volumeNode)
  areaOfPixelMm2 = volumeNode.GetSpacing()[0] * volumeNode.GetSpacing()[1]
  minimumIslandSizeInPixels = int(round(minimumIslandSizeInMm2/areaOfPixelMm2))
  numberOfSlices = voxelArray.shape[0]
  totalScore = 0
  for sliceIndex in range(numberOfSlices):
    voxelArraySlice = voxelArray[sliceIndex]
    maxIntensity = voxelArraySlice.max()
    if maxIntensity < minimumIntensityThreshold:
      continue
    # Get a thresholded image to analyse islands (connected components)
    # If island size less than minimum size then it will be discarded.
    thresholdedVoxelArraySlice = voxelArraySlice>minimumIntensityThreshold
    sliceImage = sitk.GetImageFromArray(voxelArraySlice)
    thresholdedSliceImage = sitk.GetImageFromArray(thresholdedVoxelArraySlice.astype(int))
    labelImage = sitk.ConnectedComponentImageFilter().Execute(thresholdedSliceImage)
    stats = sitk.LabelStatisticsImageFilter()
    stats.Execute(sliceImage, labelImage)
    numberOfNonZeroVoxels = 0
    numberOfIslands = 0
    sliceScore = 0
    for labelIndex in stats.GetLabels():
      if labelIndex == 0:
        continue
      if stats.GetCount(labelIndex) < minimumIslandSizeInPixels:
        continue
      maxIntensity = stats.GetMaximum(labelIndex)
      weightFactor = math.floor(maxIntensity/100)
      if weightFactor > 4:
        weightFactor = 4.0
      numberOfNonZeroVoxelsInIsland = stats.GetCount(labelIndex)
      sliceScore += numberOfNonZeroVoxelsInIsland * areaOfPixelMm2 * weightFactor
      numberOfNonZeroVoxels += numberOfNonZeroVoxelsInIsland
      numberOfIslands += 1
    totalScore += sliceScore
    if (sliceScore > 0) and verbose:
      print("Slice {0} score: {1:.1f} (from {2} islands of size > {3}, {4} voxels)".format(
        sliceIndex, sliceScore, numberOfIslands, minimumIslandSizeInPixels,
        numberOfNonZeroVoxels))
      slicer.app.processEvents()
  return totalScore

print("Total Agatston score: {0}".format(computeAgatstonScore(getNode('Segmentation-Calcification-label'), verbose=True)))
Total Agatston score: 0

The input to the algorithm is a masked volume, not a labelmap. The reason is that the computation requires the original HU values. I’ve uploaded a sample masked data set here.

It would be great if you could test if the results look good to you. I’ve just implemented the algorithm based on the reference paper and have not compared the results to known implementations. I’ve updated the script with some more detailed logging of computation details so that you can verify the results more easily.

Hi

Thanks for the response. I have tried it on the sample data and it is giving a reasonable reading.

I have now tried it on my own scan using the updated script but it’s giving me errors. I think it’s probably the masking. I tried to mask in segment editor–>masking (I have attached a screenshot of the masking process and masked data set). is this the correct way to mask?

It seems it is just a typo in the node name (you typed an extra “S” at the beginning of the name).