Adam,
I think that your answer gave an impression that was somewhat more negative than I would wish to do. What Justin is asking for is actually a very reasonable request and, whilst not possible directly from the REST-API in a single call, is certainly do-able via a combination of REST and Java.
I don't agree with the phrase "let alone DICOM's lexically meaningless book-keeping arcana". Yes, the DICOM UID system can appear rather clumsy at times, but can also be rather useful. In fact, all the required information is already stored in XNAT. Both the StudyInstanceUID and the SeriesInstanceUID are at the places one would expect in the XNAT schema: at xnat:imageSessionData/UID and xnat:imageScanData/UID respectively. To get the SOPInstanceUID's, you have to dig down to the catalog level, but they are there.
I do something very similar to what Justin wants in my data-uploader application. The use-case is as follows. I have the result of some piece of data-processing - doesn't matter what it is - that I wish to upload to the repository and at the same time, I want to parse the significant metadata from the object and store it as a custom qcAssessmentData "object" in the SQL database, However, I disallow the upload if the original image file dependencies that were used to perform the processing are not already present in XNAT.
Justin, below is a code snippet that might help give a rough idea of how I do things. You can't directly use it, because there is a whole infrastructure sitting below it to parse the various XML files that the REST API generates, but it might help a little by showing how I went about doing the REST queries. If you need more of the code, please get in touch. Most of it will get uploaded onto XNAT Marketplace soon.
Hope some of this is useful.
Best wishes,
Simon
/**
* Check that all the relevant studies, series and SOPInstances have been
* loaded into the XNAT database.
* @return true if all studies are present, false if not or on error.
*/
protected void dependenciesInDatabase(XNATProfile xnprf)
throws XNATException, Exception
{
// Normally, the reason we are creating an RTStruct is to upload a given
// file to a particular project and so the given profile will be supplied
// with only one element in the project list. However, this might change ...
XNATProjectID = xnprf.getProjectList().get(0);
String RESTCommand;
XNATRESTToolkit xnrt = new XNATRESTToolkit(xnprf);
Vector2D<String> result;
String[][] parseResult;
Document resultDoc;
XNATNamespaceContext XNATns = new XNATNamespaceContext();
try
{
// Find information on all the studies in the database.
RESTCommand = "/data/archive/projects/" + XNATProjectID + "/experiments"
+ "?xsiType=xnat:imageSessionData"
+ "&columns=xnat:imageSessionData/UID,xnat:imageSessionData/subject_ID"
+ "&format=xml";
result = xnrt.RESTGetResultSet(RESTCommand);
}
catch (XNATException exXNAT)
{
throw exXNAT;
}
// Are all the DICOM studies from the uploaded ROI file present
// in the database? Note that studyUIDs has previously been populated from the
// file we want to upload.
for (int i=0; i<studyUIDs.size(); i++)
{
if (!result.columnContains(1, studyUIDs.get(i)))
throw new Exception("The DICOM study with UID " + studyUIDs.get(i) + "\n"
+ " is referenced by the file you are loading,"
+ " but it is not yet in the database.\n"
+ "Please ensure that all files on which this"
+ " structure set is dependent are already loaded into XNAT.");
// Now check that all the series are present.
int row = result.indexOfForCol(1, studyUIDs.get(i));
String XNATExp = result.atom(0, row);
String XNATSubj = result.atom(2, row);
// Note that we have an incompatibility between the possibility in
// DICOM that the structure set can reference more than one DICOM
// study and the XNAT requirement to archive the structure set file
// with a particular XNAT Experiment (imageSession). Resolve this
// by arbitrarily choosing the first.
if (i == 0)
{
XNATExperimentID = XNATExp;
XNATSubjectID = XNATSubj;
XNATScanID = new ArrayList<String>();
}
try
{
RESTCommand = "/data/archive/projects/" + XNATProjectID
+ "/subjects/" + XNATSubj
+ "/experiments/" + XNATExp
+ "?format=xml";
resultDoc = xnrt.RESTGetDoc(RESTCommand);
parseResult = XMLUtilities.getAttributes(resultDoc, XNATns, "xnat:scan",
new String[] {"ID", "UID"});
}
catch (XNATException exXNAT)
{
throw exXNAT;
}
boolean present = false;
for (int j=0; j<parseResult.length; j++)
{
// Not all of the experiments returned are scans. Some might be
// assessors, with no UID. These need to be screened out.
if (parseResult[j][1] != null)
{
if (parseResult[j][1].equals(seriesUIDs.get(i)))
{
present = true;
XNATScanID.add(parseResult[j][0]);
}
}
}
if (!present)
throw new Exception("The DICOM series with UID " + studyUIDs.get(i) + "\n"
+ " is referenced by the file you are loading,"
+ " but it is not yet in the database.\n"
+ "Please ensure that all files on which this"
+ "structure set is dependent are already loaded into XNAT.\n'n");
}
// Finally, we need a list of the actual data files in the repository
// that are referenced, to go in the "in" section of qcAssessmentData.
// See the Class DICOMFileListWorker for an example of how to do this
// both if the files are local or remote. Here, for simplicity, I don't
// assume anything and use the REST method whether the files are local
// or remote.
fileSOPMap = new HashMap<String, String>();
for (int i=0; i<XNATScanID.size(); i++)
{
try
{
RESTCommand = "/data/archive/projects/" + XNATProjectID
+ "/subjects/" + XNATSubjectID
+ "/experiments/" + XNATExperimentID
+ "/scans/" + XNATScanID.get(i)
+ "/resources/DICOM?format=xml";
resultDoc = xnrt.RESTGetDoc(RESTCommand);
parseResult = XMLUtilities.getAttributes(resultDoc, XNATns, "cat:entry",
new String[] {"URI", "UID"});
}
catch(XNATException exXNAT)
{
throw exXNAT;
}
// Cater for the obscure case where parseResult comes back null. This
// happened to me after I had (manually) screwed up the data repository.
if (parseResult == null)
throw new Exception("There are no relevant DICOM image files. This might be an"
+ " inconsistent condition in the repository and be"
+ " worth investigating further.");
for (int j=0; j<parseResult.length; j++)
{
if (SOPInstanceUIDs.contains(parseResult[j][1]))
fileSOPMap.put(parseResult[j][1], parseResult[j][0]);
}
}
// Retrieve some further demographic information so that it is available
// for output where necessary.
try
{
RESTCommand = "/data/archive/projects/" + XNATProjectID
+ "/subjects/" + XNATSubjectID
+ "?format=xml";
resultDoc = xnrt.RESTGetDoc(RESTCommand);
XNATSubjectLabel = XMLUtilities.getAttribute(resultDoc, XNATns,
"xnat:Subject", "label")[0];
XNATDateOfBirth = XMLUtilities.getElementText(resultDoc, XNATns, "xnat:dob")[0];
XNATGender = XMLUtilities.getElementText(resultDoc, XNATns, "xnat:gender")[0];
}
catch(XNATException exXNAT)
{
throw exXNAT;
}