Skip to content

Instantly share code, notes, and snippets.

@jdnarvaez
Last active October 10, 2023 19:21
Show Gist options
  • Save jdnarvaez/3368ac3e4f7f4eb14bc5f453f8a1ed1b to your computer and use it in GitHub Desktop.
Save jdnarvaez/3368ac3e4f7f4eb14bc5f453f8a1ed1b to your computer and use it in GitHub Desktop.
DICOM Overlays
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();
}
}
//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();
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