diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bd64155 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,43 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text eol=lf + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.c text +*.h text +*.css text +*.html text +*.xml text +*.js text +*.conf text +*.sass text +*.scss text +*.md text + +# Declare files that will always have CRLF line endings on checkout. +*.bat text eol=crlf +*.cmd text eol=crlf + +# Declare files that will always have LF line endings on checkout. +*.sh text eol=lf +*.php text eol=lf + + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.bmp binary +*.gif binary +*.tiff binary +*.dcm binary +*.pdf binary +*.otf binary +*.eot binary +*.ttf binary +*.woff binary +*.woff2 binary +*.odt binary +*.zip binary +wait binary +/web/wait binary +/dicomprocessor/wait binary \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..039a7b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.settings +.buildpath +.project +node_modules +.sass-cache +.DS_Store +.vscode/* \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..322cb38 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,11 @@ +module.exports = function(grunt) { + + /* Set the config */ + grunt.initConfig(require('./grunt/config')(grunt)); + + /* Load the npm grunt tasks */ + require('load-grunt-tasks')(grunt, 'grunt-*'); + + /* Load our custom grunt tasks */ + grunt.loadTasks('./grunt/tasks'); +}; \ No newline at end of file diff --git a/OphInKowastereoModule.php b/OphInKowastereoModule.php new file mode 100644 index 0000000..f9a21b3 --- /dev/null +++ b/OphInKowastereoModule.php @@ -0,0 +1,31 @@ +. + * + * @package OpenEyes + * @link http://www.openeyes.org.uk + * @author OpenEyes + * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust + * @copyright Copyright (c) 2011-2012, OpenEyes Foundation + * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + */ + +class OphInKowastereoModule extends BaseEventTypeModule +{ + public function init() { + $this->setImport(array( + 'OphInKowastereo.models.*', + 'OphInKowastereo.components.*', + 'OphInKowastereo.commands.*', + )); + + parent::init(); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..930bc7b --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +OphInVisualfields +================= + +This module is currently under development and has no stable releases. diff --git a/RELEASE b/RELEASE new file mode 100644 index 0000000..6bebd89 --- /dev/null +++ b/RELEASE @@ -0,0 +1,6 @@ +Release History +=============== + +1.7.0 +----- +Initial release diff --git a/assets/css/.gitignore b/assets/css/.gitignore new file mode 100644 index 0000000..0953f01 --- /dev/null +++ b/assets/css/.gitignore @@ -0,0 +1 @@ +.x diff --git a/assets/css/desktop.ini b/assets/css/desktop.ini new file mode 100644 index 0000000..3a4e71b Binary files /dev/null and b/assets/css/desktop.ini differ diff --git a/assets/css/module.css b/assets/css/module.css new file mode 100644 index 0000000..66f69d8 --- /dev/null +++ b/assets/css/module.css @@ -0,0 +1,30 @@ +/** Visual Fields module styles */ +/** + * OpenEyes + * + * (C) Moorfields Eye Hospital NHS Foundation Trust, 2008-2011 + * (C) OpenEyes Foundation, 2011-2013 + * This file is part of OpenEyes. + * OpenEyes is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * OpenEyes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with OpenEyes in a file titled COPYING. If not, see . + * + * @package OpenEyes + * @link http://www.openeyes.org.uk + * @author OpenEyes + * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust + * @copyright Copyright (c) 2011-2013, OpenEyes Foundation + * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + */ +/* line 17, ../sass/components/_event.scss */ +.event { + border-color: #a5a0a9; +} +/* line 19, ../sass/components/_event.scss */ +.event .event-content { + background-image: url('../img/watermark.png?1400161568'); +} +/* line 22, ../sass/components/_event.scss */ +.event .event-title { + background-image: url('../img/medium.png?1400161568'); +} diff --git a/assets/desktop.ini b/assets/desktop.ini new file mode 100644 index 0000000..3a4e71b Binary files /dev/null and b/assets/desktop.ini differ diff --git a/assets/img/desktop.ini b/assets/img/desktop.ini new file mode 100644 index 0000000..3a4e71b Binary files /dev/null and b/assets/img/desktop.ini differ diff --git a/assets/img/medium.png b/assets/img/medium.png new file mode 100644 index 0000000..3d5e2bf Binary files /dev/null and b/assets/img/medium.png differ diff --git a/assets/img/small.png b/assets/img/small.png new file mode 100644 index 0000000..5db3ef8 Binary files /dev/null and b/assets/img/small.png differ diff --git a/assets/img/watermark.png b/assets/img/watermark.png new file mode 100644 index 0000000..3a7d5ab Binary files /dev/null and b/assets/img/watermark.png differ diff --git a/assets/js/.gitignore b/assets/js/.gitignore new file mode 100644 index 0000000..0953f01 --- /dev/null +++ b/assets/js/.gitignore @@ -0,0 +1 @@ +.x diff --git a/assets/js/desktop.ini b/assets/js/desktop.ini new file mode 100644 index 0000000..3a4e71b Binary files /dev/null and b/assets/js/desktop.ini differ diff --git a/assets/js/imageLoader.js b/assets/js/imageLoader.js new file mode 100644 index 0000000..5169677 --- /dev/null +++ b/assets/js/imageLoader.js @@ -0,0 +1,84 @@ +var currentImage = 0; + +/** + * Changes the + */ +function ev_mousemove (ev, id, imageObj, images, idOtherSide, imageObjOtherSide, + imagesOtherSide, id_linked, imagesLinked) { + var canvas = document.getElementById(id); + var context = canvas.getContext("2d"); + var canvasOtherSide = document.getElementById(idOtherSide); + var contextOtherSide = canvasOtherSide.getContext("2d"); + + // Get the mouse position relative to the canvas element. + if (ev.layerX || ev.layerX == 0) { // Firefox + x = ev.layerX; + } else if (ev.offsetX || ev.offsetX == 0) { // Opera + x = ev.offsetX; + } + + // image_width / images + var incr = 300 / images.length; + // image_width / increment + + var rect = canvas.getBoundingClientRect(); + x = ev.clientX - rect.left; + var pos = x / incr; +// alert(x + ', ' + ev.clientX + ', ' + rect.left); +// alert(x + ', ' + incr + ', ' + pos + ', ' + images.length + ', ' + imagesLinked.length + ', ' + currentImage); + if (currentImage != Math.floor(pos)) { + currentImage = Math.floor(pos); + if (currentImage > images.length) { + currentImage = images.length-1; + } +// alert(currentImage); + imageObj.onload = function() { + context.drawImage(imageObj, 0, 0); + } + imageObjOtherSide.onload = function() { + contextOtherSide.drawImage(imageObjOtherSide, 0, 0); + } +// imageObjAlg.onload = function() { +// } + if (currentImage < images.length) { + imageObj.src = images[currentImage]; + imageObjOtherSide.src = imagesOtherSide[currentImage]; + } else { + return; + } + + var canvasLinked = document.getElementById(id_linked); + + if (canvasLinked != null) { + var context2 = canvasLinked.getContext("2d"); + var multiplier = 1; + if (images.length < imagesLinked.length) { + multiplier = Math.round(imagesLinked.length / images.length); + } else if (images.length > imagesLinked.length) { + multiplier = Math.round(images.length / imagesLinked.length); + } else { + multiplier = 1; + } + var imageObj2 = new Image(); + imageObj2.onload = function() { + context2.drawImage(imageObj2, 0, 0); + contextOtherSide.drawImage(imageObjOtherSide, 0, 0); + } + if (images.length < imagesLinked.length) { + var index = 0; + if (currentImage > 0) { + index = Math.round((currentImage+1) * multiplier ); + if (index > imagesLinked.length -1) { + index = imagesLinked.length -1; + } + } + imageObj2.src = imagesLinked[index]; + imageObjOtherSide.src = imagesOtherSide[index]; + } else { + var index = Math.floor( (currentImage + 1 ) / multiplier); + imageObj2.src = imagesLinked[index]; + imageObjOtherSide.src = imagesOtherSide[index]; + } + } + } +} \ No newline at end of file diff --git a/assets/js/module.js b/assets/js/module.js new file mode 100644 index 0000000..e3dfb2e --- /dev/null +++ b/assets/js/module.js @@ -0,0 +1,16 @@ +$(document).ready(function() { + function selectField(e) { + + var side = e.data.side, id = $(this).val(); + + var field = window["OphInKowastereo_available_fields_" + side][id]; + + $('#Element_OphInKowastereo_Image_image_' + side).attr('src', field.url); + $('#Element_OphInKowastereo_Image_strategy_' + side).text(field.strategy); + $('#Element_OphInKowastereo_Image_pattern_' + side).text(field.pattern); + } + + $('#Element_OphInKowastereo_Image_right_field_id').change({side: "right"}, selectField); + $('#Element_OphInKowastereo_Image_left_field_id').change({side: "left"}, selectField); +}); + diff --git a/assets/sass/components/_event.scss b/assets/sass/components/_event.scss new file mode 100644 index 0000000..a0566e1 --- /dev/null +++ b/assets/sass/components/_event.scss @@ -0,0 +1,25 @@ +// * OpenEyes +// * +// * (C) Moorfields Eye Hospital NHS Foundation Trust, 2008-2011 +// * (C) OpenEyes Foundation, 2011-2013 +// * This file is part of OpenEyes. +// * OpenEyes is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// * OpenEyes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// * You should have received a copy of the GNU General Public License along with OpenEyes in a file titled COPYING. If not, see . +// * +// * @package OpenEyes +// * @link http://www.openeyes.org.uk +// * @author OpenEyes +// * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust +// * @copyright Copyright (c) 2011-2013, OpenEyes Foundation +// * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + +.event { + border-color: #a5a0a9; + .event-content { + background-image: image-url("watermark.png"); + } + .event-title { + background-image: image-url("medium.png"); + } +} diff --git a/assets/sass/components/desktop.ini b/assets/sass/components/desktop.ini new file mode 100644 index 0000000..3a4e71b Binary files /dev/null and b/assets/sass/components/desktop.ini differ diff --git a/assets/sass/desktop.ini b/assets/sass/desktop.ini new file mode 100644 index 0000000..3a4e71b Binary files /dev/null and b/assets/sass/desktop.ini differ diff --git a/assets/sass/module.scss b/assets/sass/module.scss new file mode 100644 index 0000000..7e7e680 --- /dev/null +++ b/assets/sass/module.scss @@ -0,0 +1,20 @@ +// * OpenEyes +// * +// * (C) Moorfields Eye Hospital NHS Foundation Trust, 2008-2011 +// * (C) OpenEyes Foundation, 2011-2013 +// * This file is part of OpenEyes. +// * OpenEyes is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// * OpenEyes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// * You should have received a copy of the GNU General Public License along with OpenEyes in a file titled COPYING. If not, see . +// * +// * @package OpenEyes +// * @link http://www.openeyes.org.uk +// * @author OpenEyes +// * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust +// * @copyright Copyright (c) 2011-2013, OpenEyes Foundation +// * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + +/** Visual Fields module styles */ + +@import "openeyes/module"; +@import "components/event"; diff --git a/commands/AnonymiseFieldsCommand.php b/commands/AnonymiseFieldsCommand.php new file mode 100644 index 0000000..0cad40f --- /dev/null +++ b/commands/AnonymiseFieldsCommand.php @@ -0,0 +1,243 @@ +. + * + * @package OpenEyes + * @link http://www.openeyes.org.uk + * @author OpenEyes + * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust + * @copyright Copyright (c) 2011-2012, OpenEyes Foundation + * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + */ +class AnonymiseFieldsCommand extends CConsoleCommand { + + public function getHelp() { + return "Usage:\n\n\tanonymisefields [command]\n\nwhere command can be any one of:\n" + . "\ntransformFmes --fmesDir=[inputdir] --outputDir=[outputDir] --realPidFile=[pidFile] --anonPidFile=[anonPidFile]\n\n" + . "\tTake the specified directory of FMES humphrey measurements, unpack the image from it, anonymise\n" + . "\tthe patient data, swap the recorded patient ID and swap it for the specified anonymous PID\n" + . "\tTwo files must be specified, each giving a newline separated list of patient IDs to substitute.\n" + . "\n\n" + . "anonymisefields redact --pattern=[filePattern]\n\n" + . "\tFor all files that match the specified pattern in the database, perform image oerations to remove name, PID etc.\n" + . "\tOnly images of the correct size (2400x3180) are transformed.\n" + . "\n\n" + . "anonymisefields redactTif --inDir=[tifInDir] --outDir=[tifOutDir]\n\n" + . "\tFor all files that match the specified pattern, perform image oerations to remove name, PID etc.\n" + . "\tOnly images of the correct size (2400x3180) are transformed.\n"; + + } + + /** + * Take a list of real patient identifiers that appear in a collection + * of FMES files, and remove the 'real' PID in the FMES file in favour + * of + * + * @param type $realPidFile + * @param type $anonPidFile + */ + public function actiontransformFmes($fmesDir, $outputDir, $realPidFile, $anonPidFile) { + foreach (array($fmesDir, $realPidFile, $anonPidFile) as $file) { + if (!file_exists($file)) { + echo $file . ' does not exist' . PHP_EOL; + exit(1); + } + } + + $realPids = file_get_contents($realPidFile); + $anonPids = file_get_contents($anonPidFile); + $rPids = explode(PHP_EOL, $realPids); + $aPids = explode(PHP_EOL, $anonPids); + // make sure PID count is equal: + if (count($rPids) != count($aPids)) { + echo 'Error: PID counts do not match; file contents must match 1-1' . PHP_EOL; + exit(1); + } + // check all real patients exist: + foreach ($aPids as $pid) { + if ($pid) { + if (count(Patient::model()->find("hos_num='" . $pid . "'")) < 1) { + echo 'Failed to find anonymous patient ' . $pid . PHP_EOL; + exit(1); + } + } + } + // now check that all 'real' patients are listed in the files: + $entries = array(); + // build up an array of matches we've encountered so far, and if it's + // been matched before, ignore it. + + $smgr = Yii::app()->service; + $fhirMarshal = Yii::app()->fhirMarshal; + if ($entry = glob($fmesDir . '/*.fmes')) { + foreach ($entry as $file) { + $field = file_get_contents($file); + $fieldObject = $fhirMarshal->parseXml($field); + $match = $this->getHosNum($file, $field); + if (!in_array($match, $entries)) { + // only add it if it's in the list of real patient IDs: + if (in_array($match, $rPids)) { + array_push($entries, $match); + } + } + } + } + // now create new FMES files + // need to go through each one, pairing anonymised IDs with real ones, + // replacing the real ID with the anonymised ID; note that we also + // need to swap out the image and do some redaction: + + if ($entry = glob($fmesDir . '/*.fmes')) { + foreach ($entry as $file) { + $field = file_get_contents($file); + $fieldObject = $fhirMarshal->parseXml($field); + // swap out hos nums: + $match = $this->getHosNum($file, $field); + if (in_array($match, $rPids)) { + $index = array_search($match, $rPids); + $anonPid = $aPids[$index]; + unset($fieldObject->patient_id); + $fieldObject->patient_id = "__OE_PATIENT_ID_" . $anonPid . "__"; + echo 'replacing ' . $match . ' with ' . $anonPid . PHP_EOL; + } else { + // not interested, move on: + continue; + } + // now swap out the actual image. This is slightly involved - + // we need to write the image to temporary file, perform + // image operations on it to anonymise PID, DoB etc., + // step 1: extract image: + $image = base64_decode($fieldObject->image_scan_data); + unset($fieldObject->image_scan_data); + // now redact it - we need to perform imagemagick operations: + $img = 'img.gif'; + file_put_contents($img, $image); + $image = new Imagick($img); + $this->fillImage($image); + $image->writeImage($img); + $contents = file_get_contents($img); + $fieldObject->image_scan_data = base64_encode($contents); + $doc = new DOMDocument; + file_put_contents($outputDir . '/' . basename($file), $fhirMarshal->renderXml($fieldObject)); + echo "Successfully written " . $file . PHP_EOL; + } + } + } + + /** + * Take images from the in-directory, anonymise them and place the resulting + * file in the sepcified out-directory. + * + * @param type $inDir + * @param type $outDir + */ + public function actionRedactTif($inDir, $outDir) { + + if ($entries = glob($inDir . '/*.tif')) { + foreach($entries as $entry) { + $this->anonymiseTif($inDir . '/' . basename($entry), $outDir); + } + } + } + + /** + * Trawl an existing OE database and find all files that match the given + * pattern. A pattern MUST be specified. + * + * If no pattern is specified, all images that match the standard humphrey + * image size are processed. + */ + public function actionRedact($pattern = null) { + $criteria = new CDbCriteria; + if ($pattern != null) { + $criteria->condition = 'name like :name'; + $criteria->params = array(':name' => $pattern); + } else { + echo 'You MUST specify a file pattern to match.'; + } + $files = ProtectedFile::model()->findAll($criteria); + // we can't really filter images, except on size - for now just + // assume the count is half the amount when taking thumbnails into + // consideration + echo (count($files)/2) . " files found for modification."; + if ($files) { + foreach ($files as $file) { + if (file_exists($file->getPath())) { + $this->anonymiseTif($file->getPath()); + } else { + echo 'Could not transform file; ' . $file->getPathName() + . ' does not exist.' . PHP_EOL; + } + } + } + } + + /** + * Create a new image based on the image passed in and anonymise. + * + * @param type $file specified image file to anonymise; must be a valid path + * and the image must be the correct size. + * @param type $out the directory to place the anonymised file. + */ + private function anonymiseTif($file, $out) { + $image = new Imagick($file); + $geo = $image->getImageGeometry(); + // only modify the main image, not the thumbnails: + if ($geo['width'] == 2400 + && $geo['height'] == 3180) { + echo 'Modifying ' . $file . PHP_EOL; + $this->fillImage($image); + echo $out.PHP_EOL; + $image->writeImage($file . '.tmp'); + copy($file . '.tmp', $out . '/' . basename($file, '.tif') . '.gif'); + } + } + + /** + * Fills the image at the specified locations. Designed specifically to + * grey-out patient name, DoB, PID and HFA serial number, + * + * @param Imagick $image + */ + private function fillImage($image) { + $draw = new ImagickDraw(); //Create a new drawing class (?) + + $draw->setFillColor('grey'); + // main patient details - name, pid, dob: + $draw->rectangle(190, 80, 2210, 254); + // date, time, age: + $draw->rectangle(1773, 291, 2160, 489); + // bottom of image - serial number etc.: + $draw->rectangle(190, 2960, 2160, 3099); + $image->drawImage($draw); + $image->setImageFormat('gif'); + } + + /** + * + * @param type $file + * @param type $field + * @param array $matches + * @return type + */ + private function getHosNum($file, $field) { + + $matches = array(); + preg_match("/__OE_PATIENT_ID_([0-9]*)__/", $field, $matches); + if (count($matches) < 2) { + echo "Failed to extract patient ID in " . basename($file) . "; moving to " . $this->errorDir . PHP_EOL; + $this->move($this->errorDir, $file); + continue; + } + return str_pad($matches[1], 7, '0', STR_PAD_LEFT); + } + +} diff --git a/commands/AnonymiseFieldsCommand.php.anon-xml b/commands/AnonymiseFieldsCommand.php.anon-xml new file mode 100644 index 0000000..726d13a --- /dev/null +++ b/commands/AnonymiseFieldsCommand.php.anon-xml @@ -0,0 +1,223 @@ +. + * + * @package OpenEyes + * @link http://www.openeyes.org.uk + * @author OpenEyes + * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust + * @copyright Copyright (c) 2011-2012, OpenEyes Foundation + * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + */ +class AnonymiseFieldsCommand extends CConsoleCommand { + + public function getHelp() { + return "Usage(first form):\n\nanonymisefields transformFmes --fmesDir=[inputdir] --outputDir=[outputDir] --realPidFile=[pidFile] --anonPidFile\n\n" + . "Take the specified directory of FMES humphrey measurements, unpack the image from it, anonymise\n" + . "the patient data, swap the recorded patient ID and swap it for the specified anonymous PID\n" + . "Two files must be specified, each giving a newline separated list of patient IDs to substitute.\n" + . "\n" + . "Usage: (second form):\n\n" + . "anonymisefields redact --pattern=[filePattern]\n\n" + . "For all files that match the specified pattern, perform image oerations to remove name, PID etc.\n" + . "Only images of the correct size (2400x3180) are transformed.\n"; + + } + + /** + * Take a list of real patient identifiers that appear in a collection + * of FMES files, and remove the 'real' PID in the FMES file in favour + * of + * + * @param type $realPidFile + * @param type $anonPidFile + */ + public function actiontransformFmes($fmesDir, $outputDir, $realPidFile, $anonPidFile) { + foreach (array($fmesDir, $realPidFile, $anonPidFile) as $file) { + if (!file_exists($file)) { + echo $file . ' does not exist' . PHP_EOL; + exit(1); + } + } + + $realPids = file_get_contents($realPidFile); + $anonPids = file_get_contents($anonPidFile); + $rPids = explode(PHP_EOL, $realPids); + $aPids = explode(PHP_EOL, $anonPids); + // make sure PID count is equal: + if (count($rPids) != count($aPids)) { + echo 'Error: PID counts do not match; file contents must match 1-1' . PHP_EOL; + exit(1); + } + // check all real patients exist: + foreach ($aPids as $pid) { + if ($pid) { + if (count(Patient::model()->find("hos_num='" . $pid . "'")) < 1) { + echo 'Failed to find anonymous patient ' . $pid . PHP_EOL; + exit(1); + } + } + } + // now check that all 'real' patients are listed in the files: + $entries = array(); + // build up an array of matches we've encountered so far, and if it's + // been matched before, ignore it. + + $smgr = Yii::app()->service; + $fhirMarshal = Yii::app()->fhirMarshal; + if ($entry = glob($fmesDir . '/*.fmes')) { + foreach ($entry as $file) { + $field = file_get_contents($file); + $fieldObject = $fhirMarshal->parseXml($field); + $match = $this->getHosNum($file, $field); + if (!in_array($match, $entries)) { + // only add it if it's in the list of real patient IDs: + if (in_array($match, $rPids)) { + array_push($entries, $match); + } + } + } + } + // now create new FMES files + // need to go through each one, pairing anonymised IDs with real ones, + // replacing the real ID with the anonymised ID; note that we also + // need to swap out the image and do some redaction: + + if ($entry = glob($fmesDir . '/*.fmes')) { + foreach ($entry as $file) { + $field = file_get_contents($file); + $fieldObject = $fhirMarshal->parseXml($field); + // swap out hos nums: + $match = $this->getHosNum($file, $field); + if (in_array($match, $rPids)) { + $index = array_search($match, $rPids); + $anonPid = $aPids[$index]; + unset($fieldObject->patient_id); + $fieldObject->patient_id = "__OE_PATIENT_ID_" . $anonPid . "__"; + echo 'replacing ' . $match . ' with ' . $anonPid . PHP_EOL; + } else { + // not interested, move on: + continue; + } + if ($fieldObject->source) { + $source = $this->anonymiseXml(base64_decode($fieldObject->source)); + unset($fieldObject->source); + $fieldObject->source = $soruce; + } // now swap out the actual image. This is slightly involved - + // we need to write the image to temporary file, perform + // image operations on it to anonymise PID, DoB etc., + $image = base64_decode($fieldObject->image_scan_data); + unset($fieldObject->image_scan_data); + // now redact it - we need to perform imagemagick operations: + $img = 'img.gif'; + file_put_contents($img, $image); + $image = new Imagick($img); + $this->fillImage($image); + $image->writeImage($img); + $contents = file_get_contents($img); + $fieldObject->image_scan_data = base64_encode($contents); + $doc = new DOMDocument; + file_put_contents($outputDir . '/' . basename($file), $fhirMarshal->renderXml($fieldObject)); + echo "Successfully written " . $file . PHP_EOL; + } + } + } + + /** + * Trawl an existing OE database and find all files that match the given + * pattern. A pattern MUST be specified. + * + * If no pattern is specified, all images that match the standard humphrey + * image size are processed. + */ + public function actionRedact($pattern = null) { + $criteria = new CDbCriteria; + if ($pattern != null) { + $criteria->condition = 'name like :name'; + $criteria->params = array(':name' => $pattern); + } else { + echo 'You MUST specify a file pattern to match.'; + } + $files = ProtectedFile::model()->findAll($criteria); + // we can't really filter images, except on size - for now just + // assume the count is half the amount when taking thumbnails into + // consideration + echo (count($files)/2) . " files found for modification."; + if ($files) { + foreach ($files as $file) { + if (file_exists($file->getPath())) { + $image = new Imagick($file->getPath()); + $geo = $image->getImageGeometry(); + // only modify the main image, not the thumbnails: + if ($geo['width'] == 2400 + && $geo['height'] == 3180) { + echo 'Modifying ' . $file->getPath() . PHP_EOL; + $this->fillImage($image); + $image->writeImage($file->getPath()); + } + } else { + echo 'Could not transform file; ' . $file->getPathName() + . ' does not exist.' . PHP_EOL; + } + } + } + } + + /** + * Fills the image at the specified locations. Designed specifically to + * grey-out patient name, DoB, PID and HFA serial number, + * + * @param Imagick $image + */ + private function fillImage($image) { + $draw = new ImagickDraw(); //Create a new drawing class (?) + + $draw->setFillColor('grey'); + // main patient details - name, pid, dob: + $draw->rectangle(190, 80, 2210, 254); + // date, time, age: + $draw->rectangle(1773, 291, 2160, 489); + // bottom of image - serial number etc.: + $draw->rectangle(190, 2960, 2160, 3099); + $image->drawImage($draw); + } + + /** + * + * @param type $file + * @param type $field + * @param array $matches + * @return type + */ + private function getHosNum($file, $field) { + + $matches = array(); + preg_match("/__OE_PATIENT_ID_([0-9]*)__/", $field, $matches); + if (count($matches) < 2) { + echo "Failed to extract patient ID in " . basename($file) . "; moving to " . $this->errorDir . PHP_EOL; + $this->move($this->errorDir, $file); + continue; + } + return str_pad($matches[1], 7, '0', STR_PAD_LEFT); + } + + /** + * + * @param type $source + * @return type + */ + private function anonymiseXml($source) { + $xml = new SimpleXMLElement($source); +// echo $xml-> + return ""; + } + +} diff --git a/commands/Gif2JPegCommand.php b/commands/Gif2JPegCommand.php new file mode 100644 index 0000000..1bcc80a --- /dev/null +++ b/commands/Gif2JPegCommand.php @@ -0,0 +1,126 @@ +. + * + * @package OpenEyes + * @link http://www.openeyes.org.uk + * @author OpenEyes + * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust + * @copyright Copyright (c) 2011-2012, OpenEyes Foundation + * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + */ +class Gif2JpegCommand extends CConsoleCommand { + + + /** + * Take a list of real patient identifiers that appear in a collection + * of FMES files, and remove the 'real' PID in the FMES file in favour + * of + * + * @param type $realPidFile + * @param type $anonPidFile + */ + public function actionTransform($archiveDir) { + print $archiveDir . PHP_EOL; + $files = glob($archiveDir . "/*.jpg"); + echo count($files) . PHP_EOL; + foreach ($files as $file) { + echo basename($file) . PHP_EOL; + $pfs = ProtectedFile::model()->findAll("name=\"" . basename($file) . "\""); +// echo count($pfs) . PHP_EOL; + foreach($pfs as $pf) { + echo $pf->getPath() . PHP_EOL; + $dims = getimagesize($pf->getPath()); + echo $dims[3] . PHP_EOL; + } + } + return; + + $realPids = file_get_contents($realPidFile); + $anonPids = file_get_contents($anonPidFile); + $rPids = explode(PHP_EOL, $realPids); + $aPids = explode(PHP_EOL, $anonPids); + // make sure PID count is equal: + if (count($rPids) != count($aPids)) { + echo 'Error: PID counts do not match; file contents must match 1-1' . PHP_EOL; + exit(1); + } + // check all real patients exist: + foreach ($aPids as $pid) { + if ($pid) { + if (count(Patient::model()->find("hos_num='" . $pid . "'")) < 1) { + echo 'Failed to find anonymous patient ' . $pid . PHP_EOL; + exit(1); + } + } + } + // now check that all 'real' patients are listed in the files: + $entries = array(); + // build up an array of matches we've encountered so far, and if it's + // been matched before, ignore it. + + $smgr = Yii::app()->service; + $fhirMarshal = Yii::app()->fhirMarshal; + if ($entry = glob($fmesDir . '/*.fmes')) { + foreach ($entry as $file) { + $field = file_get_contents($file); + $fieldObject = $fhirMarshal->parseXml($field); + $match = $this->getHosNum($file, $field); + if (!in_array($match, $entries)) { + // only add it if it's in the list of real patient IDs: + if (in_array($match, $rPids)) { + array_push($entries, $match); + } + } + } + } + // now create new FMES files + // need to go through each one, pairing anonymised IDs with real ones, + // replacing the real ID with the anonymised ID; note that we also + // need to swap out the image and do some redaction: + + if ($entry = glob($fmesDir . '/*.fmes')) { + foreach ($entry as $file) { + $field = file_get_contents($file); + $fieldObject = $fhirMarshal->parseXml($field); + // swap out hos nums: + $match = $this->getHosNum($file, $field); + if (in_array($match, $rPids)) { + $index = array_search($match, $rPids); + $anonPid = $aPids[$index]; + unset($fieldObject->patient_id); + $fieldObject->patient_id = "__OE_PATIENT_ID_" . $anonPid . "__"; + echo 'replacing ' . $match . ' with ' . $anonPid . PHP_EOL; + } else { + // not interested, move on: + continue; + } + // now swap out the actual image. This is slightly involved - + // we need to write the image to temporary file, perform + // image operations on it to anonymise PID, DoB etc., + // step 1: extract image: + $image = base64_decode($fieldObject->image_scan_data); + unset($fieldObject->image_scan_data); + // now redact it - we need to perform imagemagick operations: + $img = 'img.gif'; + file_put_contents($img, $image); + $image = new Imagick($img); + $this->fillImage($image); + $image->writeImage($img); + $contents = file_get_contents($img); + $fieldObject->image_scan_data = base64_encode($contents); + $doc = new DOMDocument; + file_put_contents($outputDir . '/' . basename($file), $fhirMarshal->renderXml($fieldObject)); + echo "Successfully written " . $file . PHP_EOL; + } + } + } +} diff --git a/commands/LegacyFieldsCommand.php b/commands/LegacyFieldsCommand.php new file mode 100644 index 0000000..8681bae --- /dev/null +++ b/commands/LegacyFieldsCommand.php @@ -0,0 +1,239 @@ +. + * + * @package OpenEyes + * @link http://www.openeyes.org.uk + * @author OpenEyes + * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust + * @copyright Copyright (c) 2011-2012, OpenEyes Foundation + * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0 + */ +class LegacyFieldsCommand extends CConsoleCommand { + + public $importDir; + public $archiveDir; + public $errorDir; + public $dupDir; + public $interval; + + public function getHelp() { + return "Usage: legacyfields import --interval=