src_dicom_dicomTag.js

import {
  dictionary,
  tagGroups
} from './dictionary';

/**
 * Immutable tag.
 */
export class Tag {

  /**
   * The tag group.
   *
   * @type {string}
   */
  #group;

  /**
   * The tag element.
   *
   * @type {string}
   */
  #element;

  /**
   * @param {string} group The tag group as '####'.
   * @param {string} element The tag element as '####'.
   */
  constructor(group, element) {
    if (!group || typeof group === 'undefined') {
      throw new Error('Cannot create tag with no group.');
    }
    if (group.length !== 4) {
      throw new Error('Cannot create tag with badly sized group: ' + group);
    }
    if (!element || typeof element === 'undefined') {
      throw new Error('Cannot create tag with no element.');
    }
    if (element.length !== 4) {
      throw new Error('Cannot create tag with badly sized element: ' + element);
    }
    this.#group = group;
    this.#element = element;
  }

  /**
   * Get the tag group.
   *
   * @returns {string} The tag group.
   */
  getGroup() {
    return this.#group;
  }

  /**
   * Get the tag element.
   *
   * @returns {string} The tag element.
   */
  getElement() {
    return this.#element;
  }

  /**
   * Get as string representation of the tag: 'key: name'.
   *
   * @returns {string} A string representing the tag.
   */
  toString() {
    return this.getKey() + ': ' + this.getNameFromDictionary();
  }

  /**
   * Check for Tag equality.
   *
   * @param {Tag} rhs The other tag to compare to.
   * @returns {boolean} True if both tags are equal.
   */
  equals(rhs) {
    return rhs !== null &&
      typeof rhs !== 'undefined' &&
      this.#group === rhs.getGroup() &&
      this.#element === rhs.getElement();
  }

  /**
   * Get the group-element key used to store DICOM elements.
   *
   * @returns {string} The key as '########'.
   */
  getKey() {
    return this.#group + this.#element;
  }

  /**
   * Get the group name as defined in TagGroups.
   *
   * @returns {string} The name.
   */
  getGroupName() {
    return tagGroups[this.#group];
  }

  /**
   * Does this tag have a VR.
   * Basically not the Item, ItemDelimitationItem nor
   *  SequenceDelimitationItem tags.
   *
   * @returns {boolean} True if this tag has a VR.
   */
  isWithVR() {
    return !(this.#group === 'FFFE' &&
      (this.#element === 'E000' ||
      this.#element === 'E00D' ||
      this.#element === 'E0DD')
    );
  }

  /**
   * Is the tag group a private tag group ?
   *
   * See: {@link http://dicom.nema.org/medical/dicom/2022a/output/html/part05.html#sect_7.8}.
   *
   * @returns {boolean} True if the tag group is private,
   *   ie if its group is an odd number.
   */
  isPrivate() {
    return parseInt(this.#group, 16) % 2 === 1;
  }

  /**
   * Get the tag info from the dicom dictionary.
   *
   * @returns {string[]|undefined} The info as [vr, multiplicity, name].
   */
  #getInfoFromDictionary() {
    let info;
    if (typeof dictionary[this.#group] !== 'undefined' &&
      typeof dictionary[this.#group][this.#element] !==
        'undefined') {
      info = dictionary[this.#group][this.#element];
    }
    return info;
  }

  /**
   * Get the tag Value Representation (VR) from the dicom dictionary.
   *
   * @returns {string|undefined} The VR.
   */
  getVrFromDictionary() {
    let vr;
    const info = this.#getInfoFromDictionary();
    if (typeof info !== 'undefined') {
      vr = info[0];
    }
    return vr;
  }

  /**
   * Get the tag name from the dicom dictionary.
   *
   * @returns {string|undefined} The VR.
   */
  getNameFromDictionary() {
    let name;
    const info = this.#getInfoFromDictionary();
    if (typeof info !== 'undefined') {
      name = info[2];
    }
    return name;
  }

} // Tag class

/**
 * Tag compare function.
 *
 * @param {Tag} a The first tag.
 * @param {Tag} b The second tag.
 * @returns {number} The result of the tag comparison,
 *   positive for b before a, negative for a before b and
 *   zero to keep same order.
 */
export function tagCompareFunction(a, b) {
  // first by group
  let res = parseInt(a.getGroup(), 16) - parseInt(b.getGroup(), 16);
  if (res === 0) {
    // by element if same group
    res = parseInt(a.getElement(), 16) - parseInt(b.getElement(), 16);
  }
  return res;
}

/**
 * Split a group-element key used to store DICOM elements.
 *
 * @param {string} key The key in form "00280102" as generated by tag::getKey.
 * @returns {Tag} The DICOM tag.
 */
export function getTagFromKey(key) {
  if (!key || typeof key === 'undefined') {
    throw new Error('Cannot create tag with no key.');
  }
  if (key.length !== 8) {
    throw new Error('Cannot create tag with badly sized key: ' + key);
  }
  return new Tag(key.substring(0, 4), key.substring(4, 8));
}

/**
 * Get the TransferSyntaxUID Tag.
 *
 * @returns {Tag} The tag.
 */
export function getTransferSyntaxUIDTag() {
  return new Tag('0002', '0010');
}

/**
 * Get the FileMetaInformationGroupLength Tag.
 *
 * @returns {Tag} The tag.
 */
export function getFileMetaInformationGroupLengthTag() {
  return new Tag('0002', '0000');
}

/**
 * Is the input tag the FileMetaInformationGroupLength Tag.
 *
 * @param {Tag} tag The tag to test.
 * @returns {boolean} True if the asked tag.
 */
export function isFileMetaInformationGroupLengthTag(tag) {
  return tag.equals(getFileMetaInformationGroupLengthTag());
}

/**
 * Get the Item Tag.
 *
 * @returns {Tag} The tag.
 */
export function getItemTag() {
  return new Tag('FFFE', 'E000');
}

/**
 * Is the input tag the Item Tag.
 *
 * @param {Tag} tag The tag to test.
 * @returns {boolean} True if the asked tag.
 */
export function isItemTag(tag) {
  // faster than tag.equals(getItemTag());
  return tag.getKey() === 'FFFEE000';
}

/**
 * Get the ItemDelimitationItem Tag.
 *
 * @returns {Tag} The tag.
 */
export function getItemDelimitationItemTag() {
  return new Tag('FFFE', 'E00D');
}

/**
 * Is the input tag the ItemDelimitationItem Tag.
 *
 * @param {Tag} tag The tag to test.
 * @returns {boolean} True if the asked tag.
 */
export function isItemDelimitationItemTag(tag) {
  // faster than tag.equals(getItemDelimitationItemTag());
  return tag.getKey() === 'FFFEE00D';
}

/**
 * Get the SequenceDelimitationItem Tag.
 *
 * @returns {Tag} The tag.
 */
export function getSequenceDelimitationItemTag() {
  return new Tag('FFFE', 'E0DD');
}

/**
 * Is the input tag the SequenceDelimitationItem Tag.
 *
 * @param {Tag} tag The tag to test.
 * @returns {boolean} True if the asked tag.
 */
export function isSequenceDelimitationItemTag(tag) {
  // faster than tag.equals(getSequenceDelimitationItemTag());
  return tag.getKey() === 'FFFEE0DD';
}

/**
 * Get the PixelData Tag.
 *
 * @returns {Tag} The tag.
 */
export function getPixelDataTag() {
  return new Tag('7FE0', '0010');
}

/**
 * Is the input tag the PixelData Tag.
 *
 * @param {Tag} tag The tag to test.
 * @returns {boolean} True if the asked tag.
 */
export function isPixelDataTag(tag) {
  // faster than tag.equals(getPixelDataTag());
  return tag.getKey() === '7FE00010';
}

/**
 * Get a tag from the dictionary using a tag string name.
 *
 * @param {string} tagName The tag string name.
 * @returns {Tag|undefined} The tag object or null if not found.
 */
export function getTagFromDictionary(tagName) {
  if (typeof tagName === 'undefined' || tagName === null) {
    return null;
  }
  let group = null;
  let element = null;
  const dict = dictionary;
  const keys0 = Object.keys(dict);
  let keys1 = null;
  let foundTag = false;
  // search through dictionary
  for (let k0 = 0, lenK0 = keys0.length; k0 < lenK0; ++k0) {
    group = keys0[k0];
    keys1 = Object.keys(dict[group]);
    for (let k1 = 0, lenK1 = keys1.length; k1 < lenK1; ++k1) {
      element = keys1[k1];
      if (dict[group][element][2] === tagName) {
        foundTag = true;
        break;
      }
    }
    if (foundTag) {
      break;
    }
  }
  let tag;
  if (foundTag) {
    tag = new Tag(group, element);
  }
  return tag;
}