Files
ADAndor/documentation/NDPluginProcess.html
2010-03-22 17:31:37 +00:00

1019 lines
29 KiB
HTML
Executable File

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>areaDetector Plugin NDPluginProcess</title>
<meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type" />
</head>
<body>
<div style="text-align: center">
<h1>
areaDetector Plugin NDPluginProcess</h1>
<h2>
March 20, 2010</h2>
<h2>
Mark Rivers</h2>
<h2>
University of Chicago</h2>
</div>
<h2>
Contents</h2>
<ul>
<li><a href="#Overview">Overview</a></li>
<li><a href="#Configuration">Configuration</a></li>
<li><a href="#Screens">Screen shots</a></li>
</ul>
<h2 id="Overview">
Overview
</h2>
<p>
NDPluginProcess performs arithmetic processing on NDArray data. It performs the
following operations in the order listed. Each of these operations can be individually
enabled and disabled.
</p>
<ol>
<li>Subtracts a background array which has been previously acquired.</li>
<li>Divides by a flat field array which has been previously acquired, and then multiplies
by a flat field scale factor.</li>
<li>Multiplies by a scale factor and adds an offset.</li>
<li>Clips to a minimum specified value.</li>
<li>Clips to a minimum specified value.</li>
<li>Applies a recursive digital filter.</li>
<li>Converts to the specified output data type.</li>
<li>Exports the processed data as a new NDArray object.</li>
</ol>
<p>
If any of the above operations is enabled, then the array is first converted to
NDFloat64 data type, i.e. double-precision float. The operations are all performed
in double-precision, and then the array is converted to the specified output data
type.
</p>
<p>
NDPluginProcess is both a <b>recipient</b> of callbacks and a <b>source</b> of NDArray
callbacks. This means that other plugins, such the NDPluginStdArrays, NDPluginStats,
and NDPluginFile plugins can be connected to an NDPluginProcess plugin, in which
case they will use the processed data.
</p>
<p>
NDPluginProcess is fully N-dimensional. It can be used for 2-D images, 3-D (color)
images, or any type of N-dimensional data.
</p>
<p>
NDPluginProcess inherits from NDPluginDriver. The <a href="areaDetectorDoxygenHTML/class_n_d_plugin_process.html">
NDPluginProcess class documentation</a> describes this class in detail.
</p>
<p>
NDPluginProcess.h defines the following parameters. It also implements all of the
standard plugin parameters from <a href="pluginDoc.html#NDPluginDriver">NDPluginDriver</a>.
The EPICS database NDProcess.template provide access to these parameters, listed
in the following table. Note that to reduce the width of this table the enum names
have been split into 2 lines, but these are just a single name, for example <code>
NDPluginProcessSaveBackground</code>.
</p>
<table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
<tbody>
<tr>
<td align="center" colspan="7,">
<b>Parameter Definitions in NDPluginProcess.h and EPICS Record Definitions in NDProcess.template</b></td>
</tr>
<tr>
<th>
Parameter index variable</th>
<th>
asyn interface</th>
<th>
Access</th>
<th>
Description</th>
<th>
drvUser string</th>
<th>
EPICS record name</th>
<th>
EPICS record type</th>
</tr>
<tr>
<td align="center" colspan="7,">
<b>Background subtraction</b></td>
</tr>
<tr>
<td>
NDPluginProcess<br />
SaveBackground</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Command to use the most recently acquired array as a background. Note that this
recently acquired array should have been acquired with EnableBackground=0, or else
that array will already have had the background subtracted, which is probably not
what was intended!</td>
<td>
SAVE_BACKGROUND</td>
<td>
$(P)$(R)SaveBackground<br />
$(P)$(R)SaveBackground_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
ValidBackground</td>
<td>
asynInt32</td>
<td>
r/o</td>
<td>
Flag indicating whether there is a valid background array that has been acquired
for this array using SaveBackground. This flag will be Invalid (0) if no background
has been acquired, or if the size of the array has changed since the background
was last acquired.</td>
<td>
VALID_BACKGROUND</td>
<td>
$(P)$(R)ValidBackground_RBV</td>
<td>
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
EnableBackground</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Flag indicating whether the background array acquired with SaveBackground should
be subtracted when processing the array. If ValidBackground=0 then no background
subtraction is done even if EnableBackground=Enable.</td>
<td>
ENABLE_BACKGROUND</td>
<td>
$(P)$(R)EnableBackground<br />
$(P)$(R)EnableBackground_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td align="center" colspan="7,">
<b>Flat field normalization</b></td>
</tr>
<tr>
<td>
NDPluginProcess<br />
SaveFlatField</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Command to use the most recently acquired array as a flat field. Note that this
recently acquired array should have been acquired with EnableFlatField=0, or else
that array will already have been flat field normalized, which is probably not what
was intended!</td>
<td>
SAVE_FLAT_FIELD</td>
<td>
$(P)$(R)SaveFlatField<br />
$(P)$(R)SaveFlatField_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
ValidFlatField</td>
<td>
asynInt32</td>
<td>
r/o</td>
<td>
Flag indicating whether there is a valid flat field array that has been acquired
for this array using SaveFlatField. This flag will be Invalid (0) if no flat field
has been acquired, or if the size of the array has changed since the flat field
was last acquired.</td>
<td>
VALID_FLAT_FIELD</td>
<td>
$(P)$(R)ValidFlatField_RBV</td>
<td>
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
EnableFlatField</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Flag indicating whether the array should be divided by the flat field array (acquired
with SaveFlatField) when processing the array. If ValidFlatField=0 then no flat
field normalization is done even if EnableBackground=Enable. The processing step
consists of:
<br />
Array = Array / FlatField * ScaleFlatField</td>
<td>
ENABLE_FLAT_FIELD</td>
<td>
$(P)$(R)EnableFlatField<br />
$(P)$(R)EnableFlatField_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
ScaleFlatField</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
The scale factor to multiply by after dividing the array by the flat field array.
This scale factor is normally chosen so that the data after scaling fills the dynamic
range of the output data type.
</td>
<td>
SCALE_FLAT_FIELD</td>
<td>
$(P)$(R)ScaleFlatField<br />
$(P)$(R)ScaleFlatField_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td align="center" colspan="7,">
<b>Scaling and offset</b></td>
</tr>
<tr>
<td>
NDPluginProcess<br />
EnableScaleOffset</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Flag indicating whether the array should be multiplied by Scale and then summed
with Offset when processing the array. The processing step consists of:
<br />
Array = Array * Scale + Offset</td>
<td>
ENABLE_SCALE_OFFSET</td>
<td>
$(P)$(R)EnableScaleOffset<br />
$(P)$(R)EnableScaleOffset_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
Scale</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
The scale factor to multiply by.
</td>
<td>
SCALE</td>
<td>
$(P)$(R)Scale<br />
$(P)$(R)Scale_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
Offset</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
The offset to add.
</td>
<td>
OFFSET</td>
<td>
$(P)$(R)Offset<br />
$(P)$(R)Offset_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td align="center" colspan="7,">
<b>Low and high clipping</b></td>
</tr>
<tr>
<td>
NDPluginProcess<br />
EnableLowClip</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Flag to control whether to clip values to the LowClip value for this array (0=Disable,
1=Enable).
</td>
<td>
ENABLE_LOW_CLIP</td>
<td>
$(P)$(R)EnableLowClip<br />
$(P)$(R)EnableLowClip_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
LowClip</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
The minimum allowed value for this array. If EnableLowClip=1, then all values in
the array less than LowClip will be replaced by LowClip.
</td>
<td>
LOW_CLIP</td>
<td>
$(P)$(R)LowClip<br />
$(P)$(R)LowClip_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
EnableHighClip</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Flag to control whether to clip values to the HighClip value for this array (0=Disable,
1=Enable).
</td>
<td>
ENABLE_HIGH_CLIP</td>
<td>
$(P)$(R)EnableHighClip<br />
$(P)$(R)EnableHighClip_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
HighClip</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
The maximum allowed value for this array. If EnableHighClip=1, then all values in
the array greater than HighClip will be replaced by HighClip.
</td>
<td>
HIGH_CLIP</td>
<td>
$(P)$(R)HighClip<br />
$(P)$(R)HighClip_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
DataType</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Data type of the output array (NDDataType_t). This can be different from the data
type of the NDArray callback data.</td>
<td>
PROCESS_DATA_TYPE</td>
<td>
$(P)$(R)DataTypeOut<br />
$(P)$(R)DataTypeOut_RBV</td>
<td>
mbbo<br />
mbbi</td>
</tr>
<tr>
<td align="center" colspan="7,">
<b>Recursive filter</b></td>
</tr>
<tr>
<td>
NDPluginProcess<br />
EnableFilter</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Flag indicating whether the array should be processed with a recursive filter. The
details of the filter operation are explained below.<br />
</td>
<td>
ENABLE_FILTER</td>
<td>
$(P)$(R)EnableFilter<br />
$(P)$(R)EnableFilter_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
ResetFilter</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
Command to reset the filter back to its initial state.</td>
<td>
RESET_FILTER</td>
<td>
$(P)$(R)ResetFilter<br />
$(P)$(R)ResetFilter_RBV</td>
<td>
bo<br />
bi</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
NumFilter</td>
<td>
asynInt32</td>
<td>
r/w</td>
<td>
The characteristic number of arrays to use when filtering. The value of NumFiltered
will increase as each array is processed, until it reaches the value of NumFilter,
when it will no longer increase. The value of NumFiltered is used in the filter
equations, as explained below.
</td>
<td>
NUM_FILTER</td>
<td>
$(P)$(R)NumFilter<br />
$(P)$(R)NumFilter_RBV</td>
<td>
longout<br />
longin</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
NumFiltered</td>
<td>
asynInt32</td>
<td>
r/o</td>
<td>
The number of arrays that have been processed by the filter since the filter was
last reset. The value of NumFiltered is incremented as each array is processed,
until it reaches the value of NumFilter, when it will cease incrementing. The value
of NumFiltered is used in the filter equations, as explained below.
</td>
<td>
NUM_FILTERED</td>
<td>
$(P)$(R)NumFiltered_RBV</td>
<td>
longin</td>
</tr>
<tr>
<td>
N.A.</td>
<td>
N.A.</td>
<td>
r/w</td>
<td>
The filter type, chosen from a predefined list, as described below.
</td>
<td>
N.A.</td>
<td>
$(P)$(R)FilterType</td>
<td>
mbbo</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
OOffset</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Output offset coefficient.
</td>
<td>
FILTER_OOFFSET</td>
<td>
$(P)$(R)OOffset<br />
$(P)$(R)OOffset_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
OScale</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Output scale coefficient.
</td>
<td>
FILTER_OSCALE</td>
<td>
$(P)$(R)OScale<br />
$(P)$(R)OScale_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
OC1</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Output coefficient #1.
</td>
<td>
FILTER_OC1</td>
<td>
$(P)$(R)OC1<br />
$(P)$(R)OC1_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
OC2</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Output coefficient #2.
</td>
<td>
FILTER_OC2</td>
<td>
$(P)$(R)OC2<br />
$(P)$(R)OC2_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
OC3</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Output coefficient #3.
</td>
<td>
FILTER_OC3</td>
<td>
$(P)$(R)OC3<br />
$(P)$(R)OC3_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
OC4</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Output coefficient #4.
</td>
<td>
FILTER_OC4</td>
<td>
$(P)$(R)OC4<br />
$(P)$(R)OC4_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
FOffset</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter offset coefficient.
</td>
<td>
FILTER_FOFFSET</td>
<td>
$(P)$(R)FOffset<br />
$(P)$(R)FOffset_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
FScale</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter scale coefficient.
</td>
<td>
FILTER_FSCALE</td>
<td>
$(P)$(R)FScale<br />
$(P)$(R)FScale_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
FC1</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter coefficient #1.
</td>
<td>
FILTER_FC1</td>
<td>
$(P)$(R)FC1<br />
$(P)$(R)FC1_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
FC2</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter coefficient #2.
</td>
<td>
FILTER_FC2</td>
<td>
$(P)$(R)FC2<br />
$(P)$(R)FC2_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
FC3</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter coefficient #3.
</td>
<td>
FILTER_FC3</td>
<td>
$(P)$(R)FC3<br />
$(P)$(R)FC3_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
FC4</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter coefficient #4.
</td>
<td>
FILTER_FC4</td>
<td>
$(P)$(R)FC4<br />
$(P)$(R)FC4_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
ROffset</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Reset offset coefficient.
</td>
<td>
FILTER_ROFFSET</td>
<td>
$(P)$(R)ROffset<br />
$(P)$(R)ROffset_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
RC1</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter coefficient #1.
</td>
<td>
FILTER_RC1</td>
<td>
$(P)$(R)RC1<br />
$(P)$(R)RC1_RBV</td>
<td>
ao<br />
ai</td>
</tr>
<tr>
<td>
NDPluginProcess<br />
RC2</td>
<td>
asynFloat64</td>
<td>
r/w</td>
<td>
Filter coefficient #2.
</td>
<td>
FILTER_RC2</td>
<td>
$(P)$(R)RC2<br />
$(P)$(R)RC2_RBV</td>
<td>
ao<br />
ai</td>
</tr>
</tbody>
</table>
<h3>
Recursive filter implementation</h3>
<p>
The recursive filter performs filtering in the time (not spatial) domain. It is
implemented in a fairly general manner, so that a variety of filters can be implemented.
These include integrating filters and differentiating filter types. The recursive
filter stores one "filter" array internally. Using this internal filter array, and
a new input array, it computes an output array, and a new version of the filter
array. The equations governing the output array and new filter array are:
</p>
<pre>
O[n] = OOffset + OScale*((OC1 + OC2/N)*F[n-1] +
(OC3 + OC4/N)*I[n])
F[n] = FOffset + FScale*((FC1 + FC2/N)*F[n-1] +
(FC3 + FC4/N)*I[n])
On filter reset
F[0] = ROffset + RC1*F[0] + RC2*I[0]
where
I[n] = New input array from callback
I[0] = First input array after a filter reset
F[n-1] = Stored filter array
F[n] = New filter array
F[0] = Current filter array when filter is reset.
May be a copy of I[0] if there was no valid filter array.
N = Current value of NumFiltered
O[n] = Output array passed to clients
</pre>
<h3>
Predefined filters</h3>
<p>
The NDProcess.template database implements the following predefined filters using
the $(P)$(R)FilterType record. The implementation of these predefined filter types
is done entirely in the database, not in the plugin code. The database simply downloads
values of OC1-OC4, FC1-FC4, and RC1-RC2 that result in predefined filter behaviours.
The operation of the recursive filter is by no means limited to these fixed filter
types, they are simply provided as a convenience, and can be easily modified or
extended. Note that the database does not download values of OOffset, OScale, FOffset,
FScale, or ROffset, so these remain unchanged when a new filter is selected. The
equations below do not include these offset and scale values, but they are useful
in many circumstances.</p>
<p>
The NDProcess_settings.req file for save/restore saves the values of all of the
filter coefficients, and downloads them when EPICS initialized at iocInit. It also
saves the FilterType, but does not process this record at iocInit, so that any customized
filter coefficients will be preserved, and will not be replaced by the defaults
for that filter type.
</p>
<h4>
Recursive Average</h4>
<p>
The recursive average filter does input array averaging. It is defined as:</p>
<pre>
O[n] = F[n] = (1-1/N)*F[n-1] + 1/N*I[n]
</pre>
<p>
N is the characteristic number of arrays in the average. For example, if N=100,
then each new array is weighted by 0.01 and the previous filter value is weighted
by 0.99. This is an infinite impulse response (IIR) filter, because the effect of
one array never complelely dissappears, but rather decays exponentially. When this
filter is reset the filter array is initialized with the first input array. This
filter can be used to decrease the noise and increase the dynamic range of a repetitive
input signal with a small signal. In that case it can be useful to use set EnableScaleOffset=Enable
and set Scale to a large enough number to more fully use the dynamic range of the
output data type. The averaged signal will then better use the dynamic range of
the output data type.
</p>
<h4>
Recursive Sum</h4>
<p>
The recursive average filter does input array summing. It is defined as:</p>
<pre>
O[n] = F[n] = F[n-1] + 1/N*I[n]
</pre>
<p>
N is the characteristic number of arrays in the sum. For example, if N=100, then
each new array is weighted by 0.01. When this filter is reset the filter array is
initialized to 0. This filter typically cannot be run forever, because the output
grows monotonically and will lead to overflow.
</p>
<h4>
Difference</h4>
<p>
The difference filter computes the difference between frame N and frame N-1. It
is defined as:</p>
<pre>
O[n] = -F[n-1] + I[n]
F[n] = I[n]
</pre>
<p>
Note that the difference will often be a small signed number. If the output datatype
is unsigned, or if a display client expects unsigned numbers, then it can be useful
to set OOffset to a non-zero value to add an offset to the result. For example,
if the output data type is UInt8 (unsigned 8-bit), then setting OOffset to 128 will
produce an unsigned image centered at 128. Similarly OScale can be used to increase
the range of the difference by multiplying by a factor greater than 1, although
this will not increase the dynamic range of the result, only the absolute range.
</p>
<h4>
Recursive Average Difference</h4>
<p>
This filter computes the difference between frame N and the recursive average of
previous frames. It is a combination of the Difference and Recursive Average filters
described above. The new filter is a recursive average, but the output is the difference
between the current frame and that recursive average. It is defined as:</p>
<pre>
O[n] = -F[n-1] + I[n]
F[n] = (1-1/N)*F[n-1] + 1/N*I[n]
</pre>
<p>
N is again the characteristic number of arrays in the average.
</p>
<h4>
Copy to Filter</h4>
<p>
This filter simply copies the input array to the filter array and the output array.
It is defined as:</p>
<pre>
O[n] = F[n] = I[n]
</pre>
<p>
This filter can be used to load the filter (F) with a certain array, which is then
subsequently used for some other filter type.</p>
<h2 id="Configuration">
Configuration</h2>
<p>
The NDPluginProcess plugin is created with the NDProcessConfigure command, either
from C/C++ or from the EPICS IOC shell.</p>
<pre>
NDProcessConfigure(const char *portName, int queueSize, int blockingCallbacks,
const char *NDArrayPort, int NDArrayAddr,
int maxBuffers, size_t maxMemory,
int priority, int stackSize)
</pre>
<p>
For details on the meaning of the parameters to this function refer to the detailed
documentation on the NDProcessConfigure function in the <a href="areaDetectorDoxygenHTML/_n_d_plugin_process_8cpp.html">
NDPluginProcess.cpp documentation</a> and in the documentation for the constructor
for the <a href="areaDetectorDoxygenHTML/class_n_d_plugin_process.html">NDPluginProcess
class</a>.
</p>
<h2 id="Screens">
Screen shots</h2>
<p>
The following is the MEDM screen that provides access to the parameters in NDPluginDriver.h
and NDPluginProcess.h through records in NDPluginBase.template and NDProcess.template. In this
example the input image is first scaled by 35 and offset by -110. The image is then run through
a recursive averaging filter with N=100.
</p>
<div style="text-align: center">
<h3>
NDProcess.adl</h3>
<img alt="NDProcess.png" src="NDProcess.png" />
</div>
<p>
Image collected with 30 microsecond exposure time, so it is very noisy. This is
the image output from the NDPlugProcess plugin with Scale and Offset as shown above,
but with the recursive filter disabled.</p>
<div style="text-align: center">
<img alt="NDProcess_unfilted.jpg" src="NDProcess_unfiltered.jpg" />
</div>
<p>
NDPluginStats plugin connected to the NDPluginProcess filter for the noisy image
above. Note that there are only 6 non-zero intensities in the histogram, because
the brightest pixels are only 6 A/D units above background.</p>
<div style="text-align: center">
<img alt="NDStats_unfiltered.png" src="NDStats_unfiltered.png" />
</div>
<p>
Same image collected with 30 microsecond exposure time. This is the image output
from the NDPlugProcess plugin with Scale and Offset as shown above, with the recursive
filter enabled. Note the dramatic improvement in signal to noise.</p>
<div style="text-align: center">
<img alt="NDProcess_filtered.jpg" src="NDProcess_filtered.jpg" />
</div>
<p>
NDPluginStats plugin connected to the NDPluginProcess filter for the filtered image
above. Note that there are many non-zero intensities in the histogram, because the
filtering is improving the dynamic range significantly.</p>
<div style="text-align: center">
<img alt="NDStats_filtered.png" src="NDStats_filtered.png" />
</div>
</body>
</html>