Skip to content

Commit

Permalink
Merge pull request #4016 from melissalinkert/dicom-provide-metadata
Browse files Browse the repository at this point in the history
Add API and DICOM-specific implementations for providing supplemental metadata during conversion
  • Loading branch information
dgault authored Dec 5, 2023
2 parents d519601 + caccc63 commit 766577d
Show file tree
Hide file tree
Showing 12 changed files with 1,153 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import loci.formats.meta.IPyramidStore;
import loci.formats.out.DicomWriter;
import loci.formats.ome.OMEPyramidStore;
import loci.formats.out.IExtraMetadataWriter;
import loci.formats.out.TiffWriter;
import loci.formats.services.OMEXMLService;
import loci.formats.services.OMEXMLServiceImpl;
Expand Down Expand Up @@ -132,6 +133,8 @@ public final class ImageConverter {
private boolean precompressed = false;
private boolean tryPrecompressed = false;

private String extraMetadata = null;

private IFormatReader reader;
private MinMaxCalculator minMax;
private DimensionSwapper dimSwapper;
Expand Down Expand Up @@ -267,6 +270,9 @@ else if (args[i].equals("-fill")) {
// allow specifying 0-255
fillColor = (byte) Integer.parseInt(args[++i]);
}
else if (args[i].equals("-extra-metadata")) {
extraMetadata = args[++i];
}
else if (!args[i].equals(CommandLineTools.NO_UPGRADE_CHECK)) {
LOGGER.error("Found unknown command flag: {}; exiting.", args[i]);
return false;
Expand Down Expand Up @@ -671,6 +677,15 @@ else if (w instanceof DicomWriter) {
((DicomWriter) w).setBigTiff(bigtiff);
}
}
if (writer instanceof IExtraMetadataWriter) {
((IExtraMetadataWriter) writer).setExtraMetadata(extraMetadata);
}
else if (writer instanceof ImageWriter) {
IFormatWriter w = ((ImageWriter) writer).getWriter(out);
if (w instanceof IExtraMetadataWriter) {
((IExtraMetadataWriter) w).setExtraMetadata(extraMetadata);
}
}

String format = writer.getFormat();
LOGGER.info("[{}] -> {} [{}]",
Expand Down
7 changes: 7 additions & 0 deletions components/formats-bsd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@
<version>${jxrlib.version}</version>
</dependency>

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>


<!-- xerces is no longer a dependency of metadata-extractor, but it may be required by other BF components -->
<dependency>
<groupId>xerces</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,8 @@ public enum DicomAttribute {

private static final Map<Integer, String[]> dict =
new HashMap<Integer, String[]>();
private static final Map<String, Integer> nameLookup =
new HashMap<String, Integer>();

static {
try (InputStream stream = DicomAttribute.class.getResourceAsStream("dicom-dictionary.txt")) {
Expand All @@ -802,7 +804,9 @@ public enum DicomAttribute {
if (tokens.length > 2) {
tokens[2] = tokens[2].trim();
}
dict.put((int) Long.parseLong(tokens[0], 16), tokens);
int key = (int) Long.parseLong(tokens[0], 16);
dict.put(key, tokens);
nameLookup.put(tokens[1].replaceAll("\\s", "").toLowerCase(), key);
}
}
catch (Exception e) {
Expand Down Expand Up @@ -872,6 +876,10 @@ public static String getDescription(int newTag) {
return null;
}

public static Integer getTag(String description) {
return nameLookup.get(description.toLowerCase());
}

/**
* Lookup the attribute for the given tag.
* May return null if the tag is not defined in our dictionary.
Expand Down
211 changes: 211 additions & 0 deletions components/formats-bsd/src/loci/formats/dicom/DicomJSONProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2023 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/

package loci.formats.dicom;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import loci.common.Constants;
import loci.common.DataTools;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Provide DICOM tags from a file containing JSON.
* Formal JSON schema yet to be determined, but the idea is to accept a hierarchy
* of tags of the form:
*
* {
* "BodyPartExamined": {
* "Value": "BRAIN",
* "VR": "CS",
* "Tag": "(0018,0015)"
* }
* "ContributingEquipmentSequence": {
* "VR": "SQ",
* "Tag": "(0018,a001)",
* "Sequence": {
* "Manufacturer": {
* "Value": "PixelMed",
* "VR": "LO",
* "Tag": "(0008,0070)"
* },
* "ContributionDateTime": {
* "Value": "20210710234601.105+0000",
* "VR": "DT",
* "Tag": "(0018,a002)"
* }
* }
* }
* }
*
* This is similar to JSON examples in https://github.com/QIICR/dcmqi/tree/master/doc/examples,
* but allows for the VR (type) and tag to be explicitly stated, in addition to
* the more human-readable description.
*/
public class DicomJSONProvider implements ITagProvider {

private static final Logger LOGGER =
LoggerFactory.getLogger(DicomJSONProvider.class);

private List<DicomTag> tags = new ArrayList<DicomTag>();

@Override
public void readTagSource(String location) throws IOException {
String rawJSON = DataTools.readFile(location);
try {
JSONObject root = new JSONObject(rawJSON);

for (String tagKey : root.keySet()) {
JSONObject tag = root.getJSONObject(tagKey);
String value = tag.has("Value") ? tag.getString("Value") : null;

Integer intTagCode = lookupTag(tagKey, tag);
DicomVR vrEnum = lookupVR(intTagCode, tag);

DicomTag dicomTag = new DicomTag(intTagCode, vrEnum);
dicomTag.value = value;
LOGGER.debug("Adding tag: {}, VR: {}, value: {}",
DicomAttribute.formatTag(intTagCode), vrEnum, value);
dicomTag.validateValue();

if (vrEnum == DicomVR.SQ && tag.has("Sequence")) {
readSequence(tag, dicomTag);
}

ResolutionStrategy rs = vrEnum == DicomVR.SQ ? ResolutionStrategy.APPEND : ResolutionStrategy.REPLACE;
if (tag.has("ResolutionStrategy")) {
String strategy = tag.getString("ResolutionStrategy");
rs = Enum.valueOf(ResolutionStrategy.class, strategy);
}
dicomTag.strategy = rs;

tags.add(dicomTag);
}
}
catch (JSONException e) {
throw new IOException("Could not parse JSON", e);
}
}

@Override
public List<DicomTag> getTags() {
return tags;
}

private void readSequence(JSONObject rootTag, DicomTag parent) {
JSONObject sequence = rootTag.getJSONObject("Sequence");
for (String key : sequence.keySet()) {
JSONObject tag = sequence.getJSONObject(key);

Integer intTagCode = lookupTag(key, tag);
DicomVR vrEnum = lookupVR(intTagCode, tag);

DicomTag dicomTag = new DicomTag(intTagCode, vrEnum);

if (tag.has("Value")) {
dicomTag.value = tag.get("Value");
}

LOGGER.debug("Adding tag: {}, VR: {}, value: {}", intTagCode, vrEnum, dicomTag.value);
dicomTag.validateValue();

dicomTag.parent = parent;
parent.children.add(dicomTag);

if (vrEnum == DicomVR.SQ && tag.get("Sequence") != null) {
readSequence(tag, dicomTag);
}
}
parent.children.sort(null);
}

/**
* Get the tag code corresponding to the given JSON object.
* If "Tag" is not a defined attribute, lookup the default
* for the object name in the dictionary.
*/
private Integer lookupTag(String tagKey, JSONObject tag) {
Integer intTagCode = null;

if (tag.has("Tag")) {
String[] tagCode = tag.getString("Tag").replaceAll("[()]", "").split(",");

int tagUpper = Integer.parseInt(tagCode[0], 16);
int tagLower = Integer.parseInt(tagCode[1], 16);

intTagCode = tagUpper << 16 | tagLower;
}
else {
intTagCode = DicomAttribute.getTag(tagKey);

if (intTagCode == null) {
throw new IllegalArgumentException(
"Tag not defined and could not be determined from description '" +
tagKey + "'");
}
}

return intTagCode;
}

/**
* Get the VR associated with the given tag code and JSON object.
* If "VR" is not a defined attribute, lookup the default for the
* given tag code in the dictionary.
*/
private DicomVR lookupVR(Integer intTagCode, JSONObject tag) {
DicomVR vrEnum = DicomAttribute.getDefaultVR(intTagCode);
if (tag.has("VR")) {
DicomVR userEnum = DicomVR.valueOf(DicomVR.class, tag.getString("VR"));
if (!vrEnum.equals(userEnum)) {
LOGGER.warn("User-defined VR ({}) for {} does not match expected VR ({})",
userEnum, DicomAttribute.formatTag(intTagCode), vrEnum);
if (userEnum != null) {
vrEnum = userEnum;
}
}
}

return vrEnum;
}



}
Loading

0 comments on commit 766577d

Please sign in to comment.