diff --git a/Modules/IO/ImageBase/test/Input/rf_voltage_15_freq_0005000000_2017-5-31_12-36-44_ReferenceSpectrum_side_lines_03_fft1d_size_128.mha.cid b/Modules/IO/ImageBase/test/Input/rf_voltage_15_freq_0005000000_2017-5-31_12-36-44_ReferenceSpectrum_side_lines_03_fft1d_size_128.mha.cid index 6c6999f37c0..5d86bb280ea 100644 --- a/Modules/IO/ImageBase/test/Input/rf_voltage_15_freq_0005000000_2017-5-31_12-36-44_ReferenceSpectrum_side_lines_03_fft1d_size_128.mha.cid +++ b/Modules/IO/ImageBase/test/Input/rf_voltage_15_freq_0005000000_2017-5-31_12-36-44_ReferenceSpectrum_side_lines_03_fft1d_size_128.mha.cid @@ -1 +1 @@ -bafkreigfnnwheantnltrycahox5r3agtmf4q2vfmbabrjx7rjs66iikozi +bafkreifuvkmy4vvxyhxwcga5rvs3y75dwyt4bf3h6g2fyoxt3am5jqzbra diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/CMakeLists.txt b/Modules/ThirdParty/MetaIO/src/MetaIO/src/CMakeLists.txt index 393d0cfed1b..6a5cd2fc33f 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/CMakeLists.txt +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/CMakeLists.txt @@ -12,7 +12,7 @@ if(METAIO_FOR_VTK) set(METAIO_NAMESPACE "vtkmetaio") - set(METAIO_TARGET "metaio") + set(METAIO_TARGET "vtkmetaio") set(METAIO_INSTALL_NO_LIBRARIES TRUE) set(METAIO_INSTALL_NO_DEVELOPMENT TRUE) if(BUILD_SHARED_LIBS) @@ -124,6 +124,9 @@ if (METAIO_FOR_VTK) SOURCES ${sources} HEADERS ${headers} HEADERS_SUBDIR "vtkmetaio") + vtk_module_set_property(VTK::metaio + PROPERTY DEFINE_SYMBOL + VALUE metaio_EXPORTS) else () add_library(${METAIO_TARGET} ${sources} diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaArray.cxx b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaArray.cxx index 9c42c127dde..bcca7dacc88 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaArray.cxx +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaArray.cxx @@ -979,19 +979,27 @@ MetaArray::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, int // If compressed we inflate if (m_CompressedData) { - // if m_CompressedElementDataSize is not defined we assume the size of the - // file is the size of the compressed data + // if m_CompressedElementDataSize is not defined we assume the compressed + // data runs from the current position to the end of the file if (m_CompressedElementDataSize == 0) { + const std::streampos dataPos = _fstream->tellg(); _fstream->seekg(0, std::ios::end); - m_CompressedElementDataSize = _fstream->tellg(); - _fstream->seekg(0, std::ios::beg); + m_CompressedElementDataSize = static_cast(_fstream->tellg() - dataPos); + _fstream->seekg(dataPos); } auto * compr = new unsigned char[static_cast(m_CompressedElementDataSize)]; _fstream->read(reinterpret_cast(compr), static_cast(m_CompressedElementDataSize)); - MET_PerformUncompression(compr, m_CompressedElementDataSize, static_cast(_data), readSize); + const bool uncompressed = + MET_PerformUncompression(compr, m_CompressedElementDataSize, static_cast(_data), readSize); + delete[] compr; + if (!uncompressed) + { + std::cerr << "MetaArray: M_ReadElements: could not uncompress element data" << '\n'; + return false; + } } else // if not compressed { diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.cxx b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.cxx index 814991c3acc..63599d5395a 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.cxx +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.cxx @@ -550,7 +550,7 @@ MetaImage::InitializeEssential(int _nDims, m_ElementDirection[i*m_NDims+j] = 1; } } - } + } } if (_elementData != nullptr) @@ -1330,10 +1330,13 @@ MetaImage::ReadStream(int _nDims, METAIO_STREAM::ifstream * _stream, bool _readE delete[] wrds[i]; } delete[] wrds; - if ((fileImageDim == 0) || (fileImageDim > m_NDims)) + if ((fileImageDim <= 0) || (fileImageDim >= m_NDims)) { - // if optional file dimension size is not given or is larger than - // overall dimension then default to a size of m_NDims - 1. + // if optional file dimension size is not given, is not positive, or + // does not leave a dimension to iterate files over, then default to + // a size of m_NDims - 1. m_DimSize and m_SubQuantity are only + // indexable over [0, m_NDims), so out-of-range values must not reach + // the loops below. fileImageDim = m_NDims - 1; } std::string s; @@ -1346,6 +1349,7 @@ MetaImage::ReadStream(int _nDims, METAIO_STREAM::ifstream * _stream, bool _readE { totalFiles *= m_DimSize[i - 1]; } + int filesRead = 0; for (i = 0; i < totalFiles && !_stream->eof(); i++) { std::getline(*_stream, s); @@ -1382,9 +1386,17 @@ MetaImage::ReadStream(int _nDims, METAIO_STREAM::ifstream * _stream, bool _readE } readStreamTemp->close(); + filesRead++; } } delete readStreamTemp; + if (filesRead < totalFiles) + { + // The list ended early, so the tail of m_ElementData was never read. + std::cerr << "MetaImage: Read: LIST names " << filesRead << " file(s), but " << totalFiles << " are required" + << '\n'; + return false; + } } else if (m_ElementDataFileName.find('%') != std::string::npos) { @@ -2475,7 +2487,7 @@ MetaImage::M_Read() m_Offset[i] = mF->value[i]; } } - + mF = MET_GetFieldRecord("ElementDirection", &m_Fields); if (mF && mF->defined) { @@ -2496,7 +2508,7 @@ MetaImage::M_Read() m_ElementOrigin[i] = mF->value[i]; } } - + mF = MET_GetFieldRecord("ElementDirection", &m_Fields); if (mF && mF->defined) { @@ -2610,15 +2622,16 @@ MetaImage::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, std: // If compressed we inflate if (m_BinaryData && m_CompressedData) { - // if m_CompressedDataSize is not defined we assume the size of the - // file is the size of the compressed data + // if m_CompressedDataSize is not defined we assume the compressed data + // runs from the current position to the end of the file bool compressedDataDeterminedFromFile = false; if (m_CompressedDataSize == 0) { compressedDataDeterminedFromFile = true; + const std::streampos dataPos = _fstream->tellg(); _fstream->seekg(0, std::ios::end); - m_CompressedDataSize = _fstream->tellg(); - _fstream->seekg(0, std::ios::beg); + m_CompressedDataSize = static_cast(_fstream->tellg() - dataPos); + _fstream->seekg(dataPos); } auto * compr = new unsigned char[static_cast(m_CompressedDataSize)]; @@ -2629,7 +2642,8 @@ MetaImage::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, std: return false; } - MET_PerformUncompression(compr, m_CompressedDataSize, static_cast(_data), readSize); + const bool uncompressed = + MET_PerformUncompression(compr, m_CompressedDataSize, static_cast(_data), readSize); if (compressedDataDeterminedFromFile) { @@ -2637,6 +2651,12 @@ MetaImage::M_ReadElements(METAIO_STREAM::ifstream * _fstream, void * _data, std: } delete[] compr; + + if (!uncompressed) + { + std::cerr << "MetaImage: M_ReadElements: could not uncompress element data" << '\n'; + return false; + } } else // if not compressed { diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.h b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.h index 4e18824f957..736ea219fe6 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.h +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaImage.h @@ -263,7 +263,7 @@ class METAIO_EXPORT MetaImage : public MetaObject // direction that the fastest moving index in the image traverses in // physical space while the last column defines the direction that the // slowest moving index in the image traverses in physical space. - // + // // Set the direction cosines of the image. The direction cosines // are vectors that point from one pixel to the next. // @@ -438,7 +438,7 @@ class METAIO_EXPORT MetaImage : public MetaObject double m_ElementOrigin[10]{}; // "ElementOrigin = " 0,0,0 double m_ElementDirection[100]{}; // "ElementDirection = " 1,0,0,0,1,0,0,0,1 - + bool m_AutoFreeElementData{}; void * m_ElementData{}; diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaObject.cxx b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaObject.cxx index 6a6faf0a0ce..f0f36b9d5b3 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaObject.cxx +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaObject.cxx @@ -209,16 +209,16 @@ MetaObject::ClearUserFields() if (!deleted) { - auto it2 = m_Fields.begin(); - auto end2 = m_Fields.end(); - while (it2 != end2) + auto fieldit2 = m_Fields.begin(); + auto fieldend2 = m_Fields.end(); + while (fieldit2 != fieldend2) { - if (*it2 == field) + if (*fieldit2 == field) { - m_Fields.erase(it2); + m_Fields.erase(fieldit2); break; } - ++it2; + ++fieldit2; } delete field; } @@ -1750,7 +1750,7 @@ MetaObject::M_Read() ++it; continue; } - + // Don't add a read field to the write fields if it is already in the write fields bool found = false; FieldsContainerType::iterator dup; diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaUtils.cxx b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaUtils.cxx index 1c4896bc36b..2244c819e37 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaUtils.cxx +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/metaUtils.cxx @@ -876,6 +876,10 @@ MET_PerformUncompression(const unsigned char * sourceCompressed, d_stream.next_out = uncompressedData + dest_pos; d_stream.avail_out = cur_remain_chunk; err = inflate(&d_stream, Z_NO_FLUSH); + // Account for this call's output before any exit, including the final + // call that reports Z_STREAM_END. + uInt count_uncompressed = cur_remain_chunk - d_stream.avail_out; + dest_pos += count_uncompressed; if (err == Z_STREAM_END || err < 0) { if (err != Z_STREAM_END && err != Z_BUF_ERROR) // Z_BUF_ERROR means there is still data to uncompress, @@ -884,11 +888,15 @@ MET_PerformUncompression(const unsigned char * sourceCompressed, } break; } - uInt count_uncompressed = cur_remain_chunk - d_stream.avail_out; - dest_pos += count_uncompressed; } while (d_stream.avail_out == 0); } while (err != Z_STREAM_END && err >= 0); inflateEnd(&d_stream); + if (dest_pos != uncompressedDataSize) + { + std::cerr << "MET_PerformUncompression: expected " << uncompressedDataSize << " bytes, produced " << dest_pos + << '\n'; + return false; + } return true; } diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/CMakeLists.txt b/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/CMakeLists.txt index 8f9a29cf6cd..c0f470d1124 100644 --- a/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/CMakeLists.txt +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/CMakeLists.txt @@ -26,3 +26,5 @@ MetaAddTest(testMeta9Landmark) MetaAddTest(testMeta10Contour) MetaAddTest(testMeta11Form) MetaAddTest(testMeta12Array) +MetaAddTest(testMeta13ImageList) +MetaAddTest(testMeta14ImageCompressed) diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/testMeta13ImageList.cxx b/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/testMeta13ImageList.cxx new file mode 100644 index 00000000000..5d73fff75c7 --- /dev/null +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/testMeta13ImageList.cxx @@ -0,0 +1,119 @@ +// Regression tests for the "ElementDataFile = LIST" reader. + +#include +#include +#include +#include + +#include + +namespace +{ + +const char * const slice0 = "testMeta13_s0.raw"; +const char * const slice1 = "testMeta13_s1.raw"; + +void +WriteSlice(const char * name, unsigned char value) +{ + std::ofstream out(name, std::ios::binary); + const unsigned char buf[4] = { value, value, value, value }; + out.write(reinterpret_cast(buf), 4); +} + +// A 2x2x2 MET_UCHAR volume whose slices come from a LIST. "listBody" supplies +// the ElementDataFile value and the file names that follow it. +void +WriteHeader(const std::string & name, const std::string & listBody) +{ + std::ofstream out(name.c_str()); + out << "ObjectType = Image\n" + << "NDims = 3\n" + << "DimSize = 2 2 2\n" + << "ElementType = MET_UCHAR\n" + << "ElementSpacing = 1 1 1\n" + << "ElementByteOrderMSB = False\n" + << listBody; +} + +bool +SlicesAreOneThenTwo(MetaImage & image) +{ + for (int i = 0; i < 8; ++i) + { + const int expected = (i < 4) ? 1 : 2; + if (static_cast(image.ElementData(i)) != expected) + { + std::cout << " element " << i << " is " << image.ElementData(i) << ", expected " << expected << '\n'; + return false; + } + } + return true; +} + +} // namespace + +int +main(int, char *[]) +{ + WriteSlice(slice0, 1); + WriteSlice(slice1, 2); + + const std::string bothSlices = std::string(slice0) + "\n" + slice1 + "\n"; + + // A well-formed list reads both slices. + WriteHeader("testMeta13_valid.mhd", "ElementDataFile = LIST\n" + bothSlices); + { + MetaImage image; + if (!image.Read("testMeta13_valid.mhd")) + { + std::cout << "Well-formed LIST failed to read: FAIL" << '\n'; + return EXIT_FAILURE; + } + if (!SlicesAreOneThenTwo(image)) + { + std::cout << "Well-formed LIST read wrong values: FAIL" << '\n'; + return EXIT_FAILURE; + } + } + + // A list naming fewer files than DimSize requires must not report success, + // because the tail of the buffer is never written. + WriteHeader("testMeta13_short.mhd", std::string("ElementDataFile = LIST\n") + slice0 + "\n"); + { + MetaImage image; + if (image.Read("testMeta13_short.mhd")) + { + std::cout << "Short LIST reported success: FAIL" << '\n'; + return EXIT_FAILURE; + } + } + + // An out-of-range file dimension must fall back to NDims - 1 rather than + // indexing m_DimSize/m_SubQuantity outside [0, NDims). + const char * const outOfRange[] = { "ElementDataFile = LIST -1\n", "ElementDataFile = LIST 3\n" }; + for (const char * const listType : outOfRange) + { + WriteHeader("testMeta13_range.mhd", listType + bothSlices); + MetaImage image; + if (!image.Read("testMeta13_range.mhd")) + { + std::cout << "Out-of-range file dimension failed to read: FAIL (" << listType << ')' << '\n'; + return EXIT_FAILURE; + } + if (!SlicesAreOneThenTwo(image)) + { + std::cout << "Out-of-range file dimension read wrong values: FAIL (" << listType << ')' << '\n'; + return EXIT_FAILURE; + } + } + + std::remove(slice0); + std::remove(slice1); + std::remove("testMeta13_valid.mhd"); + std::remove("testMeta13_short.mhd"); + std::remove("testMeta13_range.mhd"); + + std::cout << "LIST element data file tests: PASS" << '\n'; + return EXIT_SUCCESS; +} diff --git a/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/testMeta14ImageCompressed.cxx b/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/testMeta14ImageCompressed.cxx new file mode 100644 index 00000000000..abdfcaca099 --- /dev/null +++ b/Modules/ThirdParty/MetaIO/src/MetaIO/src/tests/testMeta14ImageCompressed.cxx @@ -0,0 +1,110 @@ +// Regression tests for reading compressed element data. + +#include +#include +#include +#include +#include + +#include + +namespace +{ + +const char * const localName = "testMeta14_local.mha"; + +// Write a compressed single-file (LOCAL) image, then strip CompressedDataSize +// from its header so the reader has to determine the size itself. +bool +WriteLocalWithoutCompressedDataSize(int quantity) +{ + { + std::vector values(static_cast(quantity)); + for (int i = 0; i < quantity; ++i) + { + values[static_cast(i)] = static_cast(i % 251); + } + MetaImage image(quantity, 1, 1, 1, MET_UCHAR, 1, values.data()); + image.CompressedData(true); + if (!image.Write(localName)) + { + return false; + } + } + + std::ifstream in(localName, std::ios::binary); + std::string contents((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + in.close(); + + const std::string key = "CompressedDataSize = "; + const size_t at = contents.find(key); + if (at == std::string::npos) + { + std::cout << " no CompressedDataSize field was written" << '\n'; + return false; + } + const size_t eol = contents.find('\n', at); + contents.erase(at, eol - at + 1); + + std::ofstream out(localName, std::ios::binary); + out.write(contents.data(), static_cast(contents.size())); + return true; +} + +} // namespace + +int +main(int, char *[]) +{ + const int quantity = 4096; + + // A LOCAL image whose header omits CompressedDataSize: the compressed data + // starts after the header, not at the start of the file. + if (!WriteLocalWithoutCompressedDataSize(quantity)) + { + std::cout << "Could not prepare the LOCAL test image: FAIL" << '\n'; + return EXIT_FAILURE; + } + { + MetaImage image; + if (!image.Read(localName)) + { + std::cout << "LOCAL image without CompressedDataSize failed to read: FAIL" << '\n'; + return EXIT_FAILURE; + } + for (int i = 0; i < quantity; ++i) + { + const int expected = i % 251; + if (static_cast(image.ElementData(i)) != expected) + { + std::cout << " element " << i << " is " << image.ElementData(i) << ", expected " << expected << '\n'; + std::cout << "LOCAL image without CompressedDataSize read wrong values: FAIL" << '\n'; + return EXIT_FAILURE; + } + } + } + + // Truncating the compressed stream must be reported, not silently accepted. + { + std::ifstream in(localName, std::ios::binary); + std::string contents((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + in.close(); + contents.resize(contents.size() - 16); + std::ofstream out("testMeta14_truncated.mha", std::ios::binary); + out.write(contents.data(), static_cast(contents.size())); + out.close(); + + MetaImage image; + if (image.Read("testMeta14_truncated.mha")) + { + std::cout << "Truncated compressed data reported success: FAIL" << '\n'; + return EXIT_FAILURE; + } + } + + std::remove(localName); + std::remove("testMeta14_truncated.mha"); + + std::cout << "Compressed element data tests: PASS" << '\n'; + return EXIT_SUCCESS; +}