| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- <?php
- /* vim: set expandtab tabstop=4 shiftwidth=4: */
- // +----------------------------------------------------------------------+
- // | DICOM PHP Libraries |
- // +----------------------------------------------------------------------+
- // | Copyright (c) 2007-2010 Alberto Bulletti |
- // +----------------------------------------------------------------------+
- // | |
- // +----------------------------------------------------------------------+
- // | Author: Alberto Bulletti |
- // +----------------------------------------------------------------------+
- //
- // $Id: DICOM.php,v 1.9.3 2010/02/01 19:18:17
- /*
- * Contributions to version 0.3 by PDicom
- */
- class File_DICOM_Element
- {
- /**
- * Type of VR for this element
- * @var integer
- */
- var $vr_type;
- /**
- * Element length
- * @var integer
- */
- var $value;
- /**
- * Element length
- * @var integer
- */
- var $code;
- /**
- * Element length
- * @var integer
- */
- var $length;
- /**
- * Complete header of this element. It might disappear in the future.
- * @var string
- */
- var $header;
- /**
- * Group this element belongs to
- * @var integer
- */
- var $group;
- /**
- * Element identifier
- * @var integer
- */
- var $element;
- /**
- * Position inside the current field for the element
- * @var integer
- */
- var $offset;
-
- /**
- * Name for this element
- * @var string
- */
- var $name;
- /**
- * Transfer Syntax. To be able of reading different types according to standard
- * 1.2.840.10008.1.2 Implicit VR, Little Endian
- * 1.2.840.10008.1.2.1 Explicit VR, Little Endian
- * 1.2.840.10008.1.2.2 Explicit VR, Big Endian
- * @var string
- */
- var $transfer_syntax;
- var $_elements;
-
- /**
- * Create DICOM file element from contents of the file given.
- * It assumes the element begins at the current position of the given
- * file pointer.
- *
- * @param resource $IN File handle for the file currently being parsed
- * @param array &$dictref Reference to the dictionary of DICOM headers
- * @param string $transfer_syntax The transfer syntax to use when reading the Element
- * @param integer $last_group The last group that we read
- * @access public
- */
- function File_DICOM_Element($IN, &$dictref, $transfer_syntax, $last_group, $item_from_sequence = false)
- {
- // Assign Transfer Syntax UID
- $this->transfer_syntax = $transfer_syntax;
- // The standard says that we need to read all the elements of the 0x0002 group and after that apply the proper
- // transfer syntax. The tricky part is after you have read the [0x0002][0x0010] but still need to read the
- // rest of elements in group 0x0002 as Explicit VR Little Endian
- if ($last_group <= 0x0002) {
- $offset = ftell($IN);
- // Reading the group value
- $test_group = $this->_readInt($IN, 2, 2);
- // Testing if group is either 0x000 or 0x0002
- if ($test_group == 0x0000 || $test_group == 0x0002 || $test_group == 0x0200) {
- $this->transfer_syntax = EXPLICIT_VR_LITTLE_ENDIAN;
- }
- // If not, then we have passed the 0x0002 group and new elements should be read according to the transfer
- // syntax stored in file
- fseek($IN, $offset);
- }
- // Tag holds group and element numbers in two bytes each.
- $offset = ftell($IN);
- $group = $this->_readInt($IN, 2, 2, "check_syntax");
- $element = $this->_readInt($IN, 2, 2, "check_syntax");
- if (isset($dictref[$group][$element])) {
- list($code, $numa, $name) = $dictref[$group][$element];
- } else {
- list($code, $numa, $name) = array("--", "UNKNOWN", "UNKNOWN");
- }
-
- if (!$item_from_sequence && $code == "NONE") {
- $code = "ITEM";
- }
- // Next 4 bytes are either explicit VR or length (implicit VR).
- $length = $this->_readLength($IN, $code);
-
- // Go to record start, read bytes up to value field, store in header.
- $diff = ftell($IN) - $offset;
- fseek($IN, $offset);
- $header = "";
- if ($diff > 0) {
- $header = fread($IN, $diff);
- }
-
- // Read in the value field. Certain fields need to be decoded.
- $value = '';
- //echo $code . " " . $numa . " " . $name . " " . $group . " " . $element . " " . $length . "\n";
- //if ($length == 1179258881)
- // exit;
- if ($length >= 0) {
- switch ($code) {
- // Decode ints and shorts.
- case 'UL':
- $value = $this->_readInt($IN, 4, $length, "check_syntax");
- break;
- case 'US':
- $value = $this->_readInt($IN, 2, $length, "check_syntax");
- break;
- case 'FL':
- // TODO: test this
- $value = $this->_readFloat($IN, 4, $length);
- break;
- case 'FD':
- // TODO: test this
- $value = $this->_readFloat($IN, 8, $length);
- break;
- // Binary data. Only save position. Is this right?
- case 'OW':
- case 'OB':
- case 'OX':
- case 'ITEM':
- $value = ftell($IN);
- fseek($IN, $length, SEEK_CUR);
- break;
- // It is a Sequence or Item or ItemDelimitationItem or SequenceDelimitationItem
- // Save position to value and read next elements
- case 'SQ':
- case 'NONE':
- $value = ftell($IN);
- while (ftell($IN) < $value + $length) {
- // New element. We are passing the current transfer syntax and a $last_group value of 0xFFFE
- $new_element =& new File_DICOM_Element($IN, $dictref, $transfer_syntax, 0xFFFE, true);
- $this->_elements[$new_element->group][$new_element->element][] =& $new_element;
- $this->_elements_by_name[$new_element->name][] =& $new_element;
- }
- break;
- default: // Made it to here: Read bytes verbatim.
- $value = "";
- if ($length > 0) {
- $value = fread($IN, $length);
- }
- $value = $this->trimValue($value);
- break;
- }
- }
-
- // Fill in hash of values and return them.
- $this->value = $value;
- $this->code = $code;
- $this->length = $length;
- // why save header??
- $this->header = $header;
- $this->element = $element;
- $this->group = $group;
- $this->name = $name;
- $this->offset = $offset;
- }
- /**
- * Return the Value Field length, and length before Value Field.
- * Implicit VR: Length is 4 byte int.
- * Explicit VR: 2 bytes hold VR, then 2 byte length.
- *
- * @param resource $IN File handle for the file currently being parsed
- * @access private
- * @return integer The length for the current field
- */
- function _readLength($IN, $code)
- {
- global $VR_array;
- // Read 4 bytes into b0, b1, b2, b3.
- $buff = fread($IN, 4);
- if (strlen($buff) < 4) {
- return 0;
- }
- $b = unpack("C4", $buff);
- // Temp string to test for explicit VR
- $vrstr = pack("C", $b[1]) . pack("C", $b[2]);
- # Assume that this is explicit VR if b[1] and b[2] match a known VR code.
- # Possibility (prob 26/16384) exists that the two low order field length
- # bytes of an implicit VR field will match a VR code.
- # FIXED: Now it needs to check the right value from the "code".
- # TODO: Multiple options for "code"
-
- # DICOM PS 3.5 Sect 7.1.2: Data Element Structure with Explicit VR
- # Explicit VRs store VR as text chars in 2 bytes.
- # VRs of OB, OW, SQ, UN, UT have VR chars, then 0x0000, then 32 bit VL:
- #
- # +-----------------------------------------------------------+
- # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
- # +----+----+----+----+----+----+----+----+----+----+----+----+
- # |<Group-->|<Element>|<VR----->|<0x0000->|<Length----------->|<Value->
- #
- # Other Explicit VRs have VR chars, then 16 bit VL:
- #
- # +---------------------------------------+
- # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- # +----+----+----+----+----+----+----+----+
- # |<Group-->|<Element>|<VR----->|<Length->|<Value->
- #
- # Implicit VRs have no VR field, then 32 bit VL:
- #
- # +---------------------------------------+
- # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- # +----+----+----+----+----+----+----+----+
- # |<Group-->|<Element>|<Length----------->|<Value->
-
- if (array_key_exists($vrstr,$VR_array)) {
- if (($vrstr == $code) || ($code == "--") || (substr($code,0,1) == "X") || (substr($code,1,1) == "X")) {
- // Have a code for an explicit VR: Retrieve VR element
- list($name, $bytes, $fixed) = $VR_array[$vrstr];
- if ($bytes == 0) {
- $this->vr_type = FILE_DICOM_VR_TYPE_EXPLICIT_32_BITS;
- // This is an OB, OW, SQ, UN or UT: 32 bit VL field.
- // Have seen in some files length 0xffff here...
- // Looking for:
- // unsigned long (always 32 bit, little endian byte order)
- return $this->_readInt($IN, 4, 4, "check_syntax");
- } else {
- // This is an explicit VR with 16 bit length.
- $this->vr_type = FILE_DICOM_VR_TYPE_EXPLICIT_16_BITS;
- // Here for Explicit VR Big Endian the length is read differently
- if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN) {
- return ($b[3] << 8) + $b[4];
- }
- return ($b[4] << 8) + $b[3];
- }
- }
- }
- // Made it to here: Implicit VR, 32 bit length.
- $this->vr_type = FILE_DICOM_VR_TYPE_IMPLICIT;
- // Here for Explicit VR Big Endian the length is read differently
- if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN) {
- return ($b[1] << 24) + ($b[2] << 16) + ($b[3] << 8) + $b[4];
- }
- return ($b[4] << 24) + ($b[3] << 16) + ($b[2] << 8) + $b[1];
- }
- /**
- * Read an integer field from a file handle
- * If $len > $bytes multiple values are read in and
- * stored as a string representation of an array.
- * This method will probably change in the future.
- *
- * @access private
- * @param resource $IN filehandle for the file currently being parsed
- * @param integer $bytes Number of bytes for integer (2 => short, 4 => integer)
- * @param integer $len Optional total number of bytes on the field
- * @param string $type Either "check_syntax" or "no_check_syntax".
- * @return mixed integer value if $len == $bytes, an array of integer if $len > $bytes
- */
- function _readInt($IN, $bytes, $len, $type = "no_check_syntax")
- {
- $format = ($bytes == 2) ? "v" : "V";
-
- $buff = fread($IN, $len);
- if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN && $type == "check_syntax")
- $buff = strrev($buff);
- if ($len == $bytes) {
- if (strlen($buff) > 0) {
- $val = unpack($format, $buff);
- //return $val[''];
- return current($val);
- } else {
- return '';
- }
- } else {
- // Multiple values: Create array.
- // Change this!!!
- $vals = array();
- for ($pos = 0; $pos < $len; $pos += $bytes) {
- //$unpacked = unpack("$format", substr($buff, $pos, $bytes));
- $unpacked = unpack($format, substr($buff, $pos, $bytes));
- //$vals[] = $unpacked[''];
- //var_dump($unpacked);
- $vals[] = current($unpacked);
- }
- $val = "[" . join(", ", $vals) . "]";
- return $val;
- }
- }
- /**
- * Read a float field from a file handle
- * If $len > $bytes multiple values are read in and
- * stored as a string representation of an array.
- * This method will probably change in the future.
- *
- * @access private
- * @param resource $IN filehandle for the file currently being parsed
- * @param integer $bytes Number of bytes for float (4 => float, 8 => double)
- * @param integer $len Total number of bytes on the field
- * @return mixed double value if $len == $bytes, an array of doubles if $len > $bytes
- */
- function _readFloat($IN, $bytes, $len)
- {
- $format = ($bytes == 4) ? 'f' : 'd';
-
- $buff = fread($IN, $len);
- //if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN)
- // $buff = strrev($buff);
- if ($len == $bytes) {
- if (strlen($buff) > 0) {
- $val = unpack($format, $buff);
- return current($val);
- // return $val[''];
- } else {
- return '';
- }
- } else {
- // Multiple values: Create array.
- // Change this!!!
- $vals = array();
- for ($pos = 0; $pos < $len; $pos += $bytes) {
- $unpacked = unpack("$format", substr($buff, $pos, $bytes));
- $vals[] = $unpacked[''];
- }
- $val = "[" . join(", ", $vals) . "]";
- return $val;
- }
- }
- /**
- * Retrieves the value field for this File_DICOM_Element
- *
- * @access public
- * @return mixed The value for this File_DICOM_Element
- */
- function getValue()
- {
- return $this->value;
- }
- /**
- * Sets the value field for this File_DICOM_Element
- *
- * @access public
- * @return boolean True is successfull, False if failed
- */
- function setValue()
- {
- return $this->value;
- }
-
- /**
- * Takes out extra padding chr(0) from value.
- *
- * @access public
- * @param string $value The string to be trimmed
- * @return string The trimmed value
- */
- function trimValue($value)
- {
- if (substr($value,-1) == chr(0))
- return substr($value,0,-1);
- return $value;
- }
- /**
- * Adds padding chr(0) at the end if not an even length.
- *
- * @access public
- * @param string $value The string to be padded
- * @return string The padded value
- */
- function padValue($value)
- {
- if (strlen($value) % 2 != 0)
- return $value . chr(0);
- return $value;
- }
- }
- ?>
|