Sidebar
0 votes
6 views
ago by rich-c-2789 (18.0k points)
This KB provides and example for qie.parseMultipartContentFromFile(...)

1 Answer

0 votes

A DICOMweb WADO-RS retrieve (for example, GET .../studies/{studyUID}) returns a multipart/related body that can contain many DICOM instances. Buffering that whole body in memory does not scale. qie.parseMultipartContentFromFile() lets you stream the response straight to disk and then parse it part-by-part, writing each part to its own file, so the body never has to sit in JVM memory.

The function returns an XML message that lists every part and the on-disk path to its extracted file. Your script then iterates the parts and does whatever it needs with each one — store via DIMSE C-STORE, copy to a network share, forward to another channel, and so on. Although this article is framed around WADO-RS, the function works for any multipart/related body that has been streamed to a file.

When to use it (vs. qie.parseMultipartContent)

qie.parseMultipartContentqie.parseMultipartContentFromFile
InputThe multipart body as an in-memory String or byte[]A file path (String or File) to the streamed body
Where parts goBase64-encoded inline in the XML (<content>)Written to individual files on disk (<path>)
Memory useEntire body buffered in memoryBounded — parts are streamed to disk as they are read
Best forSmall responses already held in memoryLarge WADO-RS retrieves and other big multipart/related bodies

Rule of thumb: if the response could be large (multiple or full-resolution DICOM instances), stream it to disk and use qie.parseMultipartContentFromFile().

Prerequisite: stream the response to disk

This function reads a file, so the upstream Web Service step must stream the response to disk rather than buffer it in memory. In practice that means:

  • Request a multipart/related response (the DICOMweb Accept header), and
  • Pass a streamToFile target to qie.callRESTWebService(...) so the body is written to that path.

If the response is buffered in memory instead, you will get the base64 of the body in /httpResponse/content rather than a file path — and passing that to this function throws (see Troubleshooting).

Walkthrough

The pattern is three steps: stream the body to a file, parse it, then iterate and clean up.

var content = null;

// URL template for the endpoint
var urlTemplate = qie.evaluateTemplate(qie.getWsOperationUrl("All DICOM Web APIs", "studies/{mc:study_uid}"));

var parameterMap = qie.newParameterMap();
parameterMap.put("http.header.Accept", 'multipart/related; type="application/dicom"');

// Step 1: stream a large multipart/related response body to disk so
//         the body never sits in JVM memory.
var tmpName = "/private/tmp/wado-response-" + qie.getUUID();
var responseBodyFile = qie.evaluateTemplate(tmpName + ".bin");

// Call a REST web service using the provided parameters
var value = qie.callRESTWebService(
   "All DICOM Web APIs",      // connection
   urlTemplate,               // url
   "GET",                     // operation
   content,                   // content
   null,                      // contentType
   parameterMap,              // parameters
   60000,                     // timeout
   true,                      // fullResponse
   null,                      // connectTimeout
   false,                     // returnBytes
   null,                      // tls
   responseBodyFile);         // streamToFile

// Step 2: parse the streamed multipart body into per-part files. The XML
//         returned carries the on-disk path to each part.
var partsXml = qie.parseMultipartContentFromFile(responseBodyFile);

// Step 3: iterate the parts, process each one, then clean up.
var partCount = partsXml.getCount('/mtomContent/part');
for (var i = 1; i <= partCount; i++) {
   var partPath = partsXml.getNode('/mtomContent/part[' + i + ']/path');
   var partType = partsXml.getNode('/mtomContent/part[' + i + ']/partType');
   // ... process partPath (e.g. send via DIMSE C-STORE, copy to a share, etc.) ...
   qie.deleteFile(partPath);        // remove the extracted part once consumed
}
qie.deleteFile(tmpName);            // remove the folder parts were extractd too

qie.deleteFile(responseBodyFile);   // remove the streamed response body file

What the function returns

An XML message with one <part> per multipart segment. Each part's name attribute is its Content-ID, <partType> is its Content-Type, and <path> is the absolute path of the extracted file on disk. (Part files are given an extension that matches their MIME type, e.g. .dcm for application/dicom.)

<mtomContent>
   <part name="1.2.840.113619.2.1.1">
      <partType>application/dicom</partType>
      <path>/private/tmp/wado-response-3f2a.../part-1.dcm</path>
   </part>
   <part name="1.2.840.113619.2.1.2">
      <partType>application/dicom</partType>
      <path>/private/tmp/wado-response-3f2a.../part-2.dcm</path>
   </part>
</mtomContent>

Read it with the usual XML message functions:

var partCount = partsXml.getCount('/mtomContent/part');               // how many parts
var firstPath = partsXml.getNode('/mtomContent/part[1]/path');        // first part's file
var firstType = partsXml.getNode('/mtomContent/part[1]/partType');    // first part's MIME type

Controlling where parts are written and how they are named

By default the parts are written to a sibling directory next to the input file (named after the input file with its last extension stripped — e.g. wado-response-{UUID}.bin → wado-response-{UUID}/), and each file is named part-1.{ext}, part-2.{ext}, … You can override both with the optional outputDir and filenamePrefix arguments:

var partsXml = qie.parseMultipartContentFromFile(
   responseBodyFile,                // inputFile
   "/private/tmp/wado-extracted",   // outputDir       - directory to receive the part files
   "study-1-instance"               // filenamePrefix  - files become study-1-instance-1.dcm, -2.dcm, ...
);

Clean up after yourself

The function does not delete anything. The streamed input file is left on disk after parsing, and every extracted part file remains until you remove it. Your script owns the lifecycle of both — call qie.deleteFile(...) on each part once you have consumed it and on the response body file when you are done (as shown in the walkthrough above). Skipping cleanup will leave files accumulating under the stream path.

Troubleshooting

SymptomCause & fix
Error: "inputFile is too long to be a file path… looks like the response was buffered in memory rather than streamed"You passed the response body (base64) instead of a file path. The Web Service mapping function buffered the body in memory because no Stream: path was configured. Set the Stream: field on the Web Service mapping function so the response body is streamed to disk, then pass the resulting responseBodyFile (not /httpResponse/content) to the function.
Error: "inputFile does not point to an existing local file…"The streamed file was never written, usually for the same reason — the response was not actually streamed. Confirm the Stream: field is set on the Web Service mapping function (so callRESTWebService streams the body to responseBodyFile), the configured path resolves to a writable local directory, and the file wasn't removed by an earlier qie.deleteFile cleanup.
Error about NetFile / CIFS pathsThe input (and output) must be a local file path or java.io.File. NetFile / CIFS paths are not supported — copy the body to a local path first.
 

See Also

ago by rich-c-2789 (18.0k points)
edited ago by rich-c-2789
...