Skip to content

Commit

Permalink
Added compression quality parameter to ImagePyramid, ImageExporter an…
Browse files Browse the repository at this point in the history
…d ImageFileExporter.

Breaking change due to changed order of arguments.
JPEG quality for TIFF ImagePyramid not working properly yet.
  • Loading branch information
smistad committed Dec 10, 2024
1 parent 12d8e65 commit c41e494
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 32 deletions.
16 changes: 12 additions & 4 deletions source/FAST/Algorithms/Compression/JPEGXLCompression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void* JPEGXLCompression::decompress(uchar *compressedData, std::size_t bytes, in
}
}

void JPEGXLCompression::compress(void *data, int width, int height, std::vector<uchar>* compressed) {
void JPEGXLCompression::compress(void *data, int width, int height, std::vector<uchar>* compressed, int quality) {
auto encoder = JxlEncoderMake(nullptr);
auto runner = JxlThreadParallelRunnerMake(
nullptr,
Expand All @@ -114,12 +114,17 @@ void JPEGXLCompression::compress(void *data, int width, int height, std::vector<

JxlBasicInfo basicInfo;
JxlEncoderInitBasicInfo(&basicInfo);
int channels = 3;
basicInfo.xsize = width;
basicInfo.ysize = height;
int channels = 3;
basicInfo.bits_per_sample = 32;
basicInfo.exponent_bits_per_sample = 8;
basicInfo.bits_per_sample = 8;
basicInfo.alpha_bits = 0;
basicInfo.num_color_channels = 3;
basicInfo.num_extra_channels = 0;
basicInfo.exponent_bits_per_sample = 0;
basicInfo.uses_original_profile = JXL_FALSE;
basicInfo.have_animation = 0;
basicInfo.have_preview = 0;
if(JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(encoder.get(), &basicInfo))
throw Exception("JPEGXL: JxlEncoderSetBasicInfo failed");

Expand All @@ -130,11 +135,14 @@ void JPEGXLCompression::compress(void *data, int width, int height, std::vector<
throw Exception("JPEGXL: JxlEncoderSetColorEncoding failed");

auto frameSettings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
JxlEncoderSetFrameDistance(frameSettings, JxlEncoderDistanceFromQuality((float)quality));
JxlEncoderSetFrameLossless(frameSettings, JXL_FALSE);

if(JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(frameSettings, &pixelFormat,
static_cast<const void*>(data),
sizeof(uchar) * width*height*channels))
throw Exception("JPEGXL: JxlEncoderAddImageFrame failed");

JxlEncoderCloseInput(encoder.get());

compressed->resize(64);
Expand Down
2 changes: 1 addition & 1 deletion source/FAST/Algorithms/Compression/JPEGXLCompression.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace fast {
class JPEGXLCompression {
public:
JPEGXLCompression();
void compress(void* data, int width, int height, std::vector<uint8_t>* compressedData);
void compress(void* data, int width, int height, std::vector<uint8_t>* compressedData, int quality = 90);
/**
* @brief Decompress
* @param compressedData
Expand Down
2 changes: 1 addition & 1 deletion source/FAST/Data/Access/ImagePyramidAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ uint32_t ImagePyramidAccess::writeTileToTIFFJPEGXL(int level, int x, int y, ucha
uint32_t tile_id = TIFFComputeTile(m_tiffHandle, x, y, 0, 0);
JPEGXLCompression jxl;
std::vector<uchar> compressed;
jxl.compress(data, m_image->getLevelTileWidth(level), m_image->getLevelTileHeight(level), &compressed);
jxl.compress(data, m_image->getLevelTileWidth(level), m_image->getLevelTileHeight(level), &compressed, m_image->getCompressionQuality());
TIFFSetWriteOffset(m_tiffHandle, 0); // Set write offset to 0, so that we dont appen data
TIFFWriteRawTile(m_tiffHandle, tile_id, (void *) compressed.data(), compressed.size()); // This appends data..
TIFFCheckpointDirectory(m_tiffHandle);
Expand Down
16 changes: 15 additions & 1 deletion source/FAST/Data/ImagePyramid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace fast {

int ImagePyramid::m_counter = 0;

ImagePyramid::ImagePyramid(int width, int height, int channels, int patchWidth, int patchHeight, ImageCompression compression, DataType dataType) {
ImagePyramid::ImagePyramid(int width, int height, int channels, int patchWidth, int patchHeight, DataType dataType, ImageCompression compression, int compressionQuality) {
if(channels <= 0 || channels > 4)
throw Exception("Nr of channels must be between 1 and 4");

Expand Down Expand Up @@ -79,6 +79,7 @@ ImagePyramid::ImagePyramid(int width, int height, int channels, int patchWidth,
samplesPerPixel = 3; // RGBA image pyramid is converted to RGB with getPatchAsImage
}
m_compressionFormat = compression;
m_compressionQuality = compressionQuality;

while(true) {
currentWidth = width / std::pow(2, currentLevel);
Expand Down Expand Up @@ -124,6 +125,8 @@ ImagePyramid::ImagePyramid(int width, int height, int channels, int patchWidth,
break;
case ImageCompression::JPEG:
TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_JPEG);
//TIFFSetField(tiff, TIFFTAG_JPEGTABLESMODE, JPEGTABLESMODE_QUANT);
//TIFFSetField(tiff, TIFFTAG_JPEGQUALITY, m_compressionQuality); // Must be set after previous line // FIXME not working, only 75 gives ok results
break;
case ImageCompression::JPEGXL:
TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_JXL);
Expand Down Expand Up @@ -415,6 +418,13 @@ ImagePyramid::ImagePyramid(TIFF *fileHandle, std::vector<ImagePyramidLevel> leve
break;
case COMPRESSION_JPEG:
compression = ImageCompression::JPEG;
{
int quality = -1;
int res = TIFFGetField(m_tiffHandle, TIFFTAG_JPEGQUALITY, &quality);
if(res != 1)
throw Exception("Unable to get JPEG quality from TIFF");
m_compressionQuality = quality;
}
break;
case COMPRESSION_JXL:
compression = ImageCompression::JPEGXL;
Expand Down Expand Up @@ -572,4 +582,8 @@ DataType ImagePyramid::getDataType() const {
return m_dataType;
}

int ImagePyramid::getCompressionQuality() const {
return m_compressionQuality;
}

}
31 changes: 30 additions & 1 deletion source/FAST/Data/ImagePyramid.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,36 @@ class Image;
class FAST_EXPORT ImagePyramid : public SpatialDataObject {
FAST_DATA_OBJECT(ImagePyramid)
public:
FAST_CONSTRUCTOR(ImagePyramid, int, width,, int, height,, int, channels,, int, patchWidth, = 256, int, patchHeight, = 256, ImageCompression, compression, = ImageCompression::UNSPECIFIED, DataType, dataType, = TYPE_UINT8);
/**
* @brief Create a tiled image pyramid instance
*
* Create a tiled image pyramid using TIFF.
*
* @param width Full width of image pyramid
* @param height Full height of image pyramid
* @param channels Nr of channels of image pyramid (3 == color (RGB), 1 == grayscale)
* @param patchWidth Width of each patch
* @param patchHeight Height of each patch
* @param dataType Data type
* @param compression Compression type to use when storing the data in the TIFF.
* @param compressionQuality Quality of compression when using lossy compression like JPEG and JPEGXL.
* 100 = best, 0 = worst.
* @return instance
*/
FAST_CONSTRUCTOR(ImagePyramid,
int, width,,
int, height,,
int, channels,,
int, patchWidth, = 256,
int, patchHeight, = 256,
DataType, dataType, = TYPE_UINT8,
ImageCompression, compression, = ImageCompression::UNSPECIFIED,
int, compressionQuality, = 90
);
#ifndef SWIG
FAST_CONSTRUCTOR(ImagePyramid, openslide_t*, fileHandle,, std::vector<ImagePyramidLevel>, levels,);
FAST_CONSTRUCTOR(ImagePyramid, TIFF*, fileHandle,, std::vector<ImagePyramidLevel>, levels,, int, channels,,bool, isOMETIFF, = false);
#endif
int getNrOfLevels();
int getLevelWidth(int level);
int getLevelHeight(int level);
Expand Down Expand Up @@ -67,6 +94,7 @@ class FAST_EXPORT ImagePyramid : public SpatialDataObject {
DataBoundingBox getTransformedBoundingBox() const override;
DataBoundingBox getBoundingBox() const override;
ImageCompression getCompression() const;
int getCompressionQuality() const;
void setCompressionModels(std::shared_ptr<NeuralNetwork> compressionModel, std::shared_ptr<NeuralNetwork> decompressionModel, float outputScaleFactor = 1.0f);
void setCompressionModel(std::shared_ptr<NeuralNetwork> compressionModel);
void setDecompressionModel(std::shared_ptr<NeuralNetwork> decompressionModel, float outputScaleFactor = 1.0f);
Expand Down Expand Up @@ -100,6 +128,7 @@ class FAST_EXPORT ImagePyramid : public SpatialDataObject {
std::unordered_set<std::string> m_initializedPatchList; // Keep a list of initialized patches, for tiff backend

ImageCompression m_compressionFormat;
int m_compressionQuality = -1;

// A mutex needed to control multi-threaded reading of TIFF files
std::mutex m_readMutex;
Expand Down
46 changes: 26 additions & 20 deletions source/FAST/Exporters/ImageExporter.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "FAST/Exporters/ImageExporter.hpp"
#include <utility>
#include "FAST/Exception.hpp"
#include "FAST/Utility.hpp"
#include "FAST/Data/Image.hpp"
Expand All @@ -18,10 +17,11 @@ void ImageExporter::loadAttributes() {
ImageExporter::ImageExporter() : ImageExporter("") {
}

ImageExporter::ImageExporter(std::string filename, bool resample) : FileExporter(filename) {
ImageExporter::ImageExporter(std::string filename, int quality, bool resample) : FileExporter(filename) {
createInputPort<Image>(0);
createStringAttribute("filename", "Filename", "Path to file to load", filename);
m_resample = resample;
setQuality(quality);
}

void ImageExporter::execute() {
Expand Down Expand Up @@ -53,7 +53,7 @@ void ImageExporter::execute() {
void * inputData = access->get();
// Compress pixels with JPEG XL and store to binary file
std::vector<uchar> compressedData;
jxl.compress(inputData, input->getWidth(), input->getHeight(), &compressedData);
jxl.compress(inputData, input->getWidth(), input->getHeight(), &compressedData, m_quality);
std::ofstream file(m_filename, std::fstream::binary);
std::copy(compressedData.begin(), compressedData.end(), std::ostreambuf_iterator<char>(file));
file.close();
Expand Down Expand Up @@ -87,28 +87,28 @@ void ImageExporter::execute() {
} else {
// More complicated copy which supports more..
const uint nrOfChannels = input->getNrOfChannels();
for (uint y = 0; y < input->getHeight(); ++y) {
for (uint x = 0; x < input->getWidth(); ++x) {
for(uint y = 0; y < input->getHeight(); ++y) {
for(uint x = 0; x < input->getWidth(); ++x) {
std::vector<uchar> channelData;
for (uint channel = 0; channel < nrOfChannels; ++channel) {
for(uint channel = 0; channel < nrOfChannels; ++channel) {
uchar data = 0;
switch (input->getDataType()) {
switch(input->getDataType()) {
case TYPE_FLOAT:
data = round(((float *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel] *
255.0f);
break;
case TYPE_UINT8:
data = ((uchar *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel];
break;
case TYPE_INT8:
data = ((char *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel] + 128;
break;
case TYPE_UINT16:
data = ((ushort *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel];
break;
case TYPE_INT16:
data = ((short *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel];
break;
case TYPE_UINT8:
data = ((uchar *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel];
break;
case TYPE_INT8:
data = ((char *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel] + 128;
break;
case TYPE_UINT16:
data = ((ushort *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel];
break;
case TYPE_INT16:
data = ((short *) inputData)[(x + y * input->getWidth()) * nrOfChannels + channel];
break;
}
channelData.push_back(data);
}
Expand All @@ -123,12 +123,18 @@ void ImageExporter::execute() {
}
}

image.save(QString(m_filename.c_str()));
image.save(QString(m_filename.c_str()), nullptr, m_quality);
}
}

void ImageExporter::setResampleIfNeeded(bool resample) {
m_resample = resample;
}

void ImageExporter::setQuality(int quality) {
if(quality < 0 || quality > 100)
throw Exception("Quality in ImageExporter must be between 0 and 100");
m_quality = quality;
}

}; // end namespace fast
9 changes: 7 additions & 2 deletions source/FAST/Exporters/ImageExporter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ class FAST_EXPORT ImageExporter : public FileExporter {
FAST_PROCESS_OBJECT(ImageExporter)
public:
/**
* Create instance
* @brief Create instance
* @param filename
* @param quality When lossy compression is used like JPEG or JPEGXL, this parameter controls the quality vs size.
* 100 = best, 0 = worst.
* @param resampleIfNeeded
* @return
* @return Instance
*/
FAST_CONSTRUCTOR(ImageExporter,
std::string, filename,,
int, quality, = 90,
bool, resampleIfNeeded, = true)
void setResampleIfNeeded(bool resample);
void setQuality(int quality);
void loadAttributes() override;
private:
ImageExporter();
void execute() override;

bool m_resample;
int m_quality = 90;
};


Expand Down
11 changes: 9 additions & 2 deletions source/FAST/Exporters/ImageFileExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ namespace fast {
ImageFileExporter::ImageFileExporter() : ImageFileExporter("") {
}

ImageFileExporter::ImageFileExporter(std::string filename, bool compress, bool resample) : FileExporter(filename) {
ImageFileExporter::ImageFileExporter(std::string filename, bool compress, int quality, bool resample) : FileExporter(filename) {
createInputPort<Image>(0);
setCompression(compress);
setQuality(quality);
setResampleIfNeeded(resample);
}

Expand Down Expand Up @@ -47,7 +48,7 @@ void ImageFileExporter::execute() {
matchExtension(ext, "png") ||
matchExtension(ext, "bmp")) {
#ifdef FAST_MODULE_VISUALIZATION
auto exporter = ImageExporter::create(m_filename, m_resample)
auto exporter = ImageExporter::create(m_filename, m_quality, m_resample)
->connect(input);
exporter->setFilename(m_filename);
exporter->setInputData(input);
Expand All @@ -70,4 +71,10 @@ void ImageFileExporter::setResampleIfNeeded(bool resample) {
m_resample = resample;
}

void ImageFileExporter::setQuality(int quality) {
if(quality < 0 || quality > 100)
throw Exception("Quality in ImageFileExporter must be between 0 and 100");
m_quality = quality;
}

}
5 changes: 5 additions & 0 deletions source/FAST/Exporters/ImageFileExporter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,28 @@ class FAST_EXPORT ImageFileExporter : public FileExporter {
*
* @param filename Filename to export image to
* @param compress Use lossless compression if possible (.mhd/.zraw)
* @param quality When lossy compression is used like JPEG or JPEGXL, this parameter controls the quality vs size.
* 100 = best, 0 = worst.
* @param resampleIfNeeded If image is not isotropic and target format is standard image (jpg,gif,bmp etc),
* image will be resampled first to be isotropic.
* @return instance
*/
FAST_CONSTRUCTOR(ImageFileExporter,
std::string, filename,,
bool, compress, = false,
int, quality, = 90,
bool, resampleIfNeeded, = true
)
void setCompression(bool compress);
void setResampleIfNeeded(bool resample);
void setQuality(int quality);
private:
ImageFileExporter();
void execute() override;

bool mCompress = false;
bool m_resample = true;
int m_quality = 90;
};

}
Expand Down

0 comments on commit c41e494

Please sign in to comment.