diff --git a/changelogs/diplib_next.md b/changelogs/diplib_next.md index a99907fc..2a589864 100644 --- a/changelogs/diplib_next.md +++ b/changelogs/diplib_next.md @@ -15,6 +15,8 @@ title: "Changes DIPlib 3.x.x" The functions `dip::ImageRead()` and `dip::ImageWrite()` now recognize PNG files. Previously, PNG files could only be read through *DIPjavaio* with Bio-Formats. +- Added function `dip::FileAppendExtension()`. + ### Changed functionality - The 'diverging' color map in `dip::ApplyColorMap()` switched from CET-D08 to CET-D07 (i.e. using yellow @@ -24,6 +26,8 @@ title: "Changes DIPlib 3.x.x" `dip::Distribution::ConstSample` as argument. `dip::Distribution::MutableIterator` and `dip::Distribution::ConstIterator` are two template specializations, the first one replaces the use of `dip::Distribution::Iterator`. +- `dip::FileAddExtension()` has been deprecated, the functionality is not needed in *DIPlib*. + ### Bug fixes - XYZ to Yxy conversion and its inverse were reversed, and Yxy to gray conversion was incorrect. This means @@ -45,6 +49,9 @@ title: "Changes DIPlib 3.x.x" - Equality comparison of `dip::PixelSize` objects was documented to use a tolerance, but it wasn't using one. +- `dip::ImageReadTIFF()`, `dip::ImageReadJPEG()` and `dip::ImageReadNPY()` would replace the extension of the given + file name instead of simply appending an appropriate extension, as described in the documentation. + ### Updated dependencies - Updated included *zlib* to version 1.3.1. This version no longer uses K&R function declarations, which generated diff --git a/include/diplib/file_io.h b/include/diplib/file_io.h index 4c2ad399..f0b3d906 100644 --- a/include/diplib/file_io.h +++ b/include/diplib/file_io.h @@ -55,7 +55,7 @@ struct FileInformation { /// and data type also supported by *DIPlib*, and therefore is used as the default image file format. /// /// The function tries to open `filename` as given first, and if that fails, it appends ".ics" to the -/// name and tries again. +/// name and tries again. If `filename` has an ".ids" extension, it is replaced with ".ics". /// /// `roi` can be set to read in a subset of the pixels in the file. If only one array element is given, /// it is used for all dimensions. An empty array indicates that all pixels should be read. Otherwise, @@ -123,7 +123,7 @@ DIP_NODISCARD inline Image ImageReadICS( } /// \brief Reads image information and metadata from the ICS file `filename`, without reading the actual -/// pixel data. See \ref dip::ImageReadICS for more details. +/// pixel data. See \ref dip::ImageReadICS for more details on the file format and the handling of `filename`. DIP_EXPORT FileInformation ImageReadICSInfo( String const& filename ); /// \brief Returns true if the file `filename` is an ICS file. @@ -143,7 +143,7 @@ DIP_EXPORT bool ImageIsICS( String const& filename ); /// The ".ics" extension will be added to `filename` if it's not there. Overwrites any other file with the same name. /// /// `history` is a set of strings that are written as history lines, and will be recovered by the -/// \ref dip::ImageReadICSInfo function. +/// \ref dip::FileInformation struct when reading with \ref dip::ImageReadICS or \ref dip::ImageReadICSInfo. /// /// Set `significantBits` only if the number of significant bits is different from the full range of the data /// type of `image` (use 0 otherwise). For example, it can be used to specify that a camera has produced @@ -287,7 +287,7 @@ DIP_NODISCARD inline Image ImageReadTIFFSeries( } /// \brief Reads image information and metadata from the TIFF file `filename`, without reading the actual -/// pixel data. +/// pixel data. See \ref dip::ImageReadTIFF for more details on the handling of `filename` and `imageNumber`. DIP_EXPORT FileInformation ImageReadTIFFInfo( String const& filename, dip::uint imageNumber = 0 ); /// \brief Returns true if the file `filename` is a TIFF file. @@ -296,7 +296,10 @@ DIP_EXPORT bool ImageIsTIFF( String const& filename ); /// \brief Writes `image` as a TIFF file. /// /// The TIFF image file format is very flexible in how data can be written, but is limited to multiple pages -/// of 2D images. A 3D image will be written as a multi-page TIFF file (not yet implemented). +/// of 2D images. A 3D image will be written as a multi-page TIFF file. +/// +/// !!! warning +/// Writing of 3D images has not yet been implemented. /// /// A tensor image will be written as an image with multiple samples per pixel, but the tensor shape will be lost. /// If the tensor image has color space information, and it is one of the few color spaces known to the TIFF @@ -355,7 +358,7 @@ DIP_NODISCARD inline Image ImageReadJPEG( } /// \brief Reads image information and metadata from the JPEG file `filename`, without reading the actual -/// pixel data. +/// pixel data. See \ref dip::ImageReadJPEG for more details on the handling of `filename`. DIP_EXPORT FileInformation ImageReadJPEGInfo( String const& filename ); /// \brief Returns true if the file `filename` is a JPEG file. @@ -403,7 +406,7 @@ DIP_NODISCARD inline Image ImageReadPNG( } /// \brief Reads image information and metadata from the PNG file `filename`, without reading the actual -/// pixel data. +/// pixel data. See \ref dip::ImageReadPNG for more details on the handling of `filename`. DIP_EXPORT FileInformation ImageReadPNGInfo( String const& filename ); /// \brief Returns true if the file `filename` is a PNG file. @@ -476,7 +479,7 @@ DIP_NODISCARD inline Image ImageReadNPY( } /// \brief Reads array information (size and data type) from the NumPy NPY file `filename`, without reading the actual -/// pixel data. +/// pixel data. See \ref dip::ImageReadNPY for more details on the handling of `filename`. DIP_EXPORT FileInformation ImageReadNPYInfo( String const& filename ); /// \brief Returns true if the file `filename` is a NPY file. @@ -511,7 +514,7 @@ inline String::size_type FileGetExtensionPosition( return sep + 1 + pos; } -/// \brief Returns true if the file has an extension. +/// \brief Returns true if the file name has an extension. inline bool FileHasExtension( String const& filename ) { @@ -529,7 +532,7 @@ inline String FileGetExtension( return filename.substr( pos + 1 ); } -/// \brief Returns true if the file has the given extension. +/// \brief Returns true if the file name has the given extension. inline bool FileCompareExtension( String const& filename, String const& extension @@ -538,6 +541,7 @@ inline bool FileCompareExtension( } /// \brief Adds the given extension to the file name, replacing any existing extension. +[[ deprecated( "DIPlib doesn't need functionality to replace an extension." ) ]] inline String FileAddExtension( String const& filename, String const& extension @@ -546,6 +550,13 @@ inline String FileAddExtension( return filename.substr( 0, pos ) + String{ '.' } + extension; } +/// \brief Appends the given extension to the file name. +inline String FileAppendExtension( + String const& filename, + String const& extension +) { + return filename + String{ '.' } + extension; +} /// \endgroup diff --git a/include/diplib/linear.h b/include/diplib/linear.h index 230cf89f..41418767 100644 --- a/include/diplib/linear.h +++ b/include/diplib/linear.h @@ -1122,7 +1122,8 @@ DIP_NODISCARD inline Image GaborFIR( /// Set `process` to false for those dimensions that should not be filtered. This is equivalent to setting /// `sigmas` to 0 for those dimensions. /// -/// The `order` parameter is not yet implemented. It is ignored and assumed 0 for each dimension. +/// !!! warning +/// The `filterOrder` parameter is not yet implemented. It is ignored and assumed 0 for each dimension. /// /// \see dip::Gabor2D, dip::GaborFIR /// diff --git a/include/diplib/simple_file_io.h b/include/diplib/simple_file_io.h index e47c96a1..be6279a1 100644 --- a/include/diplib/simple_file_io.h +++ b/include/diplib/simple_file_io.h @@ -153,19 +153,25 @@ DIP_NODISCARD inline Image ImageRead( /// - `"png"`: Create a PNG file, use \ref dip::ImageWritePNG. /// - `"npy"`: Create a NumPy NPY file, use \ref dip::ImageWriteNPY. /// - `""`: Select the format by looking at the file name extension. If no extension is -/// present, it defaults to ICS version 2. This is the default. +/// present, it uses ICS version 2. This is the default. /// /// The ICS format can store any image, with all its information, such that reading the file using \ref dip::ImageRead /// or \ref dip::ImageReadICS yields an image that is identical (except the strides might be different). /// -/// The TIFF format can store 2D images, as well as 3D images as a series of 2D slides (not yet implemented). +/// The TIFF format can store 2D images, as well as 3D images as a series of 2D slides (but this is not yet implemented). /// A limited set of color spaces are recognized, other color images are stored without color space information. /// Complex data is not supported, other data types are. But note that images other than 8-bit or 16-bit unsigned /// integer lead to files that are not recognized by most readers. /// -/// The JPEG format can store 2D images. Tensor images are always tagged as sRGB. Most metadata will be lost. +/// The JPEG format can store 2D images with 1 or 3 tensor elements. Tensor images are always tagged as sRGB. Most +/// metadata will be lost. /// Image data is converted to 8-bit unsigned integer, without scaling. /// +/// The PNG format can store 2D images with 1 to 4 tensor elements. Images with 3 or 4 tensor elements are always +/// tagged as sRGB, those with 1 or 2 as grayscale. The 2nd or 4th tensor element is the alpha channel. +/// Image data is converted to 8-bit unsigned integer, without scaling, unless the image is binary or 16-bit unsigned +/// integer. +/// /// The NPY format stores raw pixel data for a scalar image. Tensor images cannot be written. All metadata will be lost. /// /// `compression` determines the compression method used when writing the pixel data. It can be one of the diff --git a/src/file_io/file_io_support.cpp b/src/file_io/file_io_support.cpp index 54b99bc6..75a96557 100644 --- a/src/file_io/file_io_support.cpp +++ b/src/file_io/file_io_support.cpp @@ -15,6 +15,10 @@ * limitations under the License. */ +#include + +#include "diplib.h" +#include "diplib/file_io.h" #include "file_io_support.h" namespace dip { diff --git a/src/file_io/ics.cpp b/src/file_io/ics.cpp index 62fc8224..c907f4e3 100644 --- a/src/file_io/ics.cpp +++ b/src/file_io/ics.cpp @@ -17,7 +17,13 @@ #ifdef DIP_CONFIG_HAS_ICS -#include // std::strtoul +#include +#include +#include +#include +#include +#include +#include #include "diplib.h" #include "diplib/file_io.h" diff --git a/src/file_io/jpeg.cpp b/src/file_io/jpeg.cpp index 024c27c1..669995bd 100644 --- a/src/file_io/jpeg.cpp +++ b/src/file_io/jpeg.cpp @@ -17,6 +17,11 @@ #ifdef DIP_CONFIG_HAS_JPEG +#include +#include +#include +#include + #include "diplib.h" #include "diplib/file_io.h" @@ -62,13 +67,12 @@ class JpegInput { : filename_( std::move( filename )), jerr_( error_msg ) { infile_ = std::fopen( filename_.c_str(), "rb" ); if( infile_ == nullptr ) { - if( !FileHasExtension( filename_ )) { - filename_ = FileAddExtension( filename_, "jpg" ); // Try with "jpg" extension + filename_ = FileAppendExtension( filename_, "jpg" ); // Try with "jpg" extension + infile_ = std::fopen( filename_.c_str(), "rb" ); + if( infile_ == nullptr ) { + filename_.back() = 'e'; + filename_.push_back( 'g' ); // Try with "jpeg" extension infile_ = std::fopen( filename_.c_str(), "rb" ); - if( infile_ == nullptr ) { - filename_ = FileAddExtension( filename_, "jpeg" ); // Try with "jpeg" extension - infile_ = std::fopen( filename_.c_str(), "rb" ); - } } } if( infile_ == nullptr ) { @@ -109,12 +113,12 @@ class JpegInput { class JpegOutput { public: - explicit JpegOutput( String const& filename, std::jmp_buf const& setjmp_buffer, String& error_msg ) : jerr_( error_msg ) { + JpegOutput( String const& filename, std::jmp_buf const& setjmp_buffer, String& error_msg ) : jerr_( error_msg ) { // Open the file for writing if( FileHasExtension( filename )) { outfile_ = std::fopen(filename.c_str(), "wb"); } else { - outfile_ = std::fopen( FileAddExtension( filename, "jpg" ).c_str(), "wb" ); + outfile_ = std::fopen( FileAppendExtension( filename, "jpg" ).c_str(), "wb" ); } if( outfile_ == nullptr ) { DIP_THROW_RUNTIME( "Could not open file for writing" ); diff --git a/src/file_io/npy.cpp b/src/file_io/npy.cpp index 342ed94d..23ba9e22 100644 --- a/src/file_io/npy.cpp +++ b/src/file_io/npy.cpp @@ -20,8 +20,15 @@ // See https://github.com/rogersce/cnpy for cnpy // See https://github.com/llohse/libnpy for libnpy -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include "diplib.h" #include "diplib/file_io.h" @@ -86,8 +93,9 @@ char TypeChar( DataType dt ) { case DT_SCOMPLEX: case DT_DCOMPLEX: return 'c'; + default: + DIP_THROW( E::NOT_REACHABLE ); } - DIP_THROW( "Unknown data type" ); // This should never happen, but GCC complains. } void ReverseArray( UnsignedArray& array ) { @@ -97,7 +105,7 @@ void ReverseArray( UnsignedArray& array ) { } } -std::string CreateHeaderDict( DataType dataType, UnsignedArray const& sizes, bool fortranOrder ) { +String CreateHeaderDict( DataType dataType, UnsignedArray const& sizes, bool fortranOrder ) { std::ostringstream out; out << "{'descr': '" << SystemEndianChar(); out << TypeChar( dataType ); @@ -112,10 +120,10 @@ std::string CreateHeaderDict( DataType dataType, UnsignedArray const& sizes, boo void WriteHeader( std::ostream& ostream, DataType dataType, UnsignedArray const& sizes, bool fortranOrder ) { WriteMagic( ostream ); - std::string headerDict = CreateHeaderDict( dataType, sizes, fortranOrder ); + String headerDict = CreateHeaderDict( dataType, sizes, fortranOrder ); dip::uint length = magicStringLength + 3 + headerDict.length(); dip::uint padding = 64 - length % 64; - headerDict += std::string( padding, ' ' ); + headerDict += String( padding, ' ' ); headerDict += '\n'; length = headerDict.length(); ostream.put( static_cast< char >( length & 0xffu )); @@ -129,7 +137,7 @@ void ReadHeader( std::istream& istream, DataType& dataType, UnsignedArray& sizes istream.read( buf, 2 ); DIP_THROW_IF( !istream, BAD_NPY_HEADER ); dip::uint length = static_cast< dip::uint >( buf[ 0 ] ) + ( static_cast< dip::uint >( buf[ 1 ] ) << 8u ); - std::string headerDict( length, '\0' ); + String headerDict( length, '\0' ); istream.read( &headerDict[ 0 ], static_cast< dip::sint >( length )); //std::cout << "Header length: " << length << '\n'; //std::cout << "Header contents: >>" << headerDict << "<<\n"; @@ -148,7 +156,7 @@ void ReadHeader( std::istream& istream, DataType& dataType, UnsignedArray& sizes std::regex_search( headerDict, res, regex_shape ); DIP_THROW_IF ( res.size() != 2, "Failed to parse NPY header keyword 'shape'" ); //std::cout << "shape: " << res.str() << '\n' << " : "; - std::string shapeStr = res.str( 1 ); + String shapeStr = res.str( 1 ); std::regex regex_num( "[0-9]+" ); for( auto it = std::sregex_iterator( shapeStr.begin(), shapeStr.end(), regex_num ); it != std::sregex_iterator{}; ++it ) { //std::cout << it->str() << ", "; @@ -166,7 +174,7 @@ void ReadHeader( std::istream& istream, DataType& dataType, UnsignedArray& sizes // A is the endianness char, one of littleEndianChar, bigEndianChar or noEndianChar // B is the data type char // xxx is the number of bytes for the data type - std::string descr = res.str( 1 ); + String descr = res.str( 1 ); swapEndianness = !(( descr[ 0 ] == SystemEndianChar() ) || ( descr[ 0 ] == noEndianChar )); // if unknown endianness, just read in as-is dip::uint bytes = std::stoul( descr.substr( 2 )); if( bytes == 1 ) { @@ -257,10 +265,8 @@ std::ifstream OpenNPYForReading( String filename, FileInformation& fileInformati fileInformation.name = std::move( filename ); std::ifstream istream( fileInformation.name, std::ifstream::binary ); if( !istream ) { - if( !FileHasExtension( fileInformation.name )) { - fileInformation.name = FileAddExtension( fileInformation.name, "npy" ); - istream.open( fileInformation.name, std::ifstream::binary ); - } + fileInformation.name = FileAppendExtension( fileInformation.name, "npy" ); + istream.open( fileInformation.name, std::ifstream::binary ); if( !istream ) { DIP_THROW_RUNTIME( "Could not open the specified NPY file" ); } @@ -358,7 +364,7 @@ void ImageWriteNPY( if( FileHasExtension( filename )) { ostream.open( filename, std::ofstream::binary ); } else { - ostream.open( FileAddExtension( filename, "npy" ), std::ofstream::binary ); + ostream.open( FileAppendExtension( filename, "npy" ), std::ofstream::binary ); } if( !ostream ) { DIP_THROW_RUNTIME( "Could not open specified NPY file for writing" ); diff --git a/src/file_io/png.cpp b/src/file_io/png.cpp index 96f318d5..da10d582 100644 --- a/src/file_io/png.cpp +++ b/src/file_io/png.cpp @@ -41,10 +41,8 @@ class PngInput { PngInput( String filename ) : filename_( std::move( filename )) { infile_ = std::fopen( filename_.c_str(), "rb" ); if( infile_ == nullptr ) { - if( !FileHasExtension( filename_ )) { - filename_ = FileAddExtension( filename_, "png" ); // Try with "png" extension - infile_ = std::fopen( filename_.c_str(), "rb" ); - } + filename_ = FileAppendExtension( filename_, "png" ); // Try with "png" extension + infile_ = std::fopen( filename_.c_str(), "rb" ); } if( infile_ == nullptr ) { DIP_THROW_RUNTIME( "Could not open the specified PNG file" ); @@ -95,7 +93,7 @@ class PngOutput { if( FileHasExtension( filename )) { outfile_ = std::fopen(filename.c_str(), "wb"); } else { - outfile_ = std::fopen( FileAddExtension( filename, "png" ).c_str(), "wb" ); + outfile_ = std::fopen( FileAppendExtension( filename, "png" ).c_str(), "wb" ); } if( outfile_ == nullptr ) { DIP_THROW_RUNTIME( "Could not open file for writing" ); diff --git a/src/file_io/tiff_read.cpp b/src/file_io/tiff_read.cpp index b000231d..1aaef63f 100644 --- a/src/file_io/tiff_read.cpp +++ b/src/file_io/tiff_read.cpp @@ -17,6 +17,10 @@ #ifdef DIP_CONFIG_HAS_TIFF +#include +#include +#include + #include "diplib.h" #include "diplib/file_io.h" #include "diplib/generic_iterators.h" @@ -46,13 +50,11 @@ class TiffFile { // Open the file for reading tiff_ = TIFFOpen( filename_.c_str(), "rc" ); // c == Disable the use of strip chopping when reading images. if( tiff_ == nullptr ) { - if( !FileHasExtension( filename_ )) { - filename_ = FileAddExtension( filename_, "tif" ); // Try with "tif" extension + filename_ = FileAppendExtension( filename_, "tif" ); // Try with "tif" extension + tiff_ = TIFFOpen( filename_.c_str(), "rc" ); + if( tiff_ == nullptr ) { + filename_ = filename_ + 'f'; // Try with "tiff" extension tiff_ = TIFFOpen( filename_.c_str(), "rc" ); - if( tiff_ == nullptr ) { - filename_ = filename_ + 'f'; // Try with "tiff" extension - tiff_ = TIFFOpen( filename_.c_str(), "rc" ); - } } } if( tiff_ == nullptr ) { @@ -697,7 +699,7 @@ void ReadTIFFData( // 1234123412341234.... // We know that data.tensorElements > 1, otherwise we force to PLANARCONFIG_SEPARATE //std::cout << "[ReadTIFFData] Tiles, Contiguous\n"; - DIP_ASSERT( static_cast< dip::uint >( tileSize ) == tileWidth * tileLength * data.tensorElements * sizeOf ); + DIP_ASSERT( static_cast< dip::uint >( tileSize ) == static_cast< dip::uint >( tileWidth ) * tileLength * data.tensorElements * sizeOf ); dip::uint tileStrideY = data.tensorElements * tileWidth; dip::uint yPos = roiSpec.roi[ 1 ].Offset(); for( dip::uint y = firstTileY; y <= roiSpec.roi[ 1 ].Last(); y += tileLength ) { @@ -739,7 +741,7 @@ void ReadTIFFData( } else if( planarConfiguration == PLANARCONFIG_SEPARATE ) { // 1111...2222...3333...4444... //std::cout << "[ReadTIFFData] Tiles, Separate\n"; - DIP_ASSERT( static_cast< dip::uint >( tileSize ) == tileWidth * tileLength * sizeOf ); + DIP_ASSERT( static_cast< dip::uint >( tileSize ) == static_cast< dip::uint >( tileWidth ) * tileLength * sizeOf ); dip::uint tileStrideY = tileWidth; for( auto plane : roiSpec.channels ) { uint8* imagedataRow = imagedata; @@ -1110,7 +1112,7 @@ void ImageReadTIFFSeries( StringArray const& filenames, String const& useColorMap ) { - DIP_THROW_IF( filenames.size() < 1, E::ARRAY_PARAMETER_EMPTY ); + DIP_THROW_IF( filenames.empty(), E::ARRAY_PARAMETER_EMPTY ); // Read in first image Image tmp; diff --git a/src/file_io/tiff_write.cpp b/src/file_io/tiff_write.cpp index ae136165..1896b755 100644 --- a/src/file_io/tiff_write.cpp +++ b/src/file_io/tiff_write.cpp @@ -26,8 +26,8 @@ namespace dip { namespace { -static char const* TIFF_WRITE_TAG = "Error writing tag to TIFF file"; -static char const* TIFF_WRITE_DATA = "Error writing data"; +constexpr char const* TIFF_WRITE_TAG = "Error writing tag to TIFF file"; +constexpr char const* TIFF_WRITE_DATA = "Error writing data"; #define WRITE_TIFF_TAG( tiff, tag, value ) do { if( !TIFFSetField( tiff, tag, value )) { DIP_THROW_RUNTIME( TIFF_WRITE_TAG ); }} while(false) @@ -41,7 +41,7 @@ class TiffFile { if( FileHasExtension( filename )) { tiff_ = TIFFOpen( filename.c_str(), "w" ); } else { - tiff_ = TIFFOpen( FileAddExtension( filename, "tif" ).c_str(), "w" ); + tiff_ = TIFFOpen( FileAppendExtension( filename, "tif" ).c_str(), "w" ); } if( tiff_ == nullptr ) { DIP_THROW_RUNTIME( "Could not open the specified file" ); @@ -62,7 +62,7 @@ class TiffFile { TIFF* tiff_ = nullptr; }; -static uint16 CompressionTranslate( String const& compression ) { +uint16 CompressionTranslate( String const& compression ) { if( compression.empty() || ( compression == "deflate" )) { return COMPRESSION_DEFLATE; } else if( compression == "LZW" ) { diff --git a/tools/FindMatlab.cmake b/tools/FindMatlab.cmake index 55710d95..35e41b2b 100644 --- a/tools/FindMatlab.cmake +++ b/tools/FindMatlab.cmake @@ -1188,9 +1188,6 @@ function(matlab_add_mex) if(NOT ${prefix}_NO_IMPLICIT_LINK_TO_MATLAB_LIBRARIES) if(Matlab_HAS_CPP_API) - if(Matlab_ENGINE_LIBRARY) - target_link_libraries(${${prefix}_NAME} ${Matlab_ENGINE_LIBRARY}) - endif() if(Matlab_DATAARRAY_LIBRARY) target_link_libraries(${${prefix}_NAME} ${Matlab_DATAARRAY_LIBRARY}) endif()