anonymizer.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. /**
  3. * tools/anonymizer.php file
  4. *
  5. * @package Nanodicom
  6. * @category Tools
  7. * @author Nano Documet <nanodocumet@gmail.com>
  8. * @version 1.3.1
  9. * @copyright (c) 2010-2011
  10. * @license http://www.opensource.org/licenses/mit-license.php MIT-license
  11. */
  12. /**
  13. * Dicom_Anonymizer class.
  14. *
  15. * Extends Nanodicom. It overwrites certain file tags. Fully extensible.
  16. * @package Nanodicom
  17. * @category Tools
  18. * @author Nano Documet <nanodocumet@gmail.com>
  19. * @version 1.3.1
  20. * @copyright (c) 2010-2011
  21. * @license http://www.opensource.org/licenses/mit-license.php MIT-license
  22. */
  23. class Dicom_Anonymizer extends Nanodicom {
  24. const RETURN_BLOB = 0;
  25. // Very basic tag elements to anonymize
  26. protected static $_basic = array(
  27. array(0x0008, 0x0020, '{date|Ymd}'), // Study Date
  28. array(0x0008, 0x0021, '{date|Ymd}'), // Series Date
  29. array(0x0008, 0x0090, 'physician{random}'), // Referring Physician
  30. array(0x0010, 0x0010, 'patient{consecutive}'), // Patient Name
  31. array(0x0010, 0x0020, 'id{consecutive}'), // Patient ID
  32. );
  33. // The mapped values
  34. protected $_map;
  35. // The tags to use
  36. protected $_tags;
  37. // The case sensitivity of the mapping
  38. protected static $case = 'insensitive';
  39. /**
  40. * Anonymizes the dataset
  41. *
  42. * @param mixed NULL or an array to overwrite defaults
  43. * @param array a set of values to be used for mapping (higher preference than replacement)
  44. * Each entry has 4 values:
  45. * group = group of tag element
  46. * element = element of the tag element
  47. * value = the expect value to be matched (trimmed)
  48. * assignment = the new replacement value for the given tag element and found value combined
  49. * @param integer the mode
  50. * @return string the anonymized dataset
  51. */
  52. public function anonymize($tags = NULL, $map = NULL, $mode = self::RETURN_BLOB)
  53. {
  54. $tags = ($tags == NULL) ? self::$_basic : $tags;
  55. $this->parse();
  56. $this->profiler['anonymize']['start'] = microtime(TRUE);
  57. // Set the tags
  58. foreach ($tags as $entry)
  59. {
  60. list($group, $element, $replacement) = $entry;
  61. $this->_tags[$group][$element] = $replacement;
  62. }
  63. if ($map !== NULL)
  64. {
  65. // Values were passed to be mapped
  66. foreach ($map as $entry)
  67. {
  68. // Each entry has 4 values:
  69. // group = group of tag element
  70. // element = element of the tag element
  71. // value = the expect value to be matched (trimmed)
  72. // assignment = the new replacement value for the given tag element and found value combined
  73. list ($group, $element, $value, $assignment) = $entry;
  74. $value = (self::$case == 'insensitive') ? strtolower($value) : $value;
  75. $name = sprintf('0x%04X',$group).'.'.sprintf('0x%04X',$element);
  76. $this->_map[$name][$value] = $assignment;
  77. }
  78. }
  79. // Anonymize the top level dataset
  80. $this->_anonymize($this->_dataset);
  81. // Return the new blob
  82. // TODO: allow more modes. Maybe replace, backup?
  83. switch ($mode)
  84. {
  85. case self::RETURN_BLOB:
  86. // Return the blob
  87. $blob = $this->write();
  88. break;
  89. default:
  90. $blob = $this->write();
  91. break;
  92. }
  93. $this->profiler['anonymize']['end'] = microtime(TRUE);
  94. return $blob;
  95. }
  96. /**
  97. * Anonymizes the dataset
  98. *
  99. * @param array the dataset passed by reference
  100. * @return string the anonymized dataset
  101. */
  102. protected function _anonymize(&$dataset)
  103. {
  104. // Iterate groups
  105. foreach ($dataset as $group => $elements)
  106. {
  107. // Iterate elements
  108. foreach ($elements as $element => $indexes)
  109. {
  110. // Iterate indexes
  111. foreach ($indexes as $index => $values)
  112. {
  113. if ( ! isset($values['done']))
  114. {
  115. // Read value if not read yet
  116. $this->_read_value_from_blob($dataset[$group][$element][$index], $group, $element);
  117. }
  118. // Update the tag element to anonymized values (if conditions are met)
  119. $this->_replace($dataset, $group, $element);
  120. if (count($values['ds']) > 0)
  121. {
  122. // Take care of items
  123. $this->_anonymize($dataset[$group][$element][$index]['ds']);
  124. }
  125. }
  126. unset($values);
  127. }
  128. unset($element, $indexes);
  129. }
  130. unset($group, $elements);
  131. }
  132. /**
  133. * Replaces the values
  134. *
  135. * @param array the dataset
  136. * @param integer the group
  137. * @param integer the element
  138. * @return boolean true if replacement was made, false otherwise
  139. */
  140. protected function _replace( & $dataset, $group, $element)
  141. {
  142. // Search the value in the current dataset
  143. $original_value = $this->dataset_value($dataset, $group, $element);
  144. // Do not update arrays
  145. if (is_array($original_value))
  146. return FALSE;
  147. // In case the value is not set
  148. $value = (empty($original_value)) ? 'none' : trim($original_value);
  149. // Check if we are doing a case insensitive comparison
  150. $value = (self::$case == 'insensitive') ? strtolower($value) : $value;
  151. $name = sprintf('0x%04X',$group).'.'.sprintf('0x%04X',$element);
  152. // A mapping was found. Return it
  153. if (isset($this->_map[$name][$value]))
  154. {
  155. $this->dataset_value($dataset, $group, $element, $this->_map[$name][$value]);
  156. return TRUE;
  157. }
  158. // If no tag is found, return false. Do not update dataset
  159. if ( ! isset($this->_tags[$group][$element]))
  160. return FALSE;
  161. // Get the replacement expression
  162. $replacement = $this->_tags[$group][$element];
  163. // Search for regex expressions
  164. if (preg_match('/{([a-z0-9]+)(\|([a-z0-9]+))?}$/i', $replacement, $matches))
  165. {
  166. switch ($matches[1])
  167. {
  168. // Set to date
  169. case 'date':
  170. $replacement = $this->_map[$name][$value] = str_replace('{date|'.$matches[3].'}', date($matches[3]), $replacement);
  171. break;
  172. // Consecutive
  173. case 'consecutive':
  174. $count = (isset($this->_map[$name])) ? count($this->_map[$name]) : 0;
  175. $replacement = $this->_map[$name][$value] = str_replace('{consecutive}', $count, $replacement);
  176. break;
  177. // Random, do not store it
  178. case 'random':
  179. $replacement = str_replace('{random}', sprintf('%04d',rand()), $replacement);
  180. break;
  181. }
  182. }
  183. else
  184. {
  185. $this->_map[$name][$value] = $replacement;
  186. }
  187. // Update the dataset
  188. $this->dataset_value($dataset, $group, $element, $replacement);
  189. // Return true
  190. return TRUE;
  191. }
  192. } // End Dicom_Anonymizer