Last active
October 10, 2023 19:21
-
-
Save jdnarvaez/3368ac3e4f7f4eb14bc5f453f8a1ed1b to your computer and use it in GitHub Desktop.
DICOM Overlays
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const overlayGroupTags = []; | |
for (var i = 0; i <= (0x00ee0000); i += (0x00020000)) { | |
overlayGroupTags.push((0x60000000 + i)); | |
} | |
imageRendered(e) { | |
const eventData = e.detail; | |
if (eventData && eventData.image && eventData.image.$presentationStateDataSet) { | |
const context = eventData.canvasContext.canvas.getContext('2d'); | |
context.save(); | |
const imageWidth = Math.abs(eventData.viewport.displayedArea.brhc.x - eventData.viewport.displayedArea.tlhc.x) * eventData.viewport.displayedArea.columnPixelSpacing; | |
const imageHeight = Math.abs(eventData.viewport.displayedArea.brhc.y - eventData.viewport.displayedArea.tlhc.y) * eventData.viewport.displayedArea.rowPixelSpacing; | |
const presentationState = eventData.image.$presentationStateDataSet; | |
if (!presentationState) { | |
return; | |
} | |
_.each(overlayGroupTags, function(overlayGroupNumber) { | |
const activationLayer = Overlays.getOverlayActivationLayer(presentationState, overlayGroupNumber) || Overlays.getOverlayActivationLayer(eventData.image.dataSet, overlayGroupNumber); | |
if (!activationLayer) { | |
return; | |
} | |
const overlay = Overlays.extractOverlay(presentationState, overlayGroupNumber) || Overlays.extractOverlay(eventData.image.dataSet, overlayGroupNumber, eventData.image); | |
if (overlay) { | |
const layerCanvas = document.createElement('canvas'); | |
layerCanvas.width = imageWidth; | |
layerCanvas.height = imageHeight; | |
const layerContext = layerCanvas.getContext('2d'); | |
const transform = cornerstone.internal.getTransform(eventData.enabledElement); | |
layerContext.setTransform(transform.m[0], transform.m[1], transform.m[2], transform.m[3], transform.m[4], transform.m[5]); | |
layerContext.save(); | |
layerContext.setTransform(1, 0, 0, 1, 0, 0); | |
layerContext.fillStyle = Theme.getProperty(Theme.VIEWPORT_TEXT); | |
if (overlay) { | |
if (overlay.type === 'R') { | |
layerContext.fillRect(0, 0, layerCanvas.width, layerCanvas.height); | |
layerContext.globalCompositeOperation = 'xor'; | |
} | |
let i = 0; | |
for (var y = 0; y < overlay.height; y++) { | |
for (var x = 0; x < overlay.width; x++) { | |
const pixel = overlay.data[i++]; | |
if (pixel > 0) { | |
layerContext.fillRect(x, y, 1, 1); | |
} | |
} | |
} | |
} | |
layerContext.restore(); | |
context.drawImage(layerCanvas, 0, 0); | |
} | |
}); | |
context.restore(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//import Tag from './Tag'; DICOM Tag dictionary | |
class Overlays { | |
constructor() { | |
const overlayGroupTags = []; | |
for (var i = 0; i <= (0x00ee0000); i += (0x00020000)) { | |
overlayGroupTags.push((0x60000000 + i)); | |
} | |
this.OVERLAY_GROUP_TAGS = overlayGroupTags; | |
//AutoBind(this); This is a utility for injecting the 'this' keyword into every method | |
} | |
getOverlayWidth(dataSet, overlayNumber) { | |
return dataSet.uint16(Tag.toHexString(Tag.OverlayColumns | (overlayNumber &= 0x60FF0000))); | |
} | |
getOverlayHeight(dataSet, overlayNumber) { | |
return dataSet.uint16(Tag.toHexString(Tag.OverlayRows | (overlayNumber &= 0x60FF0000))); | |
} | |
getOverlayActivationLayer(dataSet, overlayNumber) { | |
return dataSet.string(Tag.toHexString(Tag.OverlayActivationLayer | (overlayNumber &= 0x60FF0000))); | |
} | |
isOverlay(imageIndex) { | |
return ((imageIndex & 0x60000000) === 0x60000000) && (imageIndex & 0x9F010000) === 0; | |
} | |
extractFrameNumber(imageIndex) { | |
const {isOverlay} = this; | |
if (isOverlay(imageIndex)) { | |
return imageIndex & 0xFFFF; | |
} | |
return 0; | |
} | |
extractOverlay(dataSet, overlayNumber, image) { | |
const {isOverlay, extractFrameNumber, getOverlayHeight, getOverlayWidth, getOverlayActivationLayer} = this; | |
if (!isOverlay(overlayNumber)) { | |
return undefined; | |
} | |
const frameNumber = extractFrameNumber(overlayNumber); | |
overlayNumber = overlayNumber & 0x60FE0000; | |
const rows = getOverlayHeight(dataSet, overlayNumber); | |
const columns = getOverlayWidth(dataSet, overlayNumber); | |
const bitPosition = dataSet.uint16(Tag.toHexString(overlayNumber | Tag.OverlayBitPosition)); | |
let data; | |
if (bitPosition === 0) { | |
const overlayData = dataSet.elements[Tag.toHexString(overlayNumber | Tag.OverlayData)]; | |
if (overlayData) { | |
const length = rows * columns * 8; | |
data = []; | |
for (var i = 0; i < overlayData.length; i++) { | |
for (var k = 0; k < 8; k++) { | |
const byte_as_int = dataSet.byteArray[overlayData.dataOffset + i]; | |
data[i * 8 + k] = (byte_as_int >> k) & 0b1; | |
} | |
} | |
} | |
} else if (image) { | |
const bitsAllocated = dataSet.int16(Tag.toHexString(overlayNumber | Tag.OverlayBitsAllocated)); | |
const pixelData = image.getPixelData(); | |
data = []; | |
const bit = (1 << bitPosition); | |
var j = 0; | |
for (var y = 0; y < rows; y++) { | |
for (var x = 0; x < columns; x++) { | |
const pixel = pixelData[j]; | |
if ((pixel & bit) != 0) { | |
data[j] = 1; | |
} else { | |
data[j] = 0; | |
} | |
j++; | |
} | |
} | |
} | |
if (data) { | |
return { | |
x : dataSet.int16(Tag.toHexString(overlayNumber | Tag.OverlayOrigin), 1) - 1, | |
y : dataSet.int16(Tag.toHexString(overlayNumber | Tag.OverlayOrigin), 0) - 1, | |
width : columns, | |
height : rows, | |
data : data, | |
type : dataSet.string(Tag.toHexString(overlayNumber | Tag.OverlayType)), | |
layer : getOverlayActivationLayer(dataSet, overlayNumber) | |
}; | |
} | |
} | |
} | |
export default new Overlays(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
class Tag { | |
constructor() { | |
/** (0020,0052) VR=UI, VM=1 Frame of Reference UID */ | |
this.FrameOfReferenceUID = 0x00200052; | |
/** (0028,0004) VR=CS, VM=1 Photometric Interpretation */ | |
this.PhotometricInterpretation = 0x00280004; | |
/** (60xx,0010) VR=US, VM=1 Overlay Rows */ | |
this.OverlayRows = 0x60000010; | |
/** (60xx,0011) VR=US, VM=1 Overlay Columns */ | |
this.OverlayColumns = 0x60000011; | |
/** (60xx,0102) VR=US, VM=1 Overlay Bit Position */ | |
this.OverlayBitPosition = 0x60000102; | |
/** (60xx,3000) VR=OB|OW, VM=1 Overlay Data */ | |
this.OverlayData = 0x60003000; | |
/** (60xx,0050) VR=SS, VM=2 Overlay Origin */ | |
this.OverlayOrigin = 0x60000050; | |
/** (60xx,0040) VR=CS, VM=1 Overlay Type */ | |
this.OverlayType = 0x60000040; | |
/** (60xx,0045) VR=LO, VM=1 Overlay Subtype */ | |
this.OverlaySubtype = 0x60000045; | |
/** (60xx,0100) VR=US, VM=1 Overlay Bits Allocated */ | |
this.OverlayBitsAllocated = 0x60000100; | |
/** (60xx,1001) VR=CS, VM=1 Overlay Activation Layer */ | |
this.OverlayActivationLayer = 0x60001001; | |
/** (0020,0013) VR=IS, VM=1 Instance Number */ | |
this.InstanceNumber = 0x00200013; | |
/** (0020,0012) VR=IS, VM=1 Acquisition Number */ | |
this.AcquisitionNumber = 0x00200012; | |
/** (0008,0060) VR=CS, VM=1 Modality */ | |
this.Modality = 0x00080060; | |
/** (0020,000D) VR=UI, VM=1 Study Instance UID */ | |
this.StudyInstanceUID = 0x0020000D; | |
/** (0020,000E) VR=UI, VM=1 Series Instance UID */ | |
this.SeriesInstanceUID = 0x0020000E; | |
/** (0020,0011) VR=IS, VM=1 Series Number */ | |
this.SeriesNumber = 0x00200011; | |
/** (0008,0021) VR=DA, VM=1 Series Date */ | |
this.SeriesDate = 0x00080021; | |
/** (0008,0031) VR=TM, VM=1 Series Time */ | |
this.SeriesTime = 0x00080031; | |
/** (0020,0060) VR=CS, VM=1 Laterality */ | |
this.Laterality = 0x00200060; | |
/** (0008,0016) VR=UI, VM=1 SOP Class UID */ | |
this.SOPClassUID = 0x00080016; | |
/** (0008,0018) VR=UI, VM=1 SOP Instance UID */ | |
this.SOPInstanceUID = 0x00080018; | |
/** (0010,1010) VR=AS, VM=1 Patient's Age */ | |
this.PatientAge = 0x00101010; | |
/** (0010,1020) VR=DS, VM=1 Patient's Size */ | |
this.PatientSize = 0x00101020; | |
/** (0010,1030) VR=DS, VM=1 Patient's Weight */ | |
this.PatientWeight = 0x00101030; | |
/** (0018,1072) VR=TM, VM=1 Radiopharmaceutical Start Time */ | |
this.RadiopharmaceuticalStartTime = 0x00181072; | |
/** (0018,1074) VR=DS, VM=1 Radionuclide Total Dose */ | |
this.RadionuclideTotalDose = 0x00181074; | |
/** (0018,1075) VR=DS, VM=1 Radionuclide Half Life */ | |
this.RadionuclideHalfLife = 0x00181075; | |
/** (0020,0030) VR=DS, VM=3 Image Position RET */ | |
this.ImagePosition = 0x00200030; | |
/** (0020,0032) VR=DS, VM=3 Image Position (Patient) */ | |
this.ImagePositionPatient = 0x00200032; | |
/** (0020,0035) VR=DS, VM=6 Image Orientation RET */ | |
this.ImageOrientation = 0x00200035; | |
/** (0020,0037) VR=DS, VM=6 Image Orientation (Patient) */ | |
this.ImageOrientationPatient = 0x00200037; | |
/** (0028,0030) VR=DS, VM=2 Pixel Spacing */ | |
this.PixelSpacing = 0x00280030; | |
/** (0028,0010) VR=US, VM=1 Rows */ | |
this.Rows = 0x00280010; | |
/** (0028,0011) VR=US, VM=1 Columns */ | |
this.Columns = 0x00280011; | |
/** (0028,0012) VR=US, VM=1 Planes RET */ | |
this.Planes = 0x00280012; | |
/** (0018,0050) VR=DS, VM=1 Slice Thickness */ | |
this.SliceThickness = 0x00180050; | |
/** (0020,1041) VR=DS, VM=1 Slice Location */ | |
this.SliceLocation = 0x00201041; | |
/** (0028,0002) VR=US, VM=1 Samples per Pixel */ | |
this.SamplesPerPixel = 0x00280002; | |
/** (0028,0100) VR=US, VM=1 Bits Allocated */ | |
this.BitsAllocated = 0x00280100; | |
/** (0028,0101) VR=US, VM=1 Bits Stored */ | |
this.BitsStored = 0x00280101; | |
/** (0028,0102) VR=US, VM=1 High Bit */ | |
this.HighBit = 0x00280102; | |
/** (0028,0006) VR=US, VM=1 Planar Configuration */ | |
this.PlanarConfiguration = 0x00280006; | |
/** (0028,0034) VR=IS, VM=2 Pixel Aspect Ratio */ | |
this.PixelAspectRatio = 0x00280034; | |
/** (0028,0103) VR=US, VM=1 Pixel Representation */ | |
this.PixelRepresentation = 0x00280103; | |
/** (0028,0120) VR=US|SS, VM=1 Pixel Padding Value */ | |
this.PixelPaddingValue = 0x00280120; | |
/** (0028,0121) VR=US|SS, VM=1 Pixel Padding Range Limit */ | |
this.PixelPaddingRangeLimit = 0x00280121; | |
/** (0028,0106) VR=US|SS, VM=1 Smallest Image Pixel Value */ | |
this.SmallestImagePixelValue = 0x00280106; | |
/** (0028,0107) VR=US|SS, VM=1 Largest Image Pixel Value */ | |
this.LargestImagePixelValue = 0x00280107; | |
/** (0028,1100) VR=US|SS, VM=3 Gray Lookup Table Descriptor RET */ | |
this.GrayLookupTableDescriptor = 0x00281100; | |
/** (0028,1101) VR=US|SS, VM=3 Red Palette Color Lookup Table Descriptor */ | |
this.RedPaletteColorLookupTableDescriptor = 0x00281101; | |
/** (0028,1102) VR=US|SS, VM=3 Green Palette Color Lookup Table Descriptor */ | |
this.GreenPaletteColorLookupTableDescriptor = 0x00281102; | |
/** (0028,1103) VR=US|SS, VM=3 Blue Palette Color Lookup Table Descriptor */ | |
this.BluePaletteColorLookupTableDescriptor = 0x00281103; | |
/** (0028,1200) VR=US|SS|OW, VM=1-n1 Gray Lookup Table Data RET */ | |
this.GrayLookupTableData = 0x00281200; | |
/** (0028,1201) VR=OW, VM=1 Red Palette Color Lookup Table Data */ | |
this.RedPaletteColorLookupTableData = 0x00281201; | |
/** (0028,1202) VR=OW, VM=1 Green Palette Color Lookup Table Data */ | |
this.GreenPaletteColorLookupTableData = 0x00281202; | |
/** (0028,1203) VR=OW, VM=1 Blue Palette Color Lookup Table Data */ | |
this.BluePaletteColorLookupTableData = 0x00281203; | |
/** (0028,1050) VR=DS, VM=1-n Window Center */ | |
this.WindowCenter = 0x00281050; | |
/** (0028,1051) VR=DS, VM=1-n Window Width */ | |
this.WindowWidth = 0x00281051; | |
/** (0028,1052) VR=DS, VM=1 Rescale Intercept */ | |
this.RescaleIntercept = 0x00281052; | |
/** (0028,1053) VR=DS, VM=1 Rescale Slope */ | |
this.RescaleSlope = 0x00281053; | |
/** (0028,1054) VR=LO, VM=1 Rescale Type */ | |
this.RescaleType = 0x00281054; | |
/** (0028,3000) VR=SQ, VM=1 Modality LUT Sequence */ | |
this.ModalityLUTSequence = 0x00283000 | |
/** (0028,3010) VR=SQ, VM=1 VOI LUT Sequence */ | |
this.VOILUTSequence = 0x00283010; | |
/** (0028,3110) VR=SQ, VM=1 Softcopy VOI LUT Sequence */ | |
this.SoftcopyVOILUTSequence = 0x00283110; | |
/** (2050,0020) VR=CS, VM=1 Presentation LUT Shape */ | |
this.PresentationLUTShape = 0x20500020; | |
/** (2050,0010) VR=SQ, VM=1 Presentation LUT Sequence */ | |
this.PresentationLUTSequence = 0x20500010; | |
/** (0008,1140) VR=SQ, VM=1 Referenced Image Sequence */ | |
this.ReferencedImageSequence = 0x00081140; | |
/** (0028,1056) VR=CS, VM=1 VOI LUT Function */ | |
this.VOILUTFunction = 0x00281056; | |
/** (0018,9404) VR=FL, VM=2 Object Pixel Spacing in Center of Beam */ | |
this.ObjectPixelSpacingInCenterOfBeam = 0x00189404; | |
// ??? | |
// /** (0028,0A02) VR=CS, VM=1 Pixel Spacing Calibration Type */ | |
// this.PixelSpacingCalibrationType = 0x00280A02; | |
/** (0028,0A02) VR=CS, VM=1 Pixel Spacing Calibration Type */ | |
this.PixelSpacingCalibrationType = 0x00280402; | |
/** (0018,1164) VR=DS, VM=2 Imager Pixel Spacing */ | |
this.ImagerPixelSpacing = 0x00181164; | |
/** (0018,1114) VR=DS, VM=1 Estimated Radiographic Magnification Factor */ | |
this.EstimatedRadiographicMagnificationFactor = 0x00181114; | |
/** (0018,2010) VR=DS, VM=2 Nominal Scanned Pixel Spacing */ | |
this.NominalScannedPixelSpacing = 0x00182010; | |
/** (0018,6011) VR=SQ, VM=1 Sequence of Ultrasound Regions */ | |
this.SequenceOfUltrasoundRegions = 0x00186011; | |
/** (0018,6012) VR=US, VM=1 Region Spatial Format */ | |
this.RegionSpatialFormat = 0x00186012; | |
/** (0018,6014) VR=US, VM=1 Region Data Type */ | |
this.RegionDataType = 0x00186014; | |
/** (0018,6016) VR=UL, VM=1 Region Flags */ | |
this.RegionFlags = 0x00186016; | |
/** (0018,6018) VR=UL, VM=1 Region Location Min X0 */ | |
this.RegionLocationMinX0 = 0x00186018; | |
/** (0018,601A) VR=UL, VM=1 Region Location Min Y0 */ | |
this.RegionLocationMinY0 = 0x0018601A; | |
/** (0018,601C) VR=UL, VM=1 Region Location Max X1 */ | |
this.RegionLocationMaxX1 = 0x0018601C; | |
/** (0018,601E) VR=UL, VM=1 Region Location Max Y1 */ | |
this.RegionLocationMaxY1 = 0x0018601E; | |
/** (0018,6020) VR=SL, VM=1 Reference Pixel X0 */ | |
this.ReferencePixelX0 = 0x00186020; | |
/** (0018,6022) VR=SL, VM=1 Reference Pixel Y0 */ | |
this.ReferencePixelY0 = 0x00186022; | |
/** (0018,6024) VR=US, VM=1 Physical Units X Direction */ | |
this.PhysicalUnitsXDirection = 0x00186024; | |
/** (0018,6026) VR=US, VM=1 Physical Units Y Direction */ | |
this.PhysicalUnitsYDirection = 0x00186026; | |
/** (0018,6028) VR=FD, VM=1 Reference Pixel Physical Value X */ | |
this.ReferencePixelPhysicalValueX = 0x00186028; | |
/** (0018,602A) VR=FD, VM=1 Reference Pixel Physical Value Y */ | |
this.ReferencePixelPhysicalValueY = 0x0018602A; | |
/** (0018,602C) VR=FD, VM=1 Physical Delta X */ | |
this.PhysicalDeltaX = 0x0018602C; | |
/** (0018,602E) VR=FD, VM=1 Physical Delta Y */ | |
this.PhysicalDeltaY = 0x0018602E; | |
/** (0018,6030) VR=UL, VM=1 Transducer Frequency */ | |
this.TransducerFrequency = 0x00186030; | |
/** (0018,6031) VR=CS, VM=1 Transducer Type */ | |
this.TransducerType = 0x00186031; | |
/** (0018,6032) VR=UL, VM=1 Pulse Repetition Frequency */ | |
this.PulseRepetitionFrequency = 0x00186032; | |
/** (0018,6034) VR=FD, VM=1 Doppler Correction Angle */ | |
this.DopplerCorrectionAngle = 0x00186034; | |
/** (0018,6036) VR=FD, VM=1 Steering Angle */ | |
this.SteeringAngle = 0x00186036; | |
/** (0018,6039) VR=SL, VM=1 Doppler Sample Volume X Position */ | |
this.DopplerSampleVolumeXPosition = 0x00186039; | |
/** (0018,603B) VR=SL, VM=1 Doppler Sample Volume Y Position */ | |
this.DopplerSampleVolumeYPosition = 0x0018603B; | |
/** (0018,603D) VR=SL, VM=1 TM-Line Position X0 */ | |
this.TMLinePositionX0 = 0x0018603D; | |
/** (0018,603F) VR=SL, VM=1 TM-Line Position Y0 */ | |
this.TMLinePositionY0 = 0x0018603F; | |
/** (0018,6041) VR=SL, VM=1 TM-Line Position X1 */ | |
this.TMLinePositionX1 = 0x00186041; | |
/** (0018,6043) VR=SL, VM=1 TM-Line Position Y1 */ | |
this.TMLinePositionY1 = 0x00186043; | |
this.PatientOrientation = 0x00200020; | |
} | |
toHexString(value) { | |
if (!_.isNumber(value)) { | |
return undefined; | |
} | |
var hexString = value.toString(16).toLowerCase(); | |
while(hexString.length < 8) { | |
hexString = ('0').concat(hexString); | |
} | |
return ('x').concat(hexString); | |
} | |
hexStringToTagName(hexString) { | |
return _.find(_.keys(instance), function(tagName) { | |
if (toHexString(instance[tagName]) === hexString) { | |
return tagName; | |
} | |
}); | |
} | |
} | |
export default new Tag(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment