Created
September 21, 2020 18:00
-
-
Save rherrick/e0f19111c02c62e8cb328e75be2e47ca to your computer and use it in GitHub Desktop.
A script that pulls in the XNAT Mizer library and DicomEdit 4 and 6 and applies an anonymization script to DICOM files. Requires Groovy installation. Save this file as Anonymizer.groovy, make it executable (e.g. chmod +x Anonymizer.groovy), then run it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env groovy | |
@GrabConfig(systemClassLoader = true) | |
@GrabResolver(name = "NRG Release Repo", root = "https://nrgxnat.jfrog.io/nrgxnat/libs-release") | |
@GrabResolver(name = "NRG Snapshot Repo", root = "https://nrgxnat.jfrog.io/nrgxnat/libs-snapshot") | |
@Grapes([ | |
@GrabExclude("org.slf4j:slf4j-simple"), | |
@GrabExclude("org.slf4j:slf4j-log4j12"), | |
@Grab("info.picocli:picocli:4.5.1"), | |
@Grab("ch.qos.logback:logback-classic:1.2.3"), | |
@Grab("org.nrg.dicom:mizer:1.2.0-SNAPSHOT"), | |
@Grab("org.nrg.dicom:dicom-edit4:1.0.5-SNAPSHOT"), | |
@Grab("org.nrg.dicom:dicom-edit6:1.2.0-SNAPSHOT"), | |
@Grab("commons-io:commons-io:2.7") | |
]) | |
import ch.qos.logback.classic.Logger | |
import groovy.cli.picocli.CliBuilder | |
import groovy.cli.picocli.OptionAccessor | |
import groovy.io.FileType | |
import org.apache.commons.io.FileUtils | |
import org.apache.commons.io.FilenameUtils | |
import org.dcm4che2.io.DicomCodingException | |
import org.nrg.dcm.edit.mizer.DE4Mizer | |
import org.nrg.dicom.dicomedit.mizer.DE6Mizer | |
import org.nrg.dicom.mizer.exceptions.MizerException | |
import org.nrg.dicom.mizer.service.MizerContext | |
import org.nrg.dicom.mizer.service.MizerService | |
import org.nrg.dicom.mizer.service.impl.BaseMizerService | |
import org.nrg.dicom.mizer.service.impl.MizerContextWithScript | |
import org.nrg.transaction.TransactionException | |
import java.nio.file.Paths | |
import static ch.qos.logback.classic.Level.WARN | |
import static org.slf4j.Logger.ROOT_LOGGER_NAME as ROOT | |
import static org.slf4j.LoggerFactory.getLogger | |
((Logger) getLogger(ROOT)).setLevel(WARN) | |
static void anonymize(final MizerService mizer, final MizerContext context, final File incoming, final File outgoing, final boolean verbose) { | |
final File output = outgoing == null ? new File(incoming.getAbsolutePath() + ".temp") : outgoing | |
if (verbose) { | |
println "Anonymizing incoming file ${incoming} into destination ${output}" | |
} | |
FileUtils.copyFile(incoming, output) | |
try { | |
mizer.anonymize(output, context) | |
if (outgoing == null) { | |
FileUtils.deleteQuietly(incoming) | |
FileUtils.moveFile(output, incoming) | |
} | |
} catch (MizerException e) { | |
if (e.getCause() != null && e.getCause() instanceof TransactionException && e.getCause().getCause() instanceof IOException && e.getCause().getCause().getCause() instanceof DicomCodingException && e.getCause().getCause().getCause().getMessage().endsWith("Not a DICOM Stream")) { | |
System.err.println("Skipping file ${incoming.name}, not recognized as valid DICOM") | |
FileUtils.deleteQuietly(output) | |
} else { | |
throw e | |
} | |
} | |
} | |
static File getOutputFile(final File incoming) { | |
def parent = incoming.getParentFile() | |
def parentPath = parent ? parent.toPath() : Paths.get("") | |
parentPath.resolve(FilenameUtils.getBaseName(incoming.getName()) + "-anon.dcm").toFile() | |
} | |
final def cli = new CliBuilder(usage: "groovy Anonymizer [option]") | |
cli.s(longOpt: "script", args: 1, "Anonymization script to be processed", type: String.class, required: true) | |
cli.f(longOpt: "file", args: 1, "DICOM file to be anonymized", type: String.class, required: false) | |
cli.d(longOpt: "directory", args: 1, "Directory containing DICOM files to be anonymized", type: String.class, required: false) | |
cli.t(longOpt: "target", args: 1, "Directory where anonymized DICOM files should be placed (defaults to naming output file(s) <filename>-anon.dcm unless -i or --in-place option is specified)", type: String.class, required: false) | |
cli.i(longOpt: "in-place", "Indicates that anonymized DICOM files should overwrite the original source DICOM files", required: false) | |
cli.v(longOpt: "verbose", "Indicates this script should provide more verbose output", required: false) | |
cli.h(longOpt: "help", "Display usage", required: false) | |
final OptionAccessor options = cli.parse(args) | |
if (!options) { | |
System.exit(255) | |
} | |
if (options.h) { | |
cli.usage() | |
System.exit(0) | |
} | |
if ((!options.f && !options.d) || (options.f && options.d)) { | |
System.err.println "You must specify one and only one of -f/--file or -d/--directory options" | |
cli.usage() | |
System.exit(254) | |
} | |
if (options.t && options.i) { | |
System.err.println "You can't specify both the -t/--target and -i/--in-place options" | |
cli.usage() | |
System.exit(253) | |
} | |
final File source = Paths.get(options.f ?: options.d).toFile() | |
if (!source.exists()) { | |
System.err.println options.f ? "The file indicated by the -f/--file option does not exist: ${source.toFile()}" : "The directory indicated by the -d/--directory option does not exist: ${source.toFile()}" | |
cli.usage() | |
System.exit(252) | |
} | |
if (source.isDirectory() && options.f) { | |
System.err.println "The -f/--file option was specified as ${options.f} but that's a directory, not a file" | |
cli.usage() | |
System.exit(251) | |
} | |
if (source.isFile() && options.d) { | |
System.err.println "The -d/--directory option was specified as ${options.d} but that's a file, not a directory" | |
cli.usage() | |
System.exit(250) | |
} | |
final boolean inPlace = options.i | |
final boolean verbose = options.v | |
final MizerService mizer = new BaseMizerService(Arrays.asList(new DE4Mizer(), new DE6Mizer())) | |
def lines = Paths.get(options.s).toFile().readLines() | |
if (verbose) { | |
println("Read script ${options.s}, found ${lines.size()} lines") | |
} | |
final MizerContext context = new MizerContextWithScript(lines) | |
final File target = options.t ? Paths.get(options.t).toFile() : null | |
if (source.isDirectory()) { | |
println("Processing DICOM in folder ${source}") | |
source.eachFileRecurse(FileType.FILES) { file -> | |
anonymize(mizer, context, file, inPlace ? null : (target ? target.toPath().resolve(source.relativePath(file)).toFile() : getOutputFile(file)), verbose) | |
} | |
} else { | |
println("Processing DICOM in file ${source}") | |
anonymize(mizer, context, source, inPlace ? null : (target ? target : getOutputFile(source)), verbose) | |
} | |
return 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment