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 | # +----+----+----+----+----+----+----+----+----+----+----+----+ # ||||<0x0000->|| # # Other Explicit VRs have VR chars, then 16 bit VL: # # +---------------------------------------+ # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | # +----+----+----+----+----+----+----+----+ # ||||| # # Implicit VRs have no VR field, then 32 bit VL: # # +---------------------------------------+ # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | # +----+----+----+----+----+----+----+----+ # |||| 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; } } ?>