Skip to content

Commit

Permalink
Implemented support for reading TIFF files of data types other than u…
Browse files Browse the repository at this point in the history
…int8
  • Loading branch information
smistad committed Dec 18, 2023
1 parent ca375a6 commit 5fcd023
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 81 deletions.
120 changes: 48 additions & 72 deletions source/FAST/Data/Access/ImagePyramidAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,6 @@ ImagePyramidAccess::~ImagePyramidAccess() {
release();
}

ImagePyramidPatch ImagePyramidAccess::getPatch(std::string tile) {
auto parts = split(tile, "_");
if(parts.size() != 3)
throw Exception("incorrect tile format");

int level = std::stoi(parts[0]);
int tile_x = std::stoi(parts[1]);
int tile_y = std::stoi(parts[2]);

return getPatch(level, tile_x, tile_y);
}

void jpegErrorExit(j_common_ptr cinfo) {
char jpegLastErrorMsg[JMSG_LENGTH_MAX];
// Create message
Expand Down Expand Up @@ -123,13 +111,15 @@ void ImagePyramidAccess::readVSITileToBuffer(vsi_tile_header tile, uchar* data)
}
}

std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(int level, int x, int y, int width, int height) {
std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchDataChar(int level, int x, int y, int width, int height) {
const int levelWidth = m_image->getLevelWidth(level);
const int levelHeight = m_image->getLevelHeight(level);
const int channels = m_image->getNrOfChannels();
const int tileWidth = m_image->getLevelTileWidth(level);
const int tileHeight = m_image->getLevelTileHeight(level);
auto data = std::make_unique<uchar[]>(width*height*channels);
const int bytesPerPixel = getSizeOfDataType(m_image->getDataType(), channels);
// Store as uchar even though it might be something else
auto data = std::make_unique<uchar[]>(width*height*bytesPerPixel);
if(m_tiffHandle != nullptr) {
// Read all tiles within region, then crop
if(!isPatchInitialized(level, x, y)) {
Expand All @@ -152,7 +142,7 @@ std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(int level, int x, int
// From TIFFReadTile documentation: Return the data for the tile containing the specified coordinates.
int bytesRead = readTileFromTIFF((void *) data.get(), x, y);
} else if((width < tileWidth || height < tileHeight) && x % tileWidth == 0 && y % tileHeight == 0) {
auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*channels);
auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*bytesPerPixel);
{
// From TIFFReadTile documentation: Return the data for the tile containing the specified coordinates.
// In TIFF all tiles have the same size, thus they are padded..
Expand All @@ -173,7 +163,7 @@ std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(int level, int x, int
if(x % tileWidth != 0) totalTilesX += 1;
if(y % tileHeight != 0) totalTilesY += 1;
const int targetNumberOfTiles = totalTilesX*totalTilesY;
auto fullTileBuffer = make_uninitialized_unique<uchar[]>(tileWidth*tileHeight*targetNumberOfTiles*channels);
auto fullTileBuffer = make_uninitialized_unique<uchar[]>(tileWidth*tileHeight*targetNumberOfTiles*bytesPerPixel);
// Does the buffer need to be initialized/padded?
if(x+width >= levelWidth || x+height >= levelHeight) { // Some tiles are outside, fill it
if(channels > 1) {
Expand All @@ -188,15 +178,17 @@ std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(int level, int x, int
const int fullTileBufferWidth = totalTilesX*tileWidth;
for(int i = 0; i < totalTilesX; ++i) {
for(int j = 0; j < totalTilesY; ++j) {
auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*channels);
auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*bytesPerPixel);
int tileX = i*tileWidth;
int tileY = j*tileHeight;
int bytesRead = readTileFromTIFF((void *) tileData.get(), firstTileX*tileWidth+tileX, firstTileY*tileHeight+tileY);
// Stitch tile into full buffer
for(int cy = 0; cy < tileHeight; ++cy) {
for(int cx = 0; cx < tileWidth; ++cx) {
for(int channel = 0; channel < channels; ++channel) {
fullTileBuffer[(tileX + cx + (tileY + cy)*fullTileBufferWidth)*channels + channel] = tileData[(cx + cy*tileWidth)*channels + channel];
for(int byte = 0; byte < bytesPerPixel; ++byte) {
fullTileBuffer[((tileX + cx + (tileY + cy)*fullTileBufferWidth)*channels + channel)*bytesPerPixel + byte] = tileData[((cx + cy*tileWidth)*channels + channel)*bytesPerPixel + byte];
}
}
}
}
Expand All @@ -208,7 +200,9 @@ std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(int level, int x, int
for(int cy = offsetY; cy < offsetY + height; ++cy) {
for(int cx = offsetX; cx < offsetX + width; ++cx) {
for(int channel = 0; channel < channels; ++channel) {
data[(cx - offsetX + (cy - offsetY) * width)*channels + channel] = fullTileBuffer[(cx + cy * fullTileBufferWidth)*channels + channel];
for(int byte = 0; byte < bytesPerPixel; ++byte) {
data[((cx - offsetX + (cy - offsetY) * width)*channels + channel)*bytesPerPixel + byte] = fullTileBuffer[((cx + cy * fullTileBufferWidth)*channels + channel)*bytesPerPixel + byte];
}
}
}
}
Expand Down Expand Up @@ -313,31 +307,6 @@ std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(int level, int x, int
return data;
}

ImagePyramidPatch ImagePyramidAccess::getPatch(int level, int tile_x, int tile_y) {
// Create patch
int levelWidth = m_image->getLevelWidth(level);
int levelHeight = m_image->getLevelHeight(level);
int levelTileWidth = m_image->getLevelTileWidth(level);
int levelTileHeight = m_image->getLevelTileHeight(level);
int tilesX = m_image->getLevelTilesX(level);
int tilesY = m_image->getLevelTilesY(level);
ImagePyramidPatch tile;
tile.offsetX = tile_x * levelTileWidth;
tile.offsetY = tile_y * levelTileHeight;

tile.width = levelTileWidth;
if(tile_x == tilesX - 1)
tile.width = levelWidth - tile.offsetX;
tile.height = levelTileHeight;
if(tile_y == tilesY - 1)
tile.height = levelHeight - tile.offsetY;

// Read the actual data
tile.data = getPatchData(level, tile.offsetX, tile.offsetY, tile.width, tile.height);

return tile;
}

std::shared_ptr<Image> ImagePyramidAccess::getLevelAsImage(int level) {
if(level < 0 || level >= m_image->getNrOfLevels())
throw Exception("Incorrect level given to getLevelAsImage" + std::to_string(level));
Expand All @@ -360,10 +329,41 @@ std::shared_ptr<Image> ImagePyramidAccess::getPatchAsImage(int level, int offset
if(offsetX + width > m_image->getLevelWidth(level) || offsetY + height > m_image->getLevelHeight(level))
throw Exception("offset + size exceeds level size");

auto data = getPatchData(level, offsetX, offsetY, width, height);
Image::pointer image;
switch(m_image->getDataType()) {
case TYPE_UINT8:
{
auto data = getPatchData<uchar>(level, offsetX, offsetY, width, height);
image = Image::create(width, height, m_image->getDataType(), m_image->getNrOfChannels(), std::move(data));
}
break;
case TYPE_UINT16:
{
auto data = getPatchData<ushort>(level, offsetX, offsetY, width, height);
image = Image::create(width, height, m_image->getDataType(), m_image->getNrOfChannels(), std::move(data));
}
break;
case TYPE_INT8:
{
auto data = getPatchData<char>(level, offsetX, offsetY, width, height);
image = Image::create(width, height, m_image->getDataType(), m_image->getNrOfChannels(), std::move(data));
}
break;
case TYPE_INT16:
{
auto data = getPatchData<short>(level, offsetX, offsetY, width, height);
image = Image::create(width, height, m_image->getDataType(), m_image->getNrOfChannels(), std::move(data));
}
break;
case TYPE_FLOAT:
{
auto data = getPatchData<float>(level, offsetX, offsetY, width, height);
image = Image::create(width, height, m_image->getDataType(), m_image->getNrOfChannels(), std::move(data));
}
break;
}
float scale = m_image->getLevelScale(level);
auto spacing = m_image->getSpacing();
auto image = Image::create(width, height, TYPE_UINT8, m_image->getNrOfChannels(), std::move(data));
image->setSpacing(Vector3f(
spacing.x()*scale,
spacing.y()*scale,
Expand Down Expand Up @@ -403,31 +403,7 @@ std::shared_ptr<Image> ImagePyramidAccess::getPatchAsImage(int level, int tileX,
if(tileY == tilesY - 1)
tile.height = levelHeight - tile.offsetY;

// Read the actual data
auto data = getPatchData(level, tile.offsetX, tile.offsetY, tile.width, tile.height);

float scale = m_image->getLevelScale(level);
Vector3f spacing = m_image->getSpacing();
auto image = Image::create(tile.width, tile.height, TYPE_UINT8, m_image->getNrOfChannels(), std::move(data));
image->setSpacing(Vector3f(
scale*spacing.x(),
scale*spacing.y(),
1.0f
));
// TODO Set transformation
SceneGraph::setParentNode(image, std::dynamic_pointer_cast<SpatialDataObject>(m_image));

if(m_fileHandle != nullptr && convertToRGB) {
// Data is stored as BGRA, need to delete alpha channel and reverse it
auto channelConverter = ImageChannelConverter::New();
channelConverter->setChannelsToRemove(false, false, false, true);
channelConverter->setReverseChannels(true);
channelConverter->setInputData(image);

return channelConverter->updateAndGetOutputData<Image>();
} else {
return image;
}
return getPatchAsImage(level, tile.offsetX, tile.offsetY, tile.width, tile.height, convertToRGB);
}

uint32_t ImagePyramidAccess::writeTileToTIFF(int level, int x, int y, uchar *data, int width, int height, int channels) {
Expand Down Expand Up @@ -537,7 +513,7 @@ void ImagePyramidAccess::setPatch(int level, int x, int y, Image::pointer patch,
y -= offsetY*tileHeight/2;

// Get existing tile. This gets the tile in which x, y is contained in, not where it starts..
auto newData = getPatchData(level, x, y, tileWidth, tileHeight);
auto newData = getPatchData<uchar>(level, x, y, tileWidth, tileHeight);

// Downsample tile from previous level and add it to existing tile
if(m_image->getNrOfChannels() >= 3) {
Expand Down
14 changes: 11 additions & 3 deletions source/FAST/Data/Access/ImagePyramidAccess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class ImageCompression {
JPEG2000,
LZW, // Lossless compression
NEURAL_NETWORK, // Use neural network to do the compression and decompression. See ImagePyramid::setCompressionModels
DEFLATE, // Zlib lossless
};

struct vsi_tile_header {
Expand Down Expand Up @@ -72,15 +73,15 @@ class FAST_EXPORT ImagePyramidAccess : Object {
ImagePyramidAccess(std::vector<ImagePyramidLevel> levels, openslide_t* fileHandle, TIFF* tiffHandle, std::ifstream* stream, std::vector<vsi_tile_header>& vsiTiles, std::shared_ptr<ImagePyramid> imagePyramid, bool writeAccess, std::unordered_set<std::string>& initializedPatchList, std::mutex& readMutex, ImageCompression compressionFormat);
void setPatch(int level, int x, int y, std::shared_ptr<Image> patch, bool propagate = true);
bool isPatchInitialized(uint level, uint x, uint y);
std::unique_ptr<uchar[]> getPatchData(int level, int x, int y, int width, int height);
ImagePyramidPatch getPatch(std::string tile);
ImagePyramidPatch getPatch(int level, int patchX, int patchY);
template <class T>
std::unique_ptr<T[]> getPatchData(int level, int x, int y, int width, int height);
std::shared_ptr<Image> getLevelAsImage(int level);
std::shared_ptr<Image> getPatchAsImage(int level, int offsetX, int offsetY, int width, int height, bool convertToRGB = true);
std::shared_ptr<Image> getPatchAsImage(int level, int patchIdX, int patchIdY, bool convertToRGB = true);
void release();
~ImagePyramidAccess();
private:
std::unique_ptr<uchar[]> getPatchDataChar(int level, int x, int y, int width, int height);
std::shared_ptr<ImagePyramid> m_image;
std::vector<ImagePyramidLevel> m_levels;
bool m_write;
Expand All @@ -99,4 +100,11 @@ class FAST_EXPORT ImagePyramidAccess : Object {
int readTileFromTIFF(void* data, int x, int y);
};

template <class T>
std::unique_ptr<T[]> ImagePyramidAccess::getPatchData(int level, int x, int y, int width, int height) {
auto data = getPatchDataChar(level, x, y, width, height);
std::unique_ptr<T[]> data2(reinterpret_cast<T*>(data.release())); // Cast to correct type
return data2;
}

}
54 changes: 49 additions & 5 deletions source/FAST/Data/ImagePyramid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace fast {

int ImagePyramid::m_counter = 0;

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

Expand Down Expand Up @@ -60,7 +60,8 @@ ImagePyramid::ImagePyramid(int width, int height, int channels, int patchWidth,
m_counter += 1;

uint photometric;
uint bitsPerSample = 8;
uint bitsPerSample = getSizeOfDataType(dataType, 1) * 8;
m_dataType = dataType;
uint samplesPerPixel;
if(channels == 1) {
photometric = PHOTOMETRIC_MINISBLACK; // Photometric mask causes crash..
Expand Down Expand Up @@ -366,10 +367,46 @@ ImagePyramid::ImagePyramid(TIFF *fileHandle, std::vector<ImagePyramidLevel> leve
m_levels[i].tilesX = std::ceil((float)m_levels[i].width / m_levels[i].tileWidth);
m_levels[i].tilesY = std::ceil((float)m_levels[i].height / m_levels[i].tileHeight);
}
TIFFSetDirectory(fileHandle, 0);
// Get data type
uint16_t bitsPerSample;
TIFFGetField(fileHandle, TIFFTAG_BITSPERSAMPLE, &bitsPerSample);
uint16_t sampleFormat;
TIFFGetField(fileHandle, TIFFTAG_SAMPLEFORMAT, &sampleFormat);
std::cout << "bits per sample: " << bitsPerSample << std::endl;
if(sampleFormat == SAMPLEFORMAT_IEEEFP) {
if(bitsPerSample == 32) {
m_dataType = TYPE_FLOAT;
} else {
throw Exception("Unsupported TIFF data type, float with " + std::to_string(bitsPerSample) + " bits");
}
} else if(sampleFormat == SAMPLEFORMAT_UINT) {
std::map<uint16_t, DataType> bitsPerSampleMap = {
{8, TYPE_UINT8},
{16, TYPE_UINT16},
};
if(bitsPerSampleMap.count(bitsPerSample) > 0) {
m_dataType = bitsPerSampleMap[bitsPerSample];
} else {
throw Exception("Unsupported TIFF data type, unsigned integer with " + std::to_string(bitsPerSample) + " bits");
}
} else if(sampleFormat == SAMPLEFORMAT_INT) {
std::map<uint16_t, DataType> bitsPerSampleMap = {
{8, TYPE_INT8},
{16, TYPE_INT16},
};
if(bitsPerSampleMap.count(bitsPerSample) > 0) {
m_dataType = bitsPerSampleMap[bitsPerSample];
} else {
throw Exception("Unsupported TIFF data type, signed integer with " + std::to_string(bitsPerSample) + " bits");
}
} else {
throw Exception("Unsupported TIFF data type: " + std::to_string(sampleFormat) + " " + std::to_string(bitsPerSample) + " bits");
}
// Get compression
uint compressionTag;
uint16_t compressionTag;
TIFFGetField(fileHandle, TIFFTAG_COMPRESSION, &compressionTag);
ImageCompression compression;
ImageCompression compression = ImageCompression::UNSPECIFIED;
switch(compressionTag) {
case COMPRESSION_NONE:
compression = ImageCompression::RAW;
Expand All @@ -383,6 +420,10 @@ ImagePyramid::ImagePyramid(TIFF *fileHandle, std::vector<ImagePyramidLevel> leve
case COMPRESSION_JP2000:
throw Exception("JPEG 2000 TIFF not supported yet");
break;
case COMPRESSION_ADOBE_DEFLATE:
case COMPRESSION_DEFLATE:
compression = ImageCompression::DEFLATE;
break;
case 34666:
compression = ImageCompression::NEURAL_NETWORK;
break;
Expand All @@ -393,7 +434,6 @@ ImagePyramid::ImagePyramid(TIFF *fileHandle, std::vector<ImagePyramidLevel> leve
// Get spacing from TIFF
float spacingX;
float spacingY;
TIFFSetDirectory(fileHandle, 0);
int resX = TIFFGetField(fileHandle, TIFFTAG_XRESOLUTION, &spacingX);
int resY = TIFFGetField(fileHandle, TIFFTAG_YRESOLUTION, &spacingY);
if(resX == 1 && resY == 1) {
Expand Down Expand Up @@ -529,4 +569,8 @@ float ImagePyramid::getDecompressionOutputScaleFactor() const {
return m_decompressionOutputScaleFactor;
}

DataType ImagePyramid::getDataType() const {
return m_dataType;
}

}
4 changes: 3 additions & 1 deletion source/FAST/Data/ImagePyramid.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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);
FAST_CONSTRUCTOR(ImagePyramid, int, width,, int, height,, int, channels,, int, patchWidth, = 256, int, patchHeight, = 256, ImageCompression, compression, = ImageCompression::UNSPECIFIED, DataType, dataType, = TYPE_UINT8);
FAST_CONSTRUCTOR(ImagePyramid, openslide_t*, fileHandle,, std::vector<ImagePyramidLevel>, levels,);
FAST_CONSTRUCTOR(ImagePyramid, std::ifstream*, stream,, std::vector<vsi_tile_header>, tileHeaders,, std::vector<ImagePyramidLevel>, levels,, ImageCompression, compressionFormat,);
FAST_CONSTRUCTOR(ImagePyramid, TIFF*, fileHandle,, std::vector<ImagePyramidLevel>, levels,, int, channels,,bool, isOMETIFF, = false);
Expand Down Expand Up @@ -74,6 +74,7 @@ class FAST_EXPORT ImagePyramid : public SpatialDataObject {
std::shared_ptr<NeuralNetwork> getCompressionModel() const;
std::shared_ptr<NeuralNetwork> getDecompressionModel() const;
float getDecompressionOutputScaleFactor() const;
DataType getDataType() const;
private:
ImagePyramid();
std::vector<ImagePyramidLevel> m_levels;
Expand All @@ -86,6 +87,7 @@ class FAST_EXPORT ImagePyramid : public SpatialDataObject {
std::string m_tiffPath;

int m_channels;
DataType m_dataType = TYPE_UINT8;
bool m_initialized;
/**
* Whether all patches in entire pyramid has been initialized.
Expand Down

0 comments on commit 5fcd023

Please sign in to comment.