Triana Help Index
Triana's Spectral Storage Model
Index
Triana
and Frequency Data
Many of Triana's data types implement the
Spectral interface, which requires a number of methods for storing and
retrieving spectral data. The Spectral interface makes no assumptions about
how the underlying data sets are stored. It provides continuity of operation
of units that deal with spectral data even if they employ different storage
models. The spectral data types provide special functions that return spectral
data and their associated frequencies in monotonically increasing frequency
order, so that the spectra can be displayed by a unit that does not have
to know the details of the internal storage model. These functions are
used by Triana's graphing units Grapher
and Multigraph.
However, the details of how spectral data are stored are important to
users implementing data types and dealing directly with the data. The Triana
storage model is close to other common formats for spectral data storage,
and it allows for efficient use of memory. The model is described in detail
here and in the Triana JavaDoc help files
for the types ComplexSpectrum
and Spectrum.
Understanding Frequency-Domain Data Storage
in
Triana
Triana stores spectral data in data types
like Spectrum,
ComplexSpectrum,
TimeFrequency,
and Spectrum2D.
Spectrum and ComplexSpectrum are essentially identical except that the
data in Spectrum are real and in ComplexSpectrum complex. TimeFrequency
is a two-dimensional data set, one dimension of which is spectral and the
other time, so that it normally represents a sequence of spectra taken
at different times. Spectrum2D is a genuine two-dimensional spectrum, such
as might be obtained by performing an FFT on an image or other matrix.
Both TimeFrequency and Spectrum2D can hold either real or complex data.
Since FFTs generally produce complex output, the type Spectrum is normally
used only for the output of units the produce power spectra or related
objects.
One-dimensional spectral data (including the spectral dimension
of TimeFrequency) can generally be stored in several different ways.
-
Full-bandwidth data storage means that the spectrum contains
data values for both positive and negative frequencies.
-
One-sided data storage means that only positive frequencies
are held in the data object; if negative frequencies are required they
are reconstructed from the positive frequencies, on the assumption that
the spectrum represents the Fourier transform of a real data set.
-
Narrow-band data storage means that the stored data represents
only a portion of the full spectrum; if a full-bandwidth spectrum is required
then it is reconstructed by putting zeros into the data values associated
with frequencies outside the stored bandwidth.
Narrow-band data sets can be either one-sided or two-sided. Similarly one-sided
data sets can be either wide-band or narrow-band. Narrow-band data sets
keep a memory of how large the spectrum was from which they were filtered.
This amounts effectively to remembering the highest frequency contained
in that original spectrum, which is called the Nyquist
frequency of the data set. This term is used in the filtering units,
some of which offer the user the ability to reduce the Nyquist frequency.
This means that the "memory" about the original spectrum will be altered
so that, when the set is restored to a full-spectrum set, the highest frequency
will be smaller. If the spectrum is FFT'd back to the time domain, then
the sampling rate in the time domain will be correspondingly smaller, since
this sampling rate is exactly twice the Nyquist frequency. Thus, lowering
the Nyquist frequency will result in down-sampling of the time-domain
data set associated with the spectrum.
Triana employs these various storage models
in order to save memory usage. In many applications, such as when spectra
are divided into separate bandwidths, using full-bandwidth data would mean
replicating many useless storage locations. However, because Triana
data sets are self-describing, most Triana
units that deal with spectra are written in such a way that they will automatically
do the right thing with data, regardless of its storage format. Users need
to be aware of the different storage models, however, when they display
them, combine them, or make use of their special properties. There is more
detail on this subject in the help file for the
Triana
Spectral Model, which also defines the model completely.
For two-dimensional spectral data, narrow-banding in each dimension
makes sense, but one-sidedness has limited value, since data can be reflected
through two different zero-frequency lines. Therefore Triana
generally uses only full-bandwidth 2D data sets.
Storage Details
The details of the storage of spectral data depend on the number nFull
of
points in the full-bandwidth spectrum associated with a spectral data set,
and on whether the data are one-sided and/or narrow-band. The different
cases are detailed here. In the discussions we use f to denote frequency,
fN
to denote the Nyquist frequency (the highest absolute
value of the frequency that the spectrum could contain), df to denote
the frequency resolution (difference of frequency between adjacent points
in the spectrum), and flow and fhigh
to denote the lowest and highest absolute values of the frequency that
are actually contained in the stored data set. The discussion below covers
four possible cases: Full-spectrum, one-sided/full-bandwidth,
two-sided/narrow-band, and one-sided/narrow-band
data.
Full-spectrum (two-sided, full-bandwidth)
data storage. The data are two-sided (include negative frequencies)
and full-bandwidth (flow = 0, fhigh= fN).
The data are stored so that the positive-frequency elements are first,
followed by the negative-frequency elements. The highest frequency is associated
only with the negative-frequency band if nFull is even. Here is
a sketch of the data storage:
if nFull is even then fN = (nFull/2)df and
the stored frequencies are
0, df, 2df,
..., fN - df, -fN , -fN +
df, ..., -df.
If nFull is odd then fmax = fN = (nFull-1)/2
* df and the stored frequencies are
0, df, 2df,
..., fN , -fN , -fN + df,
..., -df.
If the spectrum is a Fourier transform of a real time-domain data set,
then there are symmetries. In both above cases, the amplitude at frequency
fj
is the complex conjugate of that at -fj . In case nFull
is even, this also implies that the amplitudes for
f=0 and for -fN
are
real. If nFull is odd, then only the amplitude for
f=0 need
be real.
One-sided, full-bandwidth data storage.
Here only the non-negative frequencies are stored but the frequency domain
is full-band (flow = 0, fhigh =
fN). If negative frequencies need to be reconstructed, they
are assumed to follow the rule above for complex-conjugates. The Triana
storage model does not, however, simply leave off the negative frequencies
of the full-spectrum storage scheme. In the case where nFull is
even, the highest frequency is in the negative-frequency range; in this
case, when data are made one-sided, the amplitude for -fN
is stored at a new frequency value +fN . The storage
scheme is thus:
0, df, 2df, ..., fN .
If a two-sided spectrum derived from a complex time-series data
set is converted to one-sided, information will be lost. However, when
the two-sided spectrum is derived from a real time-series data set, then
the negative-frequency information is redundant, and the Triana one-sided
model is an efficient way to store all the information in the spectrum.
In this case the amplitudes at f=0 and f = fN are
real.
Narrow-band two-sided data storage.
If a spectrum has been filtered, then only some data points may be
non-zero. The Triana storage model allows the zeros to be omitted if the
non-zero part of the spectrum is fully contained between two frequencies
flow and fhigh. When the data are two-sided,
then the amplitudes associated with the negative frequencies must also
be stored. The arrangement depends in detail on whether nFull is
even or odd, on whether f=0 is included in the band, and on whether
n = the length of the narrow-band data set is even or odd. The basic
assumption is that filtering removes paired frequencies: if any positive-frequency
amplitude is removed (set equal to zero), so is the corresponding negative-frequency
amplitude. If a unit performs an operation that does not act in such a
symmetrical way (such as the units NegativeF
and PositiveF, which zero the positive and
negative frequency amplitudes, respectively), then it cannot take advantage
of the narrow-band storage model.
If nFull is even, then the frequencies of the two-sided
full-bandwidth spectrum at 0 and -fN are un-paired; in
particular there is no element at +fN.Thus, if the spectrum
has been filtered from the top only (low-pass) or from zero only (high-pass),
the narrow-band spectrum will have an odd number of elements; if it has
been filtered from both ends then it will have an even number of elements.
If nFull is odd, then the full-bandwidth spectrum contains
only one un-paired element at f=0; there are paired elements at
+fN and
-fN.
If it is filtered from
above, then the narrow-band data set likewise has an odd number of elements;
if from below then the result is an even number of elements.
It can be seen from this discussion that if the narrow-band two-sided
data set has an odd number of elements, then it must include either (but
of course not both) f=0 or f=-fN . Conversely,
if it has an even number of elements, they are all paired. So the
storage model for an even number of elements is simple --
If n is even then the stored frequencies are
flow
(>0) , flow + df, flow
+ 2df, ..., fhigh - df, fhigh ,-fhigh
, -fhigh + df, ..., -flow - df., -flow.
That is, first the positive-frequency elements are stored, then the
same number of negative-frequency elements. There are no elements at f=0
or f=-fN . This is independent of whether nFull
is even or odd. If this data set is sent to the units Grapher
or Multigraph, it will
be displayed as two disjoint sets in the positive- and negative-frequency
domains. If displayed as a line-graph, there will be a line between the
highest negative-frequency point and the lowest positive-frequency point.
Do not be disturbed by this line; it just means that there is no data in
the data set between these values.
On the other hand, if n is odd, then the storage will depend
on whether the un-paired element is at f=0 (part of the positive-frequency
domain) or f=-fN (part of the negative-frequency
domain). The two cases are --
If n is odd and f=0 is present (so that flow
= 0 is the un-paired element) then the stored frequencies are
0, df, 2df, ..., fhigh, -fhigh, ..., -df.
If n is odd and f=0 is not present (so that flow
> 0), then fhigh = fN is
the un-paired element and the stored frequencies are
flow
(>0) , flow + df, ..., fN
- df, -fN
, -fN + df, ..., -flow - df., -flow
.
Narrow-band one-sided data storage.
Like the full-bandwidth one-sided case, in this case the storage model
is simpler than in the two-sided case. The frequency range simply extends
from flow and fhigh. It is permissible
to have flow = 0, in which case fhigh <
fN . Alternatively, it is permissible for the last element
to be associated with fhigh , in which case flow
> 0. The stored frequencies are
flow (>0) , flow + df, ..., fhigh
- df, fhigh.
Implementation of the Triana
Spectral Storage Model
In Triana the storage model is implemented
in the data types that implement the Spectral interface. Full details of
the implementation are in the Triana JavaDoc
help files. But a few remarks are appropriate here, because these data
types provide some standard methods that the user can make use of to inspect
the data and decide how the data are stored. These are described here for
users who want to program new units to handle spectral data.
Spectral data types inherit from GraphType, and so they have some standard
methods that can be used to access the data.
-
Object getDataArrayReal(0) and Object getDataArrayImag(0)
return the real and imaginary parts of the data, stored according to the
model described above. They return Objects, that must be cast to double[]
or double[][], depending on whether the returned data are
one- or two-dimensional.
-
boolean isDependentComplex(0) returns true if the data
held in the object is complex, false if it is real.
-
int getDimensionLengths(0) returns the number of elements in the
first dimension of the data. For one-dimensional data like ComplexSpectrum,
this is the length of the data set, the same as the length of the double[]
array returned by getDataArrayReal(0). For TimeFrequency data,
this is the number of time-steps. The number of points in the frequency
dimension is returned by int getDimensionLengths(1).
-
double[] getIndependentScaleReal(0) returns the values of the
frequencies that are associated with the points actually held in the object,
for the one-dimensional types. For TimeFrequency data, this call returns
the time-values associated with the different spectra, while the frequency
values are returned by double[] getIndependentScaleReal(1). These
calls are used by Grapher to set the horizontal scale values when spectra
are displayed.
-
Object getGraphArrayReal(0) and Object getGraphArrayImag(0)
return the data in order of monotonically increasing frequency. These calls
must be cast to the appropriate types, as for getDataArrayReal(0)above.
These calls are used by Grapher to get the vertical values when spectra
a displayed.
In addition, some of the methods required by the Spectral interface can
be used to get the data that is required to decide what kind of spectral
data are stored in a data type. These include the following.
-
int getOriginalN(0), which returns the number of points in the
full spectrum, called nFull above.
-
double getFrequencyResolution(0), which returns the number called
df above.
-
double getLowerFrequencyBound(0), which returns the number called
flow above.
-
double getUpperFrequencyBound(0), which returns the number called
fhigh above.
-
boolean isNarrow(0), which returns true if the data set
is narrow-band, false if full-bandwidth.
-
boolean isTwoSided(), which returns true if the data
set is two-sided, false if one-sided. (No 0 is required in the
argument.)
Moreover, the data types ComplexSpectrum, Spectrum, and TimeFrequency implement
additional methods that can be useful:
-
double getNyquist(), which returns the Nyquist frequency, called
fN
above.
-
double getSamplingRate(), which returns the sampling rate of the
time-series data whose transform might have produced the current spectrum.
Finally, some of these functions have counterparts in Triana
units, which allow users to inspect data types at runtime. These include
the units called FrequencyResolution,
HighFreq, Length, LowFreq,
SampleRate, TestNarrow,
and TestTwoSided.
These accept a data set as input and produce the appropriate value as an
output, which can then be used as input to other units to set parameters,
or which can be displayed.