init
This commit is contained in:
18
nv/html/dcmgw/LICENSE.md
Normal file
18
nv/html/dcmgw/LICENSE.md
Normal file
@@ -0,0 +1,18 @@
|
||||
Copyright (C) 2013 Jose Antonio Perez
|
||||
[ http://goo.gl/lW17d ]
|
||||
|
||||
dcmgw: DICOM c-find gateway
|
||||
[ https://github.com/jap1968/dcmgw ]
|
||||
|
||||
This program 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
|
||||
any later version.
|
||||
|
||||
This program 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 this program. If not, see http://www.gnu.org/licenses/gpl.html
|
||||
15
nv/html/dcmgw/README.md
Normal file
15
nv/html/dcmgw/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
dcmgw is a script acting as a gateway allowing to do c-find requests to a Dicom server.
|
||||
The results are returned as XML contents. The xml folder contains the XSD specification
|
||||
of the response messages as well as some XML samples.
|
||||
|
||||
dcmgw relies upon dcmqr, from the [dcm4che2 toolkit](http://www.dcm4che.org/confluence/display/d2/dcm4che2+DICOM+Toolkit).
|
||||
|
||||
Dicom servers are intended to be accessed just by Dicom clients. This is one important limitation in order to access
|
||||
these servers from web applications.
|
||||
|
||||
Currently, things are changing and there are new proposals in order to extend the Dicom protocol by means of
|
||||
RESTful web services.
|
||||
|
||||
In particular, QIDO-RS (Query based on ID for DICOM Objects by RESTful Services) will be included
|
||||
in Dicom [supplement 166](http://www.dclunie.com/dicom-status/status.html). Meanwhile, applications like dcmgw provide similar functionality allowing
|
||||
to access Dicom servers from web applications right now.
|
||||
478
nv/html/dcmgw/dcmgw.php
Normal file
478
nv/html/dcmgw/dcmgw.php
Normal file
@@ -0,0 +1,478 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|
||||
Copyright (C) 2013 Jose Antonio Perez
|
||||
[ http://goo.gl/lW17d ]
|
||||
|
||||
This file is part of dcmgw (Dicom gateway)
|
||||
[ https://github.com/jap1968/dcmgw ]
|
||||
|
||||
This program 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
|
||||
any later version.
|
||||
|
||||
This program 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 this program. If not, see http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
define('DCMGW_INCLUDE', './');
|
||||
|
||||
include_once(DCMGW_INCLUDE.'dcmgwPacs.php');
|
||||
include_once(DCMGW_INCLUDE.'dcmgwConfig.php');
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
_dumpvar($_REQUEST);
|
||||
// operation: cfind | dicomfields | wado | binarydata
|
||||
$operation = isset($_GET['operation']) ? $_GET['operation'] : 'cfind';
|
||||
|
||||
if ($operation == 'cfind') {
|
||||
if (isset($_GET['studyUID'])) {
|
||||
$studyUID = $_GET['studyUID'];
|
||||
dicomQRStudy($pacs, $studyUID);
|
||||
}
|
||||
else {
|
||||
dicomQR($pacs);
|
||||
}
|
||||
}
|
||||
else if ($operation == 'dicomfields') {
|
||||
$studyUID = $_GET['studyUID'];
|
||||
$seriesUID = $_GET['seriesUID'];
|
||||
$objectUID = $_GET['objectUID'];
|
||||
getDicomWadoFields($pacs, $studyUID, $seriesUID, $objectUID);
|
||||
}
|
||||
else if ($operation == 'binarydata') {
|
||||
$studyUID = $_GET['studyUID'];
|
||||
$seriesUID = $_GET['seriesUID'];
|
||||
$objectUID = $_GET['objectUID'];
|
||||
$len = $_GET['len'];
|
||||
$src = $_GET['src'];
|
||||
getBinaryData($pacs, $studyUID, $seriesUID, $objectUID, $len, $src);
|
||||
}
|
||||
else {
|
||||
// define('TAM_MAX_THUMB', 120);
|
||||
$studyUID = $_GET['studyUID'];
|
||||
$seriesUID = $_GET['seriesUID'];
|
||||
$objectUID = $_GET['objectUID'];
|
||||
getWado($pacs, $studyUID, $seriesUID, $objectUID);
|
||||
|
||||
/*
|
||||
$uriWado = $pacs->getUriWado($studyUID, $seriesUID, $objectUID);
|
||||
// To get thumbnails:
|
||||
if (isset($_GET['rows']) && isset($_GET['cols'])) {
|
||||
$uriWado .= "&rows={$_GET['rows']}&cols={$_GET['cols']}";
|
||||
}
|
||||
// header("Cache-Control: public");
|
||||
// header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('+1 day')).' GMT');
|
||||
header("Content-Type: image/jpeg");
|
||||
readfile($uriWado);
|
||||
*/
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
// *****************************************************************************
|
||||
|
||||
/**
|
||||
* The Query/Retrieve answer is processed
|
||||
* outputRes: Q/R answer string
|
||||
*/
|
||||
|
||||
function processResponse($outputRes, $encoding) {
|
||||
|
||||
$pattern = "/^((?:[0|1][0-9]|2[0-3])(?::[0-5][0-9]){2},[0-9]{3})\s([A-Z]+)\s+-\s(.+)$/";
|
||||
$dicom = array();
|
||||
|
||||
foreach ($outputRes as $numLine => $strLine) {
|
||||
$matches = array();
|
||||
$numMatches = preg_match($pattern, $strLine, $matches);
|
||||
if ($numMatches == 1) {
|
||||
// $matches[1]: time (hh:mm:ss,mil)
|
||||
// $matches[2]: INFO|ERROR|???
|
||||
$outputType = $matches[2];
|
||||
// $matches[3]: info string
|
||||
$outputStr = $matches[3];
|
||||
if (DEBUG_LEVEL >= DEBUG_DUMP) {
|
||||
// Mensajes de tipo INFO / ERROR
|
||||
echo "<div style='color:red; margin:2em 0.5em;'>Patrón reconocido (I)</div>";
|
||||
echo "<pre>";
|
||||
print_r($matches);
|
||||
echo "</pre>";
|
||||
}
|
||||
// 20120120: ToDo: Dealing with errors. Inform the client about the error
|
||||
if ($outputType == 'ERROR') {
|
||||
echo "ERROR";
|
||||
if (DEBUG_LEVEL >= DEBUG_INFO) {
|
||||
echo ": $outputStr";
|
||||
}
|
||||
echo "<br>\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($element = identifyPattern($outputStr)) {
|
||||
$lineNum = $numLine + 1;
|
||||
_dumpvar($element);
|
||||
$xmlString = '';
|
||||
while(strlen($outputRes[$lineNum]) > 0) {
|
||||
if (DEBUG_LEVEL >= DEBUG_INFO) {
|
||||
echo $outputRes[$lineNum]."<br>\n";
|
||||
}
|
||||
if ($df = processDicomField($outputRes[$lineNum], $encoding)) {
|
||||
_dumpvar($df);
|
||||
$xmlString .= $df['xmlString']."\n";
|
||||
}
|
||||
$lineNum++;
|
||||
}
|
||||
$element['xmlString'] = $element['xmlPre'].$xmlString.$element['xmlPost'];
|
||||
if (DEBUG_LEVEL >= DEBUG_DUMP) {
|
||||
// print_r($dicomHeaders);
|
||||
echo $element['xmlString'];
|
||||
}
|
||||
array_push($dicom, $element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (DEBUG_LEVEL >= DEBUG_INFO) {
|
||||
echo "<pre>";
|
||||
// print_r($result);
|
||||
echo "</pre>";
|
||||
}
|
||||
elseif (DEBUG_LEVEL == DEBUG_NONE) {
|
||||
header('Content-type: text/xml; charset='.XML_ENCODING);
|
||||
echo "<?xml version=\"1.0\" encoding=\"".XML_ENCODING."\"?>\n";
|
||||
|
||||
$fechaAhora = strftime("%Y%m%d%H%M%S%z");
|
||||
echo "<dicom datetime=\"$fechaAhora\">\n";
|
||||
foreach ($dicom as $response) {
|
||||
echo $response['xmlString'];
|
||||
}
|
||||
echo "</dicom>\n";
|
||||
}
|
||||
} // function processResponse(...)
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
function identifyPattern($testStr) {
|
||||
|
||||
$patterns = array();
|
||||
$element = false;
|
||||
|
||||
if (SHOW_REQUEST)
|
||||
{
|
||||
// Send Query Request using 1.2.840.10008.5.1.4.1.2.2.1/Study Root Query/Retrieve Information Model - FIND:
|
||||
$patterns[QUERY_REQUEST_ROOT] = "/^Send Query Request using ([0-9]+(?:\.[0-9]+)+)\/([[:alpha:][:space:]\/\-]+):$/";
|
||||
|
||||
// Send Query Request #1/3 using 1.2.840.10008.5.1.4.1.2.2.1/Study Root Query/Retrieve Information Model - FIND:
|
||||
$patterns[QUERY_REQUEST] = "/^Send Query Request #([1-9][0-9]*)\/([1-9][0-9]*) using ([0-9]+(?:\.[0-9]+)+)\/([[:alpha:][:space:]\/\-]+):$/";
|
||||
}
|
||||
|
||||
// Query Response #1:
|
||||
$patterns[QUERY_RESPONSE_ROOT] = "/^Query Response #([1-9][0-9]*):$/";
|
||||
|
||||
// Query Response #1 for Query Request #1/3:
|
||||
$patterns[QUERY_RESPONSE] = "/^Query Response #([1-9][0-9]*) for Query Request #([1-9][0-9]*)\/([1-9][0-9]*):$/";
|
||||
$matches = array();
|
||||
|
||||
$numMatches = 0;
|
||||
foreach ($patterns as $type => $pattern)
|
||||
{
|
||||
if (DEBUG_LEVEL >= DEBUG_INFO) {
|
||||
echo "Testing $testStr<br>against pattern: $pattern<br>";
|
||||
}
|
||||
$numMatches = preg_match($pattern, $testStr, $matches);
|
||||
if($numMatches == 1)
|
||||
{
|
||||
$element = array();
|
||||
$element['xmlPre'] = "<!--{$matches[0]}-->\n";
|
||||
$element['type'] = $type;
|
||||
switch ($type) {
|
||||
case QUERY_REQUEST_ROOT:
|
||||
$element['tag'] = 'request';
|
||||
$element['xmlPre'] .= "<{$element['tag']} qrim='{$matches[1]}'>\n";
|
||||
break;
|
||||
case QUERY_RESPONSE_ROOT:
|
||||
$element['tag'] = 'response';
|
||||
$element['xmlPre'] .= "<{$element['tag']} number='{$matches[1]}'>\n";
|
||||
break;
|
||||
case QUERY_REQUEST:
|
||||
$element['tag'] = 'qrequest';
|
||||
$element['xmlPre'] .= "<{$element['tag']} number='{$matches[1]}' qrim='{$matches[3]}'>\n";
|
||||
break;
|
||||
case QUERY_RESPONSE:
|
||||
$element['tag'] = 'qresponse';
|
||||
$element['xmlPre'] .= "<{$element['tag']} number='{$matches[1]}' qrequest='{$matches[2]}'>\n";
|
||||
break;
|
||||
default:
|
||||
$element['tag'] = 'dummy';
|
||||
$element['xmlPre'] .= "<{$element['tag']}>\n";
|
||||
}
|
||||
$element['xmlPost'] = "</{$element['tag']}>\n";
|
||||
break; // break 2 ???
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
// 20120112: Parameters are obtained directly from $_GET
|
||||
// ToDo: Dealing with errors (returning error information) in the case of incorrect parameters
|
||||
function dicomQR ($pacs)
|
||||
{
|
||||
|
||||
// Nivel Q/R | | -P | -S | -I
|
||||
$extraFields = '-r 00080061 -r 00081030 -r 00100010 -r 00100021 -r 00100030 -r 00100040 -r 00201206 -r 00201208';
|
||||
// (0008,0061) Modalities in Study
|
||||
// (0008,1030) Study Description
|
||||
|
||||
// (0010,0010) Patient's Name
|
||||
// (0010,0021) Issuer of Patient ID
|
||||
// (0010,0030) Patient's Birth Date
|
||||
// (0010,0040) Patient's Sex
|
||||
|
||||
// (0020,1206) Number of Study Related Series
|
||||
// (0020,1208) Number of Study Related Instances
|
||||
|
||||
// (0020,000D) Study IUID
|
||||
|
||||
|
||||
// ToDo: Take also into account PatientIdIssuer
|
||||
$qPatId = isset($_GET['patId']) ? " -q 00100020='" . $_GET['patId'] . "'" : "";
|
||||
$qAccessionNo = isset($_GET['AccessionNo']) ? " -q 00080050='" . $_GET['AccessionNo'] . "'" : "";
|
||||
$qStudyDate = isset($_GET['studyDate']) ? " -q StudyDate={$_GET['studyDate']}" : ""; // AAAAMMDD ???
|
||||
$qFilter = $qPatId . $qAccessionNo . $qStudyDate . " ";
|
||||
|
||||
// $command = 'LANG='.$pacs->getLocale().' '.PATH_BASE_DCM4CHE2.'dcmqr -device '.AETITLE_GATEWAY.' '.$pacs->getDicomServer()." -q 00100020='$patientId' $extraFields";
|
||||
|
||||
if (strlen($qFilter) > 1) {
|
||||
$command = 'JAVA_HOME=/usr/lib/jvm/jdk1.7.0_80 LANG='.$pacs->getLocale().' '.PATH_BASE_DCM4CHE2.'dcmqr -device '.AETITLE_GATEWAY.' '.$pacs->getDicomServer() . $qFilter . $extraFields;
|
||||
|
||||
// echo $command;
|
||||
|
||||
$outputRes = array();
|
||||
exec($command, $outputRes);
|
||||
|
||||
if (DEBUG_LEVEL >= DEBUG_DUMP) {
|
||||
echo "<pre>";
|
||||
print_r($outputRes);
|
||||
echo "</pre>";
|
||||
}
|
||||
|
||||
processResponse($outputRes, $pacs->encoding);
|
||||
}
|
||||
|
||||
/*
|
||||
Example of a Q/R answer (at study level)
|
||||
14:28:31,609 INFO - Query Response #4:
|
||||
(0008,0005) CS #10 [ISO_IR 100] Specific Character Set
|
||||
(0008,0020) DA #8 [20080410] Study Date
|
||||
(0008,0030) TM #14 [103025.000000] Study Time
|
||||
(0008,0050) SH #0 [] Accession Number
|
||||
(0008,0052) CS #6 [STUDY] Query/Retrieve Level
|
||||
(0008,0054) AE #8 [PACSECO] Retrieve AE Title
|
||||
(0008,0056) CS #6 [ONLINE] Instance Availability
|
||||
(0010,0020) LO #6 [72471] Patient ID
|
||||
(0020,000D) UI #62 [1.2.840.113543.6.6.3.4.617968937028517893191307041671843345256] Study Instance U
|
||||
(0020,0010) SH #4 [3379] Study ID
|
||||
(0020,1206) IS #2 [1] Number of Study Related Series
|
||||
(0020,1208) IS #2 [2] Number of Study Related Instances
|
||||
(0088,0130) SH #0 [] Storage Media File-set ID
|
||||
(0088,0140) UI #0 [] Storage Media File-set UID
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
/**
|
||||
* Performa a QR operation to get series and instances from a given study
|
||||
*/
|
||||
function dicomQRStudy ($pacs, $study_IUID)
|
||||
{
|
||||
|
||||
// Nivel Q/R | | -P | -S | -I
|
||||
// Problema: Algun campo extra lo pide en la subquery de instancias, pero no en la query principal de series
|
||||
$extraFields = '-r 00080060 -r 0008103E -r 00200011 -r 00201209';
|
||||
// (0008,0060) Modality
|
||||
// (0008,103E) Series Description // Solo se envia en las subquery
|
||||
// (0020,0011) Series Number
|
||||
// (0020,1209) Number of Series Related Instances // Solo se envia en las subquery
|
||||
|
||||
$command = 'JAVA_HOME=/usr/lib/jvm/jdk1.7.0_80 LANG='.$pacs->getLocale().' '.PATH_BASE_DCM4CHE2.'dcmqr -device '.AETITLE_GATEWAY.' -I '.$pacs->getDicomServer()." -q 0020000D=$study_IUID $extraFields";
|
||||
|
||||
_debuglog($command);
|
||||
|
||||
$outputRes = array();
|
||||
exec($command, $outputRes);
|
||||
|
||||
if (DEBUG_LEVEL >= DEBUG_DUMP) {
|
||||
echo "<pre>";
|
||||
print_r($outputRes);
|
||||
echo "</pre>";
|
||||
}
|
||||
|
||||
processResponse($outputRes, $pacs->encoding);
|
||||
}
|
||||
|
||||
function processDicomField($dcmString, $encoding) {
|
||||
/*
|
||||
// (0008,0020) DA #8 [20080410] Study Date
|
||||
// Returned values (string):
|
||||
tagGroup = 0008
|
||||
tagElement = 0020
|
||||
value = 20080410
|
||||
tagName = Study Date
|
||||
valueRepr = DA
|
||||
valueLength = 8 (int)
|
||||
*/
|
||||
|
||||
if (DEBUG_LEVEL >= DEBUG_INFO) {
|
||||
echo "processDicomField($dcmString)<br>";
|
||||
}
|
||||
$matches = array();
|
||||
$pattern = "/^\(([0-9A-F]{4}),([0-9A-F]{4})\)\s([A-Z]{2})\s#([0-9]+)\s\[([^\]]*)\]\s(.*)$/";
|
||||
$numMatches = preg_match($pattern, $dcmString, $matches);
|
||||
if($numMatches == 1) {
|
||||
$d = array();
|
||||
if (DEBUG_LEVEL >= DEBUG_DUMP) {
|
||||
echo "<pre>";
|
||||
print_r($matches);
|
||||
echo "</pre>";
|
||||
}
|
||||
$d['tagGroup'] = $matches[1];
|
||||
$d['tagElement'] = $matches[2];
|
||||
$d['valueRepr'] = $matches[3];
|
||||
$d['valueLength'] = $matches[4];
|
||||
$d['value'] = $matches[5];
|
||||
$d['tagName'] = $matches[6];
|
||||
|
||||
// Characters to convert to UTF-8: Elements with VR=PN, SH, LO, ST, LT, UT in the Data Set.
|
||||
$vr = $d['valueRepr'];
|
||||
if ($vr == 'PN' || $vr == 'SH' || $vr == 'LO' || $vr == 'ST' || $vr == 'LT' || $vr == 'UT') {
|
||||
$d['value'] = iconv($encoding, XML_ENCODING, $d['value']);
|
||||
$d['value'] = htmlspecialchars($d['value'], ENT_QUOTES); // Convert non valid XML characters
|
||||
}
|
||||
|
||||
$xmlString = "<!--{$d['tagName']}-->\n";
|
||||
$xmlString .= "<attr tag=\"{$d['tagGroup']}{$d['tagElement']}\" vr=\"{$d['valueRepr']}\" len=\"{$d['valueLength']}\">";
|
||||
$xmlString .= "{$d['value']}</attr>";
|
||||
$d['xmlString'] = $xmlString;
|
||||
}
|
||||
else {
|
||||
if (DEBUG_LEVEL >= DEBUG_DUMP) {
|
||||
echo "NO match!!!<br>";
|
||||
}
|
||||
$d = false;
|
||||
}
|
||||
return $d;
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
/**
|
||||
* Recovers a Dicom object via WADO
|
||||
*/
|
||||
function getWado($pacs, $studyUID, $seriesUID, $objectUID) {
|
||||
|
||||
// $tStart = microtime(true);
|
||||
|
||||
$uriWado = $pacs->getUriWado($studyUID, $seriesUID, $objectUID);
|
||||
if (isset($_GET['contentType']) && $_GET['contentType'] == 'application/dicom') {
|
||||
$header = "Content-Type: application/dicom";
|
||||
$uriWado .= "&contentType=application%2Fdicom";
|
||||
// 20130502: Force transfer syntax to Explicit VR little endian
|
||||
$uriWado .= "&transferSyntax=1.2.840.10008.1.2.1";
|
||||
}
|
||||
else {
|
||||
error_log("ERROR jpeg/WADO files should not be used anymore");
|
||||
$header = "Content-Type: image/jpeg";
|
||||
}
|
||||
|
||||
// To get thumbnails:
|
||||
if (isset($_GET['rows']) && isset($_GET['cols']))
|
||||
{
|
||||
// $uriWado .= "&rows={$_GET['rows']}&cols={$_GET['cols']}";
|
||||
$uriWado .= "&rows=".THUMBNAIL_SIZE."&cols=".THUMBNAIL_SIZE;
|
||||
}
|
||||
|
||||
if (RETRIEVE_LOCAL) {
|
||||
// Store a local copy of the Dicom file to obtain its size.
|
||||
$tmpFWado = tempnam('/tmp', 'dicom_');
|
||||
// $tmpFWado = tempnam('/dev/shm/dicom', 'dicom_');
|
||||
if ($getOk = getLocalWado($uriWado, $tmpFWado))
|
||||
{
|
||||
$fp = fopen($tmpFWado, 'rb');
|
||||
header("Content-Type: application/dicom");
|
||||
header("Content-Length: " . filesize($tmpFWado));
|
||||
header("Content-Disposition: inline");
|
||||
fpassthru($fp);
|
||||
fclose($fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log("Error retrieving WADO/Dicom object");
|
||||
}
|
||||
unlink($tmpFWado);
|
||||
}
|
||||
else
|
||||
{
|
||||
// header("Cache-Control: public");
|
||||
// header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('+1 day')).' GMT');
|
||||
header($header);
|
||||
readfile($uriWado);
|
||||
}
|
||||
|
||||
/*
|
||||
$tEnd = microtime(true);
|
||||
$tDelta = round(1000 * ($tEnd - $tStart));
|
||||
$logMsg = "getWado: $tDelta ms";
|
||||
error_log($logMsg);
|
||||
*/
|
||||
}
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
function getLocalWado($uriWado, $tmpFWado) {
|
||||
$getOk = false;
|
||||
$numTry = 0;
|
||||
$maxTry = 3;
|
||||
$delayTry = 1;
|
||||
|
||||
while (!$getOk && $numTry < $maxTry)
|
||||
{
|
||||
if ($numTry > 0)
|
||||
{
|
||||
sleep($delayTry);
|
||||
}
|
||||
$retrieveCommand = PATH_WGET." '$uriWado' -O $tmpFWado --server-response 2> /dev/stdout | grep Content-Type | awk -F\"Content-Type: \" '{print $2}'";
|
||||
|
||||
// error_log($retrieveCommand);
|
||||
$outputCommand = array();
|
||||
exec($retrieveCommand, $outputCommand);
|
||||
// Verification: The returned contents is a dicom object
|
||||
$getOk = $outputCommand[0] == 'application/dicom';
|
||||
$numTry++;
|
||||
}
|
||||
return $getOk;
|
||||
}
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
function _debuglog($cmd) {
|
||||
error_log("$cmd\n",3,"/var/www/html/dcmgw/tmp/debuglog");
|
||||
}
|
||||
|
||||
function _dumpvar($var) {
|
||||
ob_start();
|
||||
print_r($var);
|
||||
_debuglog(ob_get_contents());
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
98
nv/html/dcmgw/dcmgwConfig.php
Normal file
98
nv/html/dcmgw/dcmgwConfig.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
ini_set("log_errors",1);
|
||||
ini_set("error_log","/var/www/html/dcmgw/php_error.log");
|
||||
|
||||
/*
|
||||
|
||||
Copyright (C) 2013 Jose Antonio Perez
|
||||
[ http://goo.gl/lW17d ]
|
||||
|
||||
This file is part of dcmgw (Dicom gateway)
|
||||
[ https://github.com/jap1968/dcmgw ]
|
||||
|
||||
This program 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
|
||||
any later version.
|
||||
|
||||
This program 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 this program. If not, see http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
/**
|
||||
* Avoids direct execution of the script
|
||||
*/
|
||||
if (!defined('DCMGW_INCLUDE')) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
|
||||
die;
|
||||
}
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
// Configuration: Change values to fit your environment
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
// You should end up having a variable $pacs pointing to your PACS server.
|
||||
// You can find below some configuration samples for public servers
|
||||
// Please, for testing purposes use your own server (if possible) to avoid overloading the public servers
|
||||
|
||||
// http://www.dicomserver.co.uk/
|
||||
// http://wado.medicalconnections.co.uk/WADO/WADO.asp
|
||||
//$pacsMedicalConnections = new PACS('ABPACS', '127.0.0.1', '11112');
|
||||
//$pacsMedicalConnections->setWado('http', '127.0.0.1', '80', 'wado/wadp.asp');
|
||||
|
||||
// DC's public PixelMed PACS.
|
||||
// No WADO?
|
||||
//$pacsPixelMed = new PACS('ABPACS', '127.0.0.1', '11112');
|
||||
|
||||
// Public JVSdicom Server. Not sure if this one has any contents.
|
||||
// No WADO ?
|
||||
// More information: http://153.1.200.58/?q=dicom_images
|
||||
//$pacsJVSdicom = new PACS('JVSDICOM', '153.1.200.58', '104');
|
||||
|
||||
// My own PACS
|
||||
$pacsHome = new DCM4CHEE('ABPACS', '128.199.154.150');
|
||||
|
||||
|
||||
$pacs = $pacsHome;
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
// This script makes use of dcmqr, from the dcm4che2 Dicom toolkit
|
||||
// dcm4che2 Dicom toolkit: http://www.dcm4che.org/confluence/display/d2/dcm4che2+DICOM+Toolkit
|
||||
// dcmqr: http://www.dcm4che.org/confluence/display/d2/dcmqr
|
||||
//define ('PATH_BASE_DCM4CHE2', '/usr/local/dcm4che/dcm4che2/bin/');
|
||||
define ('PATH_BASE_DCM4CHE2', '/var/www/dcm4che/dcm4che2/bin/');
|
||||
define ('PATH_BASE_DICOM_CACHE', '/tmp/dicom/');
|
||||
|
||||
define ('PATH_WGET', '/usr/bin/wget');
|
||||
define ('RETRIEVE_LOCAL', true);
|
||||
|
||||
define('SHOW_REQUEST', false);
|
||||
define('XML_ENCODING', 'UTF-8');
|
||||
|
||||
define ('DEBUG_NONE', 0);
|
||||
define ('DEBUG_INFO', 5);
|
||||
define ('DEBUG_DUMP', 9);
|
||||
define ('DEBUG_LEVEL', DEBUG_NONE);
|
||||
|
||||
define ('AETITLE_GATEWAY', 'DCMGW');
|
||||
define ('THUMBNAIL_SIZE', '200');
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
define('QUERY_REQUEST_ROOT', '0');
|
||||
define('QUERY_RESPONSE_ROOT', '1');
|
||||
define('QUERY_REQUEST', '2');
|
||||
define('QUERY_RESPONSE', '3');
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
133
nv/html/dcmgw/dcmgwPacs.php
Normal file
133
nv/html/dcmgw/dcmgwPacs.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|
||||
Copyright (C) 2013 Jose Antonio Perez
|
||||
[ http://goo.gl/lW17d ]
|
||||
|
||||
This file is part of dcmgw (Dicom gateway)
|
||||
[ https://github.com/jap1968/dcmgw ]
|
||||
|
||||
This program 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
|
||||
any later version.
|
||||
|
||||
This program 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 this program. If not, see http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
/**
|
||||
* Avoids direct execution of the script
|
||||
*/
|
||||
if (!defined('DCMGW_INCLUDE')) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
|
||||
die;
|
||||
}
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
// Configuration: Generic Dicom Storage SCP
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
class PACS
|
||||
{
|
||||
// DICOM setup
|
||||
public $AETitle;
|
||||
public $host;
|
||||
public $port;
|
||||
|
||||
// WADO setup
|
||||
public $wadoProtocol;
|
||||
public $wadoHost;
|
||||
public $wadoPort;
|
||||
public $wadoScript;
|
||||
|
||||
public $lang = 'en_US'; // No need to change
|
||||
public $encoding = 'iso-8859-1'; // May need to change to UTF-8
|
||||
|
||||
function __construct($AETitle, $host, $port) {
|
||||
$this->AETitle = $AETitle;
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
|
||||
$this->wadoProtocol = 'http';
|
||||
$this->wadoHost = $host;
|
||||
$this->wadoPort = $port;
|
||||
}
|
||||
|
||||
public function getDicomServer() {
|
||||
$dicomServer = $this->AETitle.'@'.$this->host.':'.$this->port;
|
||||
return $dicomServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force Dicom Q/R execution under a given locale to deal properly with non-ascii characters
|
||||
*/
|
||||
public function getLocale() {
|
||||
return $this->lang.'.'.$this->encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* In some environments Wado server runs independently of Dicom server
|
||||
*/
|
||||
public function setWado($wadoProtocol, $wadoHost, $wadoPort, $wadoScript) {
|
||||
$this->wadoProtocol = $wadoProtocol;
|
||||
$this->wadoHost = $wadoHost;
|
||||
$this->wadoPort = $wadoPort;
|
||||
$this->wadoScript = $wadoScript;
|
||||
}
|
||||
|
||||
public function setWadoScript($wadoScript) {
|
||||
$this->wadoScript = $wadoScript;
|
||||
}
|
||||
|
||||
public function getUriWado($studyUID, $seriesUID, $objectUID) {
|
||||
// Additional parameters ???
|
||||
// contentType
|
||||
// transferSyntax (UID)
|
||||
|
||||
$codeBase = "{$this->wadoProtocol}://{$this->wadoHost}:{$this->wadoPort}/{$this->wadoScript}?";
|
||||
$queryString = "requestType=WADO&studyUID={$studyUID}&seriesUID={$seriesUID}&objectUID={$objectUID}";
|
||||
$uriWado = $codeBase.$queryString;
|
||||
return $uriWado;
|
||||
}
|
||||
}
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
// Specific subclasses for some popular Dicom servers
|
||||
|
||||
class DCM4CHEE extends PACS {
|
||||
function __construct($AETitle, $host) {
|
||||
$port = '11112';
|
||||
parent::__construct($AETitle, $host, $port);
|
||||
$this->wadoPort = '8080';
|
||||
$this->setWadoScript('wado');
|
||||
}
|
||||
}
|
||||
|
||||
class CONQUEST extends PACS {
|
||||
function __construct($AETitle, $host, $port) {
|
||||
parent::__construct($AETitle, $host, $port);
|
||||
$this->setWadoScript('cgi-bin/dgate.exe');
|
||||
}
|
||||
}
|
||||
|
||||
class CLEARCANVAS extends PACS {
|
||||
function __construct($AETitle, $host, $port) {
|
||||
parent::__construct($AETitle, $host, $port);
|
||||
$this->wadoPort = '1000';
|
||||
$this->setWadoScript('wado/'.$AETitle);
|
||||
}
|
||||
}
|
||||
|
||||
// ******* ********* ********* ********* ********* ********* ********* *********
|
||||
|
||||
3
nv/html/dcmgw/info.php
Normal file
3
nv/html/dcmgw/info.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
phpinfo();
|
||||
18979
nv/html/dcmgw/php_error.log
Normal file
18979
nv/html/dcmgw/php_error.log
Normal file
File diff suppressed because it is too large
Load Diff
32
nv/html/dcmgw/tmp/DEADJOE
Normal file
32
nv/html/dcmgw/tmp/DEADJOE
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
*** These modified files were found in JOE when it aborted on Thu Apr 12 07:54:04 2018
|
||||
*** JOE was aborted because the terminal closed
|
||||
|
||||
*** File '(Unnamed)'
|
||||
while
|
||||
for
|
||||
debug
|
||||
dumpvar
|
||||
port
|
||||
8080
|
||||
8080
|
||||
mysql
|
||||
mysql
|
||||
mysql
|
||||
|
||||
*** File '(Unnamed)'
|
||||
/tmp/ck.log
|
||||
mcserver
|
||||
docs/README.txt
|
||||
mcservice
|
||||
mcservice.vmoptions
|
||||
conf/mirth.properties
|
||||
conf/mirth.properties
|
||||
conf/dbdrivers.xml
|
||||
mcservice
|
||||
mcservice
|
||||
debuglog
|
||||
|
||||
*** File '* Startup Log *'
|
||||
Processing '/etc/joe/joerc'...
|
||||
Finished processing /etc/joe/joerc
|
||||
133137
nv/html/dcmgw/tmp/debuglog
Normal file
133137
nv/html/dcmgw/tmp/debuglog
Normal file
File diff suppressed because it is too large
Load Diff
41513
nv/html/dcmgw/tmp/olddebuglog
Normal file
41513
nv/html/dcmgw/tmp/olddebuglog
Normal file
File diff suppressed because it is too large
Load Diff
73
nv/html/dcmgw/xml/qr_cfind.xml
Normal file
73
nv/html/dcmgw/xml/qr_cfind.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dicom datetime="20130526224447+0200">
|
||||
<!--Query Response #1:-->
|
||||
<response number='1'>
|
||||
<!--Specific Character Set-->
|
||||
<attr tag="00080005" vr="CS" len="0"></attr>
|
||||
<!--Study Date-->
|
||||
<attr tag="00080020" vr="DA" len="8">19991223</attr>
|
||||
<!--Study Time-->
|
||||
<attr tag="00080030" vr="TM" len="6">100545</attr>
|
||||
<!--Accession Number-->
|
||||
<attr tag="00080050" vr="SH" len="4">0001</attr>
|
||||
<!--Query/Retrieve Level-->
|
||||
<attr tag="00080052" vr="CS" len="6">STUDY</attr>
|
||||
<!--Retrieve AE Title-->
|
||||
<attr tag="00080054" vr="AE" len="4">TEST</attr>
|
||||
<!--Modalities in Study-->
|
||||
<attr tag="00080061" vr="CS" len="4">ECG</attr>
|
||||
<!--Study Description-->
|
||||
<attr tag="00081030" vr="LO" len="0"></attr>
|
||||
<!--Patient?s Name-->
|
||||
<attr tag="00100010" vr="PN" len="12">Patient^Test</attr>
|
||||
<!--Patient ID-->
|
||||
<attr tag="00100020" vr="LO" len="8">123-654</attr>
|
||||
<!--Patient?s Birth Date-->
|
||||
<attr tag="00100030" vr="DA" len="8">19440102</attr>
|
||||
<!--Patient?s Sex-->
|
||||
<attr tag="00100040" vr="CS" len="2">M</attr>
|
||||
<!--Study Instance UID-->
|
||||
<attr tag="0020000D" vr="UI" len="22">1.3.6.1.4.1.6018.4.999</attr>
|
||||
<!--Study ID-->
|
||||
<attr tag="00200010" vr="SH" len="6">43288</attr>
|
||||
<!--Number of Study Related Series-->
|
||||
<attr tag="00201206" vr="IS" len="2">1</attr>
|
||||
<!--Number of Study Related Instances-->
|
||||
<attr tag="00201208" vr="IS" len="2">1</attr>
|
||||
</response>
|
||||
<!--Query Response #2:-->
|
||||
<response number='2'>
|
||||
<!--Specific Character Set-->
|
||||
<attr tag="00080005" vr="CS" len="0"></attr>
|
||||
<!--Study Date-->
|
||||
<attr tag="00080020" vr="DA" len="8">19940323</attr>
|
||||
<!--Study Time-->
|
||||
<attr tag="00080030" vr="TM" len="6">115104</attr>
|
||||
<!--Accession Number-->
|
||||
<attr tag="00080050" vr="SH" len="0"></attr>
|
||||
<!--Query/Retrieve Level-->
|
||||
<attr tag="00080052" vr="CS" len="6">STUDY</attr>
|
||||
<!--Retrieve AE Title-->
|
||||
<attr tag="00080054" vr="AE" len="4">TEST</attr>
|
||||
<!--Modalities in Study-->
|
||||
<attr tag="00080061" vr="CS" len="2">US</attr>
|
||||
<!--Study Description-->
|
||||
<attr tag="00081030" vr="LO" len="14">Echocardiogram</attr>
|
||||
<!--Patient?s Name-->
|
||||
<attr tag="00100010" vr="PN" len="10">Rubo DEMO</attr>
|
||||
<!--Patient ID-->
|
||||
<attr tag="00100020" vr="LO" len="12">123-45-6789</attr>
|
||||
<!--Patient?s Birth Date-->
|
||||
<attr tag="00100030" vr="DA" len="8">19231016</attr>
|
||||
<!--Patient?s Sex-->
|
||||
<attr tag="00100040" vr="CS" len="2">F</attr>
|
||||
<!--Study Instance UID-->
|
||||
<attr tag="0020000D" vr="UI" len="16">999.999.3859744</attr>
|
||||
<!--Study ID-->
|
||||
<attr tag="00200010" vr="SH" len="10">027893462</attr>
|
||||
<!--Number of Study Related Series-->
|
||||
<attr tag="00201206" vr="IS" len="2">1</attr>
|
||||
<!--Number of Study Related Instances-->
|
||||
<attr tag="00201208" vr="IS" len="2">1</attr>
|
||||
</response>
|
||||
</dicom>
|
||||
36
nv/html/dcmgw/xml/qr_cfind.xsd
Normal file
36
nv/html/dcmgw/xml/qr_cfind.xsd
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Generated using Flame-Ware Solutions XML-2-XSD v2.0 at http://www.flame-ware.com/Products/XML-2-XSD/ -->
|
||||
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xs:element name="dicom">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="response" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="attr" nillable="true" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent msdata:ColumnName="attr_Text" msdata:Ordinal="3">
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute name="tag" type="xs:string" />
|
||||
<xs:attribute name="vr" type="xs:string" />
|
||||
<xs:attribute name="len" type="xs:string" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="number" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="datetime" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
|
||||
<xs:complexType>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element ref="dicom" />
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
35
nv/html/dcmgw/xml/qr_study.xml
Normal file
35
nv/html/dcmgw/xml/qr_study.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dicom datetime="20130526224718+0200">
|
||||
<!--Query Response #1:-->
|
||||
<response number='1'>
|
||||
<!--Specific Character Set-->
|
||||
<attr tag="00080005" vr="CS" len="0"></attr>
|
||||
<!--Query/Retrieve Level-->
|
||||
<attr tag="00080052" vr="CS" len="6">SERIES</attr>
|
||||
<!--Retrieve AE Title-->
|
||||
<attr tag="00080054" vr="AE" len="4">TEST</attr>
|
||||
<!--Modality-->
|
||||
<attr tag="00080060" vr="CS" len="2">US</attr>
|
||||
<!--Study Instance UID-->
|
||||
<attr tag="0020000D" vr="UI" len="16">999.999.3859744</attr>
|
||||
<!--Series Instance UID-->
|
||||
<attr tag="0020000E" vr="UI" len="16">999.999.94827453</attr>
|
||||
<!--Series Number-->
|
||||
<attr tag="00200011" vr="IS" len="4">5829</attr>
|
||||
</response>
|
||||
<!--Query Response #1 for Query Request #1/1:-->
|
||||
<qresponse number='1' qrequest='1'>
|
||||
<!--SOP Class UID-->
|
||||
<attr tag="00080016" vr="UI" len="28">1.2.840.10008.5.1.4.1.1.3.1</attr>
|
||||
<!--SOP Instance UID-->
|
||||
<attr tag="00080018" vr="UI" len="30">999.999.133.1996.1.1800.1.6.29</attr>
|
||||
<!--Query/Retrieve Level-->
|
||||
<attr tag="00080052" vr="CS" len="6">IMAGE</attr>
|
||||
<!--Retrieve AE Title-->
|
||||
<attr tag="00080054" vr="AE" len="4">TEST</attr>
|
||||
<!--Series Instance UID-->
|
||||
<attr tag="0020000E" vr="UI" len="16">999.999.94827453</attr>
|
||||
<!--Instance Number-->
|
||||
<attr tag="00200013" vr="IS" len="2">28</attr>
|
||||
</qresponse>
|
||||
</dicom>
|
||||
47
nv/html/dcmgw/xml/qr_study.xsd
Normal file
47
nv/html/dcmgw/xml/qr_study.xsd
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Generated using Flame-Ware Solutions XML-2-XSD v2.0 at http://www.flame-ware.com/Products/XML-2-XSD/ -->
|
||||
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xs:element name="attr" nillable="true">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent msdata:ColumnName="attr_Text" msdata:Ordinal="3">
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute name="tag" type="xs:string" />
|
||||
<xs:attribute name="vr" type="xs:string" />
|
||||
<xs:attribute name="len" type="xs:string" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="dicom">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="response" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="attr" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="number" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="qresponse" minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="attr" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="number" type="xs:string" />
|
||||
<xs:attribute name="qrequest" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="datetime" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
|
||||
<xs:complexType>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element ref="attr" />
|
||||
<xs:element ref="dicom" />
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
Reference in New Issue
Block a user