Difference between revisions of "Developing DICOM Anonymizer Extensions"

From MircWiki
Jump to navigation Jump to search
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
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.  
 
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. Access to extensions is provided through the standard <b><tt>@call</tt></b> function. See [[The_CTP_DICOM_Anonymizer#.40call.28id.2C_args.29|the call function]] for more information.
+
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 <b><tt>@call</tt></b> function. See [[The_CTP_DICOM_Anonymizer#.40call.28id.2C_args.29|the call function]] for more information.
  
 
Prerequisites:
 
Prerequisites:
Line 11: Line 13:
  
 
==The AnonymizerExtension Interface==
 
==The AnonymizerExtension Interface==
To be recognized as an , a class must implement two interfaces:
+
To be recognized as an anonymizer extension, a class must implement two interfaces:
 
* <b><tt>org.rsna.ctp.plugin.Plugin</tt></b>
 
* <b><tt>org.rsna.ctp.plugin.Plugin</tt></b>
 
* <b><tt>org.rsna.ctp.stdstages.anonymizer.dicom.AnonymizerExtension</tt></b>  
 
* <b><tt>org.rsna.ctp.stdstages.anonymizer.dicom.AnonymizerExtension</tt></b>  
Line 24: Line 26:
 
package org.test;
 
package org.test;
  
import java.io.File;
 
 
import org.rsna.ctp.plugin.AbstractPlugin;
 
import org.rsna.ctp.plugin.AbstractPlugin;
 
import org.rsna.ctp.stdstages.anonymizer.dicom.AnonymizerExtension;
 
import org.rsna.ctp.stdstages.anonymizer.dicom.AnonymizerExtension;
Line 50: Line 51:
 
         //Normally, you would get any configuration parameters
 
         //Normally, you would get any configuration parameters
 
         //from the configuration element here.
 
         //from the configuration element here.
        //...
 
 
         //For this test, we'll get the prefix attribute
 
         //For this test, we'll get the prefix attribute
 
         prefix = element.getAttribute("prefix");
 
         prefix = element.getAttribute("prefix");
Line 71: Line 71:
 
         //In this example, we'll get the value of the element being processed,
 
         //In this example, we'll get the value of the element being processed,
 
         //prepend the prefix from the configuration, append the value of the
 
         //prepend the prefix from the configuration, append the value of the
         //seconde argument of the fnCall, and return the result.
+
         //second argument of the fnCall, and return the result.
  
 
         String thisElementValue = fnCall.context.contents(fnCall.thisTag);
 
         String thisElementValue = fnCall.context.contents(fnCall.thisTag);
Line 149: Line 149:
 
</project>
 
</project>
 
</pre>
 
</pre>
Here is the <b><tt>ConfigurationTemplates.xml</tt></b> file that connects the plugin into the Launcher program's configuration editor, making it easy to insert the plugin into a configuration:
+
Here is the <b><tt>ConfigurationTemplates.xml</tt></b> 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 <tt>resources</tt> directory so it will be included in the root of the jar file.
 
<pre>
 
<pre>
 
<TemplateDefinitions>
 
<TemplateDefinitions>
  
<Components>
+
    <Components>
  
<Plugin>
+
        <Plugin>
<attr name="name" required="yes" default="TestAnonymizerExtension"/>
+
            <attr name="name" required="yes" default="TestAnonymizerExtension"/>
<attr name="class" required="yes" default="org.test.TestAnonymizerExtension" editable="no"/>
+
            <attr name="class" required="yes" default="org.test.TestAnonymizerExtension" editable="no"/>
<attr name="id" required="yes" default="ExtID"/>
+
            <attr name="id" required="yes" default="ExtID"/>
<attr name="root" required="yes" default="roots/TestAnonymizerExtension"/>
+
            <attr name="root" required="yes" default="roots/TestAnonymizerExtension"/>
<attr name="prefix" required="yes">
+
            <attr name="prefix" required="yes">
<helptext>The prefix to be prepended to processed elements.</helptext>
+
                <helptext>The prefix to be prepended to processed elements.</helptext>
</attr>
+
            </attr>
</Plugin>
+
        </Plugin>
  
</Components>
+
    </Components>
  
 
</TemplateDefinitions>
 
</TemplateDefinitions>

Latest revision as of 23:17, 27 May 2016

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