1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
|
Introduction
------------
ImageVis3D supports some file formats "out of the box". In many cases,
the easiest route to getting your data into ImageVis3D is to write it
out in a format the program can read natively. However, this might not
always be a viable option. This section describes the work involved
in enabling ImageVis3D to convert a new file format into UVF.
Writing a converter is a good choice if you have pre-existing,
single-block data which is not readable by any of the converters which
ship with ImageVis3D.
ImageVis3D Conversion Steps
---------------------------
ImageVis3D's IO scheme is based around a multi-stage paradigm. In
the first stage, converters identify relevant metadata and modify the
data into a raw input stream. Subsequent stages perform tasks such
as endian conversion, computing derived metadata, and generating the
hierarchy of bricks needed for interactive rendering.
Writing Your Converter
----------------------
The basic steps involved in writing your own converter are:
1. Create a new class derived from `RAWConverter`
2. Register your format's file extension with the IO subsystem.
3. Modify a conversion routine to create metadata and (potentially) rewrite it
as a raw data stream.
Converter Skeleton
~~~~~~~~~~~~~~~~~~
You'll need to create both a header file (`.h`) and an implementation
file (`.cpp`) for your new format. They should define a new
class, derived from `RAWConverter`, which implements the methods
`ConvertToRAW`, `ConvertToNative`, and `CanExportData`. Here is an
example header file:
[c++]
source~~~~
#include "RAWConverter.h"
class YourConverter : public RAWConverter {
public:
YourConverter();
virtual ~YourConverter() {}
virtual bool ConvertToRAW(const std::string& strSourceFilename,
const std::string& strTempDir,
bool bNoUserInteraction,
UINT64& iHeaderSkip, UINT64& iComponentSize,
UINT64& iComponentCount, bool& bConvertEndianness,
bool& bSigned, bool& bIsFloat,
UINT64VECTOR3& vVolumeSize,
FLOATVECTOR3& vVolumeAspect,
std::string& strTitle,
UVFTables::ElementSemanticTable& eType,
std::string& strIntermediateFile,
bool& bDeleteIntermediateFile);
virtual bool ConvertToNative(const std::string& strRawFilename,
const std::string& strTargetFilename,
UINT64 iHeaderSkip, UINT64 iComponentSize,
UINT64 iComponentCount, bool bSigned,
bool bFloatingPoint, UINT64VECTOR3 vVolumeSize,
FLOATVECTOR3 vVolumeAspect,
bool bNoUserInteraction,
const bool bQuantizeTo8Bit);
virtual bool CanExportData() const { return false; }
};
source~~~~
Now define a skeleton of an implementation in a corresponding `.cpp`
file:
[c++]
source~~~~
#include "YourConverter.h"
YourConverter::YourConverter() {}
bool YourConverter::ConvertToRAW(...)
{
return false;
}
bool YourConverter::ConvertToNative(...)
{
return false;
}
source~~~~
(I've omitted the arguments here; they should be identical to those in
the aforementioned header file.)
Congratulations! You've got a minimal converter which can be plugged in
to ImageVis3D. Let's get it part of ImageVis3D before we start trying
to read any data.
Building Your Reader
~~~~~~~~~~~~~~~~~~~~
ImageVis3D stores the list of files that are part of the program in a
few places. The files which are part of the IO subsystem are currently
listed in `Tuvok/Tuvok.pro`. In that file, you'll find two variables,
`HEADERS' and `SOURCES', which identify which files to build. You'll
need to add your converter to the list specified in both variables.
............................................................................
HEADERS += \
...
IO/AbstrConverter.h \
IO/BOVConverter.h \
IO/YourFileHere.h \
IO/BrickedDataset.h \
...
SOURCES += \
...
IO/AbstrConverter.cpp \
IO/BOVConverter.cpp \
IO/YourFileHere.cpp \
IO/BrickedDataset.cpp \
...
............................................................................
Once you've done that, rerun `qmake' from the root directory, and
`make' to rebuild -- your converter should now be part of ImageVis3D!
CAUTION: On Windows, the solution/project files have diverged from the
settings given in the qmake project file. You will need to add your
files to the Visual Studio project file in the normal way. Do *not*
run qmake on Windows, or you will not be able to compile ImageVis3D!
Register Your Converter with the IO Subsystem
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To inform ImageVis3D that your converter exists, you need to register
it with the IO subsystem. Open up `IOManager.cpp` and add a
`#include` for your converter's header file. Then, in the `IOManager`
constructor, add a line which creates an instance of your converter:
[c++]
source~~~~
m_vpConverters.push_back(new YourConverter());
source~~~~
Register Your File Extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The constructor for your new converter should modify two internal class
variables: `m_vConverterDesc` and `m_vSupportedExt`. The former should
be set to a short human-readable string that describes the file format.
This must be a single line and should generally be a short phrase of a
few words or less. The second, `m_vSupportedExt`, should be populated
with any extensions which are common for your file format.
NOTE: You may leave `m_vSupportedExt` empty if you reimplement the
`CanRead` method, described later in this document.
See `BOVConverter.cpp` for an example.
A good test at this point would be to add:
[c++]
source~~~~
MESSAGE("constructor!");
source~~~~
to your constructor, and:
[c++]
source~~~~
MESSAGE("convert!");
source~~~~
to your `ConvertToRAW` function. Run ImageVis3D and enable the
"Message" channel in the Debug Window (under "Help") tell it to load
your data file. The conversion will fail, but in the debug log you
should see both of those messages (among many others).
TIP: You can use the `WARNING` and `T_ERROR` macros to report warnings
and errors, respectively, in your converter.
Modify Raw Conversion Routine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is where all of the work happens. The purpose of this routine is
to take an input data file, fill in the appropriate metadata as given
by the arguments, and create a `strIntermediateFile` raw file with
implicit structure. Let's start with the arguments to the method:
- `strSourceFilename` - The filename where your data lives. This is the file
that the user selected via the ImageVis3D UI.
- `strTempDir` - if you need to create any temporary files, you should
prepend this directory string to each of the filenames.
- `bNoUserInteraction` - if `true`, any ambiguities should be treated as
fatal errors. Otherwise, you may query the user for more information (say,
via a `QMessageBox`).
NOTE: Qt UI elements may not be used in the Tuvok IO subsystem. If
you want to perform a graphical query when `bNoUserInteraction` is
`false`, you must put the code into the "imagevis3d" repository. Since
converters are registered dynamically, this will work fine; see the
`DialogConverter` code.
- `iHeaderSkip` - Many formats are "sectioned", in that an initial header is
given which describes the data, and a raw chunk of data follows the header.
Write the byte offset of the start of such data into this header; write `0`
if there is no header or this field makes no sense for your data format.
- `iComponentSize` - write the number of bits per component into this
argument. Note this is *bits*: so-called "short" data should write `16`
into this field.
- `iComponentCount` - write the number of components in the dataset into this
variable. This will almost always be `1`, because volume rendering really
only makes sense for scalar fields. ImageVis3D also currently supports
"color data", or RGBA data, in which case you would write `4` into this
variable. Any other setting is likely to fail later on in processing.
- `bConvertEndianness` - set this to true if the endianness of the data
differs from the endianness of the current platform. You can use the
static `EndianConvert::IsBigEndian()` method to determine the endianness of
the currently-running ImageVis3D.
- `bSigned` - set to true if the data are signed.
- `bIsFloat` - set to true if the data are floating point. This only makes
sense in combination with certain values for `iComponentSize`.
- `vVolumeSize` - the dimensions of the dataset, in X (index 0), Y (1), and
Z (2)
- `vVolumeAspect` - default aspect ratio of these data, indexed just like
`vVolumeSize`. Normally, set this to `(1,1,1)`.
- `strTitle` - any special string which identifies or describes the dataset.
Perhaps the name of the variable stored in this field.
- `eType` - See UVF's source for more detail, but generally just set this to
`UVFTables::ES_UNDEFINED`.
- `strIntermediateFile` - if you need to create a new file, set this to the
new file name. Otherwise, copy `strSourceFilename` into here.
- `bDeleteIntermediateFile` - if you need to create a new file, you should
set this to `true` to make sure ImageVis3D deletes the file when it no
longer needs it. Otherwise, make sure it is `false`, or ImageVis3D will
try to delete the input file!
The format of `strIntermediateFile` should simply be raw data which
varies slowly in X and quickly in Z. These data should be written in
"raw" format: do not use C++'s formatted IO routines if you need to
generate these data.
If all goes well, you should return `true` from this method.
*Optional*: Reimplement the `CanRead` Predicate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since there are many converters available, at various times the IO
subsystem needs to know *which* format within the candidate set is
the appropriate one to use. It does this via the `virtual` `CanRead`
method.
[c++]
source~~~~
virtual bool CanRead(const std::string& filename,
const std::vector<int8_t> bytes) const;
source~~~~
The default implementation of this method is based purely on file
extensions. The extension[s] used for your format are the ones you
added to the `m_vSupportedExt` vector in your constructor. For most
formats, this implementation will be perfectly fine.
However, some converters need to know a bit more. You might, for example,
be working with a file format that relies on *prefixes* for file names
instead of *postfixes* (i.e. "extensions"). You can override the
`CanRead` method to implement a predicate more specific to your file
format. This method should return `true` if you are reasonably sure
that your `ConvertToRAW` method will succeed for the given file, and
`false` otherwise.
The method takes two arguments. The first is the name of the file that
the IO subsystem is trying to find a converter for; for ImageVis3D,
this will be the file selected by the user in the GUI. If the user has
selected multiple files (for example, while attempting to convert a
time-dependent dataset), this will be the first file in the sequence.
The second argument is an array which contains a few bytes from the
beginning of the file (again, the first file if multiple files have
been selected).
IMPORTANT: Although the method is given the full file name and could
easily open and scan the file to see if it is valid, please do *not* do
this in your converter. If every converter operated in this fashion,
identifying the appropriate converter would be extremely slow. The
`bytes` array argument should be sufficient to identify the file; if
you need more data to do so definitively, please contact the lead
developers via the mailing lists and ask them to increase the number of
bytes given to the method.
You can use the `filename` parameter to key into any sort of custom
file naming procedure that your file format has. Many formats also
implement some concept of `magic` bytes: the first few bytes of the
files given in this format might always be statically set to a specific
value. As examples, the first 4 bytes of every NRRD file spell out
"NRRD"; the QVis file format is based on a series of key-value pairs,
and it is common for the first key to be "ObjectFileName". `CanRead`
implementations for these formats could key into such conventions to
verify that the file is what it says it is.
NOTE: You do not need to go all-out to detect errors at this stage.
For example, you should not attempt to identify if the file is
corrupted in the `CanRead` method. This method is meant to quickly
whittle down the list of available converters, and as such should do
relatively little work, and certainly no dataset-sized work. The
correct place to detect file corruption would be in the `ConvertToRAW`
method.
*Optional*: Implement Native Conversion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Many converters in the IO subsystem implement the `ConvertToNative`
method. This allows one to use ImageVis3D to convert data from one
file format to another. To do this, implement the method and modify it
to return `true`. Make sure to also modify the `CanExportData` method
to return `true`.
Examples
--------
You can read ImageVis3D's existing code for converting data to get
hints about how your converter should work.
- `REKConverter.cpp` - This is the smallest of ImageVis3D's
converters. The EZRT file format that it reads is an example of a "header
plus raw data" format; as such, the converter reads in some metadata, and
then sets up the `iHeaderSkip` variable to the location where the data
starts. No new output file is necessary.
- `QVISConverter.cpp` - This is purely a "header" file format: the user is
expected to select a file which has a simple ASCII header. One of the
fields in this header gives the name of a raw filename which stores the
data. The converter finds that field and sets `strIntermediateFile` to be
the raw filename. Since the raw file is actually *part* of the input
dataset, the converter deliberately sets `bDeleteIntermediateFile` to
`false`.
- `TiffVolumeConverter.cpp` - A little-known feature of TIFF is that
it supports so-called "directories", which provide a mechanism to
store multiple images in a single file. If these images align, then
a single TIFF file forms a volume instead of just an image. This
converter provides an example of using an external library to read the
data, and then rewriting that data as a raw binary file that the rest
of ImageVis3D's IO routines can handle.
|