ivmartel/dwv

View on GitHub
src/dicom/dicomSRContent.js

Summary

Maintainability
D
1 day
Test Coverage
import {
  getNumericMeasurement,
  getDicomNumericMeasurementItem
} from './dicomNumericMeasurement';
import {
  getCode,
  getDicomCodeItem
} from './dicomCode';
import {
  getImageReference,
  getDicomImageReferenceItem
} from './dicomImageReference';
import {
  getSopInstanceReference,
  getDicomSopInstanceReferenceItem
} from './dicomSopInstanceReference';
import {
  getSpatialCoordinate,
  getDicomSpatialCoordinateItem
} from './dicomSpatialCoordinate';
import {
  getSpatialCoordinate3D,
  getDicomSpatialCoordinate3DItem
} from './dicomSpatialCoordinate3D';

// doc imports
/* eslint-disable no-unused-vars */
import {DataElement} from './dataElement';
import {DicomCode} from './dicomCode';
/* eslint-enable no-unused-vars */

/**
 * Related DICOM tag keys.
 */
const TagKeys = {
  ReferencedSOPSequence: '00081199',
  RelationshipType: '0040A010',
  ValueType: '0040A040',
  ConceptNameCodeSequence: '0040A043',
  ConceptCodeSequence: '0040A168',
  ContentSequence: '0040A730',
  DateTime: '0040A120',
  Date: '0040A121',
  Time: '0040A122',
  UID: '0040A124',
  PersonName: '0040A123',
  TextValue: '0040A160',
  ContinuityOfContent: '0040A050'
};

export const valueTypeValueTagName = {
  TEXT: 'TextValue',
  // NUM: '',
  // CODE: '',
  DATE: 'Date',
  TIME: 'Time',
  DATETIME: 'DateTime',
  UIDREF: 'UID',
  PNAME: 'PersonName',
  // COMPOSITE: '',
  // IMAGE: '',
  // WAVEFORM: '',
  // SCOORD: '',
  // SCOORD3D: '',
  // TCOORD: '',
  CONTAINER: 'ContinuityOfContent',
  // TABLE: ''
};

/**
 * DICOM SR content: item of a SR content sequence.
 *
 * Ref: {@link https://dicom.nema.org/medical/dicom/2022a/output/chtml/part03/sect_C.17.3.html}.
 */
export class DicomSRContent {
  /**
   * Value type.
   *
   * @type {string}
   */
  valueType;
  /**
   * Concept name code.
   *
   * @type {DicomCode|undefined}
   */
  conceptNameCode;
  /**
   * Relationship Type.
   *
   * @type {string}
   */
  relationshipType;

  /**
   * Content sequence (0040,A730).
   *
   * @type {DicomSRContent[]|undefined}
   */
  contentSequence;

  /**
   * Value.
   *
   * @type {object}
   */
  value;

  /**
   * @param {string} valueType The content item value type.
   */
  constructor(valueType) {
    this.valueType = valueType;
  }

  /**
   * Get a string representation of this object.
   *
   * @param {string} [prefix] An optional prefix for recursive content.
   * @returns {string} The object as string.
   */
  toString(prefix) {
    if (typeof prefix === 'undefined') {
      prefix = '';
    }

    let res = '';

    if (typeof this.relationshipType !== 'undefined') {
      res += '(' + this.relationshipType + ') ';
    }

    res += this.valueType + ': ';

    if (typeof this.conceptNameCode !== 'undefined') {
      res += this.conceptNameCode.toString();
    }

    res += ' = ' + this.value.toString();

    if (typeof this.contentSequence !== 'undefined') {
      for (const item of this.contentSequence) {
        res += '\n' + prefix + '- ' + item.toString(prefix + '  ');
      }
    }

    return res;
  }
}

/**
 * Check if two content item objects are equal.
 *
 * @param {DicomCode} item1 The first content item.
 * @param {DicomCode} item2 The second content item.
 * @returns {boolean} True if both content items are equal.
 */
export function isEqualContentItem(item1, item2) {
  return Object.keys(item1).length === Object.keys(item2).length &&
  Object.keys(item1).every(key =>
    Object.prototype.hasOwnProperty.call(item2, key) &&
    item1[key] === item2[key]
  );
}

/**
 * Get a content item object from a dicom element.
 *
 * @param {Object<string, DataElement>} dataElements The dicom element.
 * @returns {DicomSRContent} A content item object.
 */
export function getSRContent(dataElements) {
  // valueType -> ValueType (type1)
  let valueType = '';
  if (typeof dataElements[TagKeys.ValueType] !== 'undefined') {
    valueType = dataElements[TagKeys.ValueType].value[0];
  }

  const content = new DicomSRContent(valueType);

  // relationshipType -> RelationType (type1)
  if (typeof dataElements[TagKeys.RelationshipType] !== 'undefined') {
    content.relationshipType =
      dataElements[TagKeys.RelationshipType].value[0];
  }

  if (typeof dataElements[TagKeys.ConceptNameCodeSequence] !== 'undefined') {
    content.conceptNameCode =
      getCode(dataElements[TagKeys.ConceptNameCodeSequence].value[0]);
  }

  // set value acording to valueType
  // (date and time are stored as string)
  if (valueType === 'CODE') {
    content.value = getCode(
      dataElements[TagKeys.ConceptCodeSequence].value[0]);
  } else if (valueType === 'NUM') {
    content.value = getNumericMeasurement(dataElements);
  } else if (valueType === 'IMAGE') {
    content.value = getImageReference(dataElements);
  } else if (valueType === 'COMPOSITE') {
    content.value = getSopInstanceReference(
      dataElements[TagKeys.ReferencedSOPSequence].value[0]
    );
  } else if (valueType === 'SCOORD') {
    content.value = getSpatialCoordinate(dataElements);
  } else if (valueType === 'SCOORD3D') {
    content.value = getSpatialCoordinate3D(dataElements);
  } else {
    const valueTagName = valueTypeValueTagName[valueType];
    if (typeof valueTagName !== 'undefined') {
      content.value = dataElements[TagKeys[valueTagName]].value[0];
    } else {
      console.warn('Unsupported input ValueType: ' + valueType);
    }
  }

  const contentSqEl = dataElements[TagKeys.ContentSequence];
  if (typeof contentSqEl !== 'undefined') {
    content.contentSequence = [];
    for (const item of dataElements[TagKeys.ContentSequence].value) {
      content.contentSequence.push(getSRContent(item));
    }
  }

  if (typeof content.value === 'undefined') {
    console.log('valueType', valueType);
  }

  return content;
}

/**
 * Get a simple dicom element item from a content item object.
 *
 * @param {DicomSRContent} content The content item object.
 * @returns {Object<string, any>} The item as a list of (key, value) pairs.
 */
export function getDicomSRContentItem(content) {
  // dicom item (tags are in ~group/element order)
  let contentItem = {};

  if (typeof content.relationshipType !== 'undefined') {
    contentItem.RelationshipType = content.relationshipType;
  }
  if (typeof content.valueType !== 'undefined') {
    contentItem.ValueType = content.valueType;
  }
  if (typeof content.conceptNameCode !== 'undefined') {
    contentItem.ConceptNameCodeSequence = {
      value: [getDicomCodeItem(content.conceptNameCode)]
    };
  }

  // set appropriate value tag (data and time are stored as string)
  if (content.valueType === 'CODE') {
    contentItem.ConceptCodeSequence = {
      value: [getDicomCodeItem(content.value)]
    };
  } else if (content.valueType === 'NUM') {
    contentItem = {
      ...contentItem,
      ...getDicomNumericMeasurementItem(content.value)
    };
  } else if (content.valueType === 'IMAGE') {
    contentItem = {
      ...contentItem,
      ...getDicomImageReferenceItem(content.value)
    };
  } else if (content.valueType === 'COMPOSITE') {
    contentItem = {
      ...contentItem,
      ...getDicomSopInstanceReferenceItem(content.value)
    };
  } else if (content.valueType === 'SCOORD') {
    contentItem = {
      ...contentItem,
      ...getDicomSpatialCoordinateItem(content.value)
    };
  } else if (content.valueType === 'SCOORD3D') {
    contentItem = {
      ...contentItem,
      ...getDicomSpatialCoordinate3DItem(content.value)
    };
  } else {
    const valueTagName = valueTypeValueTagName[content.valueType];
    if (typeof valueTagName !== 'undefined') {
      contentItem[valueTagName] = content.value;
    } else {
      console.warn('Unsupported output ValueType: ' + content.valueType);
    }
  }

  if (typeof content.contentSequence !== 'undefined') {
    contentItem.ContentSequence = {
      value: []
    };
    for (const item of content.contentSequence) {
      contentItem.ContentSequence.value.push(getDicomSRContentItem(item));
    }
  }

  return contentItem;
}