Element.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | DICOM PHP Libraries |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 2007-2010 Alberto Bulletti |
  7. // +----------------------------------------------------------------------+
  8. // | |
  9. // +----------------------------------------------------------------------+
  10. // | Author: Alberto Bulletti |
  11. // +----------------------------------------------------------------------+
  12. //
  13. // $Id: DICOM.php,v 1.9.3 2010/02/01 19:18:17
  14. /*
  15. * Contributions to version 0.3 by PDicom
  16. */
  17. class File_DICOM_Element
  18. {
  19. /**
  20. * Type of VR for this element
  21. * @var integer
  22. */
  23. var $vr_type;
  24. /**
  25. * Element length
  26. * @var integer
  27. */
  28. var $value;
  29. /**
  30. * Element length
  31. * @var integer
  32. */
  33. var $code;
  34. /**
  35. * Element length
  36. * @var integer
  37. */
  38. var $length;
  39. /**
  40. * Complete header of this element. It might disappear in the future.
  41. * @var string
  42. */
  43. var $header;
  44. /**
  45. * Group this element belongs to
  46. * @var integer
  47. */
  48. var $group;
  49. /**
  50. * Element identifier
  51. * @var integer
  52. */
  53. var $element;
  54. /**
  55. * Position inside the current field for the element
  56. * @var integer
  57. */
  58. var $offset;
  59. /**
  60. * Name for this element
  61. * @var string
  62. */
  63. var $name;
  64. /**
  65. * Transfer Syntax. To be able of reading different types according to standard
  66. * 1.2.840.10008.1.2 Implicit VR, Little Endian
  67. * 1.2.840.10008.1.2.1 Explicit VR, Little Endian
  68. * 1.2.840.10008.1.2.2 Explicit VR, Big Endian
  69. * @var string
  70. */
  71. var $transfer_syntax;
  72. var $_elements;
  73. /**
  74. * Create DICOM file element from contents of the file given.
  75. * It assumes the element begins at the current position of the given
  76. * file pointer.
  77. *
  78. * @param resource $IN File handle for the file currently being parsed
  79. * @param array &$dictref Reference to the dictionary of DICOM headers
  80. * @param string $transfer_syntax The transfer syntax to use when reading the Element
  81. * @param integer $last_group The last group that we read
  82. * @access public
  83. */
  84. function File_DICOM_Element($IN, &$dictref, $transfer_syntax, $last_group, $item_from_sequence = false)
  85. {
  86. // Assign Transfer Syntax UID
  87. $this->transfer_syntax = $transfer_syntax;
  88. // The standard says that we need to read all the elements of the 0x0002 group and after that apply the proper
  89. // transfer syntax. The tricky part is after you have read the [0x0002][0x0010] but still need to read the
  90. // rest of elements in group 0x0002 as Explicit VR Little Endian
  91. if ($last_group <= 0x0002) {
  92. $offset = ftell($IN);
  93. // Reading the group value
  94. $test_group = $this->_readInt($IN, 2, 2);
  95. // Testing if group is either 0x000 or 0x0002
  96. if ($test_group == 0x0000 || $test_group == 0x0002 || $test_group == 0x0200) {
  97. $this->transfer_syntax = EXPLICIT_VR_LITTLE_ENDIAN;
  98. }
  99. // If not, then we have passed the 0x0002 group and new elements should be read according to the transfer
  100. // syntax stored in file
  101. fseek($IN, $offset);
  102. }
  103. // Tag holds group and element numbers in two bytes each.
  104. $offset = ftell($IN);
  105. $group = $this->_readInt($IN, 2, 2, "check_syntax");
  106. $element = $this->_readInt($IN, 2, 2, "check_syntax");
  107. if (isset($dictref[$group][$element])) {
  108. list($code, $numa, $name) = $dictref[$group][$element];
  109. } else {
  110. list($code, $numa, $name) = array("--", "UNKNOWN", "UNKNOWN");
  111. }
  112. if (!$item_from_sequence && $code == "NONE") {
  113. $code = "ITEM";
  114. }
  115. // Next 4 bytes are either explicit VR or length (implicit VR).
  116. $length = $this->_readLength($IN, $code);
  117. // Go to record start, read bytes up to value field, store in header.
  118. $diff = ftell($IN) - $offset;
  119. fseek($IN, $offset);
  120. $header = "";
  121. if ($diff > 0) {
  122. $header = fread($IN, $diff);
  123. }
  124. // Read in the value field. Certain fields need to be decoded.
  125. $value = '';
  126. //echo $code . " " . $numa . " " . $name . " " . $group . " " . $element . " " . $length . "\n";
  127. //if ($length == 1179258881)
  128. // exit;
  129. if ($length >= 0) {
  130. switch ($code) {
  131. // Decode ints and shorts.
  132. case 'UL':
  133. $value = $this->_readInt($IN, 4, $length, "check_syntax");
  134. break;
  135. case 'US':
  136. $value = $this->_readInt($IN, 2, $length, "check_syntax");
  137. break;
  138. case 'FL':
  139. // TODO: test this
  140. $value = $this->_readFloat($IN, 4, $length);
  141. break;
  142. case 'FD':
  143. // TODO: test this
  144. $value = $this->_readFloat($IN, 8, $length);
  145. break;
  146. // Binary data. Only save position. Is this right?
  147. case 'OW':
  148. case 'OB':
  149. case 'OX':
  150. case 'ITEM':
  151. $value = ftell($IN);
  152. fseek($IN, $length, SEEK_CUR);
  153. break;
  154. // It is a Sequence or Item or ItemDelimitationItem or SequenceDelimitationItem
  155. // Save position to value and read next elements
  156. case 'SQ':
  157. case 'NONE':
  158. $value = ftell($IN);
  159. while (ftell($IN) < $value + $length) {
  160. // New element. We are passing the current transfer syntax and a $last_group value of 0xFFFE
  161. $new_element =& new File_DICOM_Element($IN, $dictref, $transfer_syntax, 0xFFFE, true);
  162. $this->_elements[$new_element->group][$new_element->element][] =& $new_element;
  163. $this->_elements_by_name[$new_element->name][] =& $new_element;
  164. }
  165. break;
  166. default: // Made it to here: Read bytes verbatim.
  167. $value = "";
  168. if ($length > 0) {
  169. $value = fread($IN, $length);
  170. }
  171. $value = $this->trimValue($value);
  172. break;
  173. }
  174. }
  175. // Fill in hash of values and return them.
  176. $this->value = $value;
  177. $this->code = $code;
  178. $this->length = $length;
  179. // why save header??
  180. $this->header = $header;
  181. $this->element = $element;
  182. $this->group = $group;
  183. $this->name = $name;
  184. $this->offset = $offset;
  185. }
  186. /**
  187. * Return the Value Field length, and length before Value Field.
  188. * Implicit VR: Length is 4 byte int.
  189. * Explicit VR: 2 bytes hold VR, then 2 byte length.
  190. *
  191. * @param resource $IN File handle for the file currently being parsed
  192. * @access private
  193. * @return integer The length for the current field
  194. */
  195. function _readLength($IN, $code)
  196. {
  197. global $VR_array;
  198. // Read 4 bytes into b0, b1, b2, b3.
  199. $buff = fread($IN, 4);
  200. if (strlen($buff) < 4) {
  201. return 0;
  202. }
  203. $b = unpack("C4", $buff);
  204. // Temp string to test for explicit VR
  205. $vrstr = pack("C", $b[1]) . pack("C", $b[2]);
  206. # Assume that this is explicit VR if b[1] and b[2] match a known VR code.
  207. # Possibility (prob 26/16384) exists that the two low order field length
  208. # bytes of an implicit VR field will match a VR code.
  209. # FIXED: Now it needs to check the right value from the "code".
  210. # TODO: Multiple options for "code"
  211. # DICOM PS 3.5 Sect 7.1.2: Data Element Structure with Explicit VR
  212. # Explicit VRs store VR as text chars in 2 bytes.
  213. # VRs of OB, OW, SQ, UN, UT have VR chars, then 0x0000, then 32 bit VL:
  214. #
  215. # +-----------------------------------------------------------+
  216. # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
  217. # +----+----+----+----+----+----+----+----+----+----+----+----+
  218. # |<Group-->|<Element>|<VR----->|<0x0000->|<Length----------->|<Value->
  219. #
  220. # Other Explicit VRs have VR chars, then 16 bit VL:
  221. #
  222. # +---------------------------------------+
  223. # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
  224. # +----+----+----+----+----+----+----+----+
  225. # |<Group-->|<Element>|<VR----->|<Length->|<Value->
  226. #
  227. # Implicit VRs have no VR field, then 32 bit VL:
  228. #
  229. # +---------------------------------------+
  230. # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
  231. # +----+----+----+----+----+----+----+----+
  232. # |<Group-->|<Element>|<Length----------->|<Value->
  233. if (array_key_exists($vrstr,$VR_array)) {
  234. if (($vrstr == $code) || ($code == "--") || (substr($code,0,1) == "X") || (substr($code,1,1) == "X")) {
  235. // Have a code for an explicit VR: Retrieve VR element
  236. list($name, $bytes, $fixed) = $VR_array[$vrstr];
  237. if ($bytes == 0) {
  238. $this->vr_type = FILE_DICOM_VR_TYPE_EXPLICIT_32_BITS;
  239. // This is an OB, OW, SQ, UN or UT: 32 bit VL field.
  240. // Have seen in some files length 0xffff here...
  241. // Looking for:
  242. // unsigned long (always 32 bit, little endian byte order)
  243. return $this->_readInt($IN, 4, 4, "check_syntax");
  244. } else {
  245. // This is an explicit VR with 16 bit length.
  246. $this->vr_type = FILE_DICOM_VR_TYPE_EXPLICIT_16_BITS;
  247. // Here for Explicit VR Big Endian the length is read differently
  248. if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN) {
  249. return ($b[3] << 8) + $b[4];
  250. }
  251. return ($b[4] << 8) + $b[3];
  252. }
  253. }
  254. }
  255. // Made it to here: Implicit VR, 32 bit length.
  256. $this->vr_type = FILE_DICOM_VR_TYPE_IMPLICIT;
  257. // Here for Explicit VR Big Endian the length is read differently
  258. if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN) {
  259. return ($b[1] << 24) + ($b[2] << 16) + ($b[3] << 8) + $b[4];
  260. }
  261. return ($b[4] << 24) + ($b[3] << 16) + ($b[2] << 8) + $b[1];
  262. }
  263. /**
  264. * Read an integer field from a file handle
  265. * If $len > $bytes multiple values are read in and
  266. * stored as a string representation of an array.
  267. * This method will probably change in the future.
  268. *
  269. * @access private
  270. * @param resource $IN filehandle for the file currently being parsed
  271. * @param integer $bytes Number of bytes for integer (2 => short, 4 => integer)
  272. * @param integer $len Optional total number of bytes on the field
  273. * @param string $type Either "check_syntax" or "no_check_syntax".
  274. * @return mixed integer value if $len == $bytes, an array of integer if $len > $bytes
  275. */
  276. function _readInt($IN, $bytes, $len, $type = "no_check_syntax")
  277. {
  278. $format = ($bytes == 2) ? "v" : "V";
  279. $buff = fread($IN, $len);
  280. if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN && $type == "check_syntax")
  281. $buff = strrev($buff);
  282. if ($len == $bytes) {
  283. if (strlen($buff) > 0) {
  284. $val = unpack($format, $buff);
  285. //return $val[''];
  286. return current($val);
  287. } else {
  288. return '';
  289. }
  290. } else {
  291. // Multiple values: Create array.
  292. // Change this!!!
  293. $vals = array();
  294. for ($pos = 0; $pos < $len; $pos += $bytes) {
  295. //$unpacked = unpack("$format", substr($buff, $pos, $bytes));
  296. $unpacked = unpack($format, substr($buff, $pos, $bytes));
  297. //$vals[] = $unpacked[''];
  298. //var_dump($unpacked);
  299. $vals[] = current($unpacked);
  300. }
  301. $val = "[" . join(", ", $vals) . "]";
  302. return $val;
  303. }
  304. }
  305. /**
  306. * Read a float field from a file handle
  307. * If $len > $bytes multiple values are read in and
  308. * stored as a string representation of an array.
  309. * This method will probably change in the future.
  310. *
  311. * @access private
  312. * @param resource $IN filehandle for the file currently being parsed
  313. * @param integer $bytes Number of bytes for float (4 => float, 8 => double)
  314. * @param integer $len Total number of bytes on the field
  315. * @return mixed double value if $len == $bytes, an array of doubles if $len > $bytes
  316. */
  317. function _readFloat($IN, $bytes, $len)
  318. {
  319. $format = ($bytes == 4) ? 'f' : 'd';
  320. $buff = fread($IN, $len);
  321. //if ($this->transfer_syntax == EXPLICIT_VR_BIG_ENDIAN)
  322. // $buff = strrev($buff);
  323. if ($len == $bytes) {
  324. if (strlen($buff) > 0) {
  325. $val = unpack($format, $buff);
  326. return current($val);
  327. // return $val[''];
  328. } else {
  329. return '';
  330. }
  331. } else {
  332. // Multiple values: Create array.
  333. // Change this!!!
  334. $vals = array();
  335. for ($pos = 0; $pos < $len; $pos += $bytes) {
  336. $unpacked = unpack("$format", substr($buff, $pos, $bytes));
  337. $vals[] = $unpacked[''];
  338. }
  339. $val = "[" . join(", ", $vals) . "]";
  340. return $val;
  341. }
  342. }
  343. /**
  344. * Retrieves the value field for this File_DICOM_Element
  345. *
  346. * @access public
  347. * @return mixed The value for this File_DICOM_Element
  348. */
  349. function getValue()
  350. {
  351. return $this->value;
  352. }
  353. /**
  354. * Sets the value field for this File_DICOM_Element
  355. *
  356. * @access public
  357. * @return boolean True is successfull, False if failed
  358. */
  359. function setValue()
  360. {
  361. return $this->value;
  362. }
  363. /**
  364. * Takes out extra padding chr(0) from value.
  365. *
  366. * @access public
  367. * @param string $value The string to be trimmed
  368. * @return string The trimmed value
  369. */
  370. function trimValue($value)
  371. {
  372. if (substr($value,-1) == chr(0))
  373. return substr($value,0,-1);
  374. return $value;
  375. }
  376. /**
  377. * Adds padding chr(0) at the end if not an even length.
  378. *
  379. * @access public
  380. * @param string $value The string to be padded
  381. * @return string The padded value
  382. */
  383. function padValue($value)
  384. {
  385. if (strlen($value) % 2 != 0)
  386. return $value . chr(0);
  387. return $value;
  388. }
  389. }
  390. ?>