Developing DICOM Anonymizer Extensions

From MircWiki
Jump to navigation Jump to search

This article describes how to add functionality to the CTP DICOM Anonymizer through the CTP Plugin mechanism. The intended audience for this article is software engineers who are extending or maintaining the code.

The purpose of an anonymizer extension is to add specialized processing that is not supported by the standard functions in the DICOM Anonymizer, such as a service provided by an external network resource.

Access to extensions is provided through the standard @call function. See the call function for more information.

Prerequisites:

As described in Building an Extension JAR, the best way to deploy CTP extensions is to put them in jars that are placed in the CTP/libraries directory or any of its subdirectories.

1 The AnonymizerExtension Interface

To be recognized as an anonymizer extension, a class must implement two interfaces:

  • org.rsna.ctp.plugin.Plugin
  • org.rsna.ctp.stdstages.anonymizer.dicom.AnonymizerExtension

The best way to implement an AnonymizerExtension is to extend the org.rsna.ctp.plugin.AbstractPlugin class and then implement the single method required by the AnonymizerExtension interface.

The AnonymizerExtension interface provides the call method. The method takes one argument, org.rsna.ctp.stdstages.anonymizer.dicom.FnCall. The FnCall object provides access to the anonymizer script, the context dataset, the element being processed, and all the arguments of the @call instruction in the script being executed.

2 An Example AnonymizerExtension

Here is the code of a test AnonymizerExtension:

package org.test;

import org.rsna.ctp.plugin.AbstractPlugin;
import org.rsna.ctp.stdstages.anonymizer.dicom.AnonymizerExtension;
import org.rsna.ctp.stdstages.anonymizer.dicom.FnCall;
import org.w3c.dom.Element;

/**
 * A test AnonymizerExtension.
 */
public class TestAnonymizerExtension extends AbstractPlugin implements AnonymizerExtension {

    String prefix;

    /**
     * IMPORTANT: When the constructor is called, neither the
     * pipelines nor the HttpServer have necessarily been
     * instantiated. Any actions that depend on those objects
     * must be deferred until the start method is called.
     * @param element the XML element from the configuration file
     * specifying the configuration of the plugin.
     */
    public TestAnonymizerExtension(Element element) {
        super(element);

        //Normally, you would get any configuration parameters
        //from the configuration element here.
        //For this test, we'll get the prefix attribute
        prefix = element.getAttribute("prefix");
    }

    /**
     * Implement the AnonymizerExtension interface
     * @param fnCall the specification of the function call.
     * @return the result of the function call.
     * @throws Exception
     */
    public String call(FnCall fnCall) throws Exception {

        //The fnCall argument contains all the information about the
        //the dataset and the element being processed, as well as the
        //arguments in the @call function in the anonymizer script.

        //The first argument must be the id attribute of the AnonymizerExtension.

        //In this example, we'll get the value of the element being processed,
        //prepend the prefix from the configuration, append the value of the
        //second argument of the fnCall, and return the result.

        String thisElementValue = fnCall.context.contents(fnCall.thisTag);
        String suffix = fnCall.getArg(1);
        return prefix + thisElementValue + suffix;
    }
}

Here is the ant build file to build it:

<project name="TestAnonymizerExtension" default="all" basedir=".">

    <property name="build" value="${basedir}/build"/>
    <property name="libraries" value="${basedir}/libraries"/>
    <property name="products" value="${basedir}/products"/>
    <property name="source" value="${basedir}/source"/>
    <property name="java" value="${source}/java"/>
    <property name="resources" value="${source}/resources"/>
    <property name="ctp" value="../CTP"/>

    <path id="classpath">
        <pathelement location="${libraries}/util.jar"/>
        <pathelement location="${libraries}/CTP.jar"/>
    </path>

    <target name="clean">
        <delete dir="${build}" failonerror="false"/>
    </target>

    <target name="init">
        <mkdir dir="${build}"/>
        <tstamp>
            <format property="today" pattern="yyyy.MM.dd"/>
            <format property="now" pattern="HH:mm:ss"/>
        </tstamp>
        <echo message="Time now ${now}"/>
        <echo message="ant.java.version = ${ant.java.version}" />
        <delete dir="${products}" failonerror="false" />
        <mkdir dir="${build}"/>
        <mkdir dir="${products}"/>
    </target>

    <target name="getLibraryJars">
        <copy overwrite="true" todir="${libraries}">
            <fileset dir="${ctp}/libraries">
                <include name="CTP.jar"/>
                <include name="util.jar"/>
            </fileset>
        </copy>
    </target>

    <target name="jar" depends="init, getLibraryJars">

        <javac destdir="${build}" optimize="on"
                classpathref="classpath"
                includeantruntime="false"
                debug="true" debuglevel="lines,vars,source">
            <src path="${java}"/>
        </javac>

        <copy overwrite="true" todir="${build}">
            <fileset dir="${resources}"/>
        </copy>

        <jar jarfile="${products}/TestAnonymizerExtension.jar">
            <manifest>
                <attribute name="Implementation-Version" value="${today} @ ${now}"/>
            </manifest>
            <fileset dir="${build}" includes="**"/>
        </jar>

    </target>

    <target name="all" depends="clean, jar"/>

</project>

Here is the ConfigurationTemplates.xml file that connects the plugin into the Launcher program's configuration editor, making it easy to insert the plugin into a configuration. Put this file in the resources directory so it will be included in the root of the jar file.

<TemplateDefinitions>

    <Components>

        <Plugin>
            <attr name="name" required="yes" default="TestAnonymizerExtension"/>
            <attr name="class" required="yes" default="org.test.TestAnonymizerExtension" editable="no"/>
            <attr name="id" required="yes" default="ExtID"/>
            <attr name="root" required="yes" default="roots/TestAnonymizerExtension"/>
            <attr name="prefix" required="yes">
                <helptext>The prefix to be prepended to processed elements.</helptext>
            </attr>
        </Plugin>

    </Components>

</TemplateDefinitions>

Here is an example CTP configuration file element deploying the plugin:

<Plugin
    class="org.test.TestAnonymizerExtension"
    id="ExtID"
    name="TestAnonymizerExtension"
    prefix="PREFIX-"
    root="roots/TestAnonymizerExtension"/>

Here is an anonymizer script calling this plugin on the PatientName element:

@call(ExtID,"-SUFFIX")

Here is the result of the execution of the @call function

PREFIX-DOE,JOHN-SUFFIX