<commit_before>#include "ImagePyramidAccess.hpp"
#include <FAST/Data/ImagePyramid.hpp>
#include <FAST/Algorithms/ImageChannelConverter/ImageChannelConverter.hpp>
#include <FAST/Utility.hpp>
#include <openslide/openslide.h>
#include <tiffio.h>
#include <FAST/Data/Image.hpp>
#include <jpeglib.h>

namespace fast {

ImagePyramidAccess::ImagePyramidAccess(
        std::vector<ImagePyramidLevel> levels,
        openslide_t* fileHandle,
        TIFF* tiffHandle,
        std::ifstream* vsiHandle,
        std::vector<vsi_tile_header>& vsiTiles,
        std::shared_ptr<ImagePyramid> imagePyramid,
        bool write,
        std::unordered_set<std::string>& initializedPatchList,
        std::mutex& readMutex,
        ImageCompression compressionFormat
        ) : m_initializedPatchList(initializedPatchList), m_readMutex(readMutex) {
	if(levels.size() == 0)
		throw Exception("Image pyramid has no levels");
	m_image = imagePyramid;
	m_levels = levels;
	m_write = write;
    m_fileHandle = fileHandle;
    m_tiffHandle = tiffHandle;
    m_vsiHandle = vsiHandle;
    m_vsiTiles = vsiTiles;
    m_compressionFormat = compressionFormat;
}

void ImagePyramidAccess::release() {
	m_image->accessFinished();
}

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
    ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg );
    throw std::runtime_error( jpegLastErrorMsg );
}

void ImagePyramidAccess::readVSITileToBuffer(vsi_tile_header tile, uchar* data) {
    if(m_compressionFormat == ImageCompression::JPEG) {
        auto buffer = make_uninitialized_unique<char[]>(tile.numbytes);

        // Reading VSI tiles is not thread safe
        {
            std::lock_guard<std::mutex> lock(m_readMutex);
            m_vsiHandle->seekg(tile.offset);
            m_vsiHandle->read(buffer.get(), tile.numbytes);
        }
        jpeg_decompress_struct cinfo;
        jpeg_error_mgr jerr; //error handling
        jpeg_source_mgr src_mem;
        jerr.error_exit = jpegErrorExit;
        cinfo.err = jpeg_std_error(&jerr);
        try {
            jpeg_create_decompress(&cinfo);
            jpeg_mem_src(&cinfo, (uchar*)buffer.get(), tile.numbytes);
            int ret = jpeg_read_header(&cinfo, false);
            if(ret != 1) {
                throw Exception("Jpeg error..");
            }
            //cinfo.jpeg_color_space = JCS_YCbCr;
            //cinfo.jpeg_color_space = JCS_RGB;
            jpeg_start_decompress(&cinfo);
            unsigned char* line = data;
            while (cinfo.output_scanline < cinfo.output_height) {
                jpeg_read_scanlines (&cinfo, &line, 1);
                line += 3*cinfo.output_width;
            }
            jpeg_finish_decompress(&cinfo);
            jpeg_destroy_decompress(&cinfo);
        } catch(std::exception &e) {
            jpeg_destroy_decompress( &cinfo );
            throw Exception("JPEG error: " + std::string(e.what())); // or return an error code
        }
    } else if(m_compressionFormat == ImageCompression::RAW) { // Uncompressed
        // Reading VSI tiles is not thread safe
        {
            auto buffer = make_uninitialized_unique<char[]>(tile.numbytes);
            std::lock_guard<std::mutex> lock(m_readMutex);
            m_vsiHandle->seekg(tile.offset);
            m_vsiHandle->read(buffer.get(), tile.numbytes);
            // Data is stored as BGR, convert it to RGB
            // TODO could optimize this by doing it on the GPU instead..
            for(int i = 0; i < tile.numbytes/3; ++i) {
                data[i*3 + 0] = buffer[i*3+2];
                data[i*3 + 1] = buffer[i*3+1];
                data[i*3 + 2] = buffer[i*3+0];
            }
        }
    } else {
        throw Exception("Unknown image compression format in ImagePyramidAccess::readVSITileToBuffer: " + std::to_string((int)m_compressionFormat));
    }
}

std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(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);
    if(m_tiffHandle != nullptr) {
        // Read all tiles within region, then crop
        if(!isPatchInitialized(level, x, y)) {
            // Tile has not be initialized, fill with zeros and return..
            // TODO Do not try render these patches..
            std::memset(data.get(), 0, width*height*channels);
            return data;
        }
        std::lock_guard<std::mutex> lock(m_readMutex);
        if(m_image->isOMETIFF()) {
            if(level == 0) {
                TIFFSetDirectory(m_tiffHandle, level);
            } else {
                TIFFSetSubDirectory(m_tiffHandle, m_levels[level].offset);
            }
        } else {
            TIFFSetDirectory(m_tiffHandle, level);
        }
        if(width == tileWidth && height == tileHeight && x % tileWidth == 0 && y % tileHeight == 0) {
            // From TIFFReadTile documentation: Return the data for the tile containing the specified coordinates.
            int bytesRead = TIFFReadTile(m_tiffHandle, (void *) data.get(), x, y, 0, 0);
        } else if((width < tileWidth || height < tileHeight) && x % tileWidth == 0 && y % tileHeight == 0) {
            auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*channels);
            {
                // 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..
                int bytesRead = TIFFReadTile(m_tiffHandle, (void *) tileData.get(), x, y, 0, 0);
            }
            // Remove extra
            for(int dy = 0; dy < height; ++dy) {
                for(int dx = 0; dx < width; ++dx) {
                    data[dx + dy*width] = tileData[dx + dy*tileWidth];
                }
            }
        } else {
            // Create buffer to contain all tiles
            int targetNumberOfTiles = std::ceil((float)width / tileWidth)*std::ceil((float)height / tileHeight);
            auto fullTileBuffer = make_uninitialized_unique<uchar[]>(tileWidth*tileHeight*targetNumberOfTiles*channels);
            // Does the buffer need to be initialized/padded?
            if(x+width >= levelWidth || x+height >= levelHeight) { // Some tiles are outside, fill it
                if(channels > 1) {
                    std::memset(fullTileBuffer.get(), 255, tileWidth*tileHeight*targetNumberOfTiles*channels);
                } else {
                    std::memset(fullTileBuffer.get(), 0, tileWidth*tileHeight*targetNumberOfTiles*channels);
                }
            }
            // Fill the full tile buffer
            int fullTileBufferWidth = std::ceil((float)width/tileWidth)*tileWidth;
            for(int i = 0; i < std::ceil((float)width/tileWidth); ++i) {
                for(int j = 0; j < std::ceil((float)height/tileHeight); ++j) {
                    auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*channels);
                    int tileX = i*tileWidth;
                    int tileY = j*tileHeight;
                    int bytesRead = TIFFReadTile(m_tiffHandle, (void *) tileData.get(), x+tileX, y+tileY, 0, 0);
                    // 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];
                            }<commit_msg>Fixed bug when reading TIFF data with channels > 1<commit_after>#include "ImagePyramidAccess.hpp"
#include <FAST/Data/ImagePyramid.hpp>
#include <FAST/Algorithms/ImageChannelConverter/ImageChannelConverter.hpp>
#include <FAST/Utility.hpp>
#include <openslide/openslide.h>
#include <tiffio.h>
#include <FAST/Data/Image.hpp>
#include <jpeglib.h>

namespace fast {

ImagePyramidAccess::ImagePyramidAccess(
        std::vector<ImagePyramidLevel> levels,
        openslide_t* fileHandle,
        TIFF* tiffHandle,
        std::ifstream* vsiHandle,
        std::vector<vsi_tile_header>& vsiTiles,
        std::shared_ptr<ImagePyramid> imagePyramid,
        bool write,
        std::unordered_set<std::string>& initializedPatchList,
        std::mutex& readMutex,
        ImageCompression compressionFormat
        ) : m_initializedPatchList(initializedPatchList), m_readMutex(readMutex) {
	if(levels.size() == 0)
		throw Exception("Image pyramid has no levels");
	m_image = imagePyramid;
	m_levels = levels;
	m_write = write;
    m_fileHandle = fileHandle;
    m_tiffHandle = tiffHandle;
    m_vsiHandle = vsiHandle;
    m_vsiTiles = vsiTiles;
    m_compressionFormat = compressionFormat;
}

void ImagePyramidAccess::release() {
	m_image->accessFinished();
}

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
    ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg );
    throw std::runtime_error( jpegLastErrorMsg );
}

void ImagePyramidAccess::readVSITileToBuffer(vsi_tile_header tile, uchar* data) {
    if(m_compressionFormat == ImageCompression::JPEG) {
        auto buffer = make_uninitialized_unique<char[]>(tile.numbytes);

        // Reading VSI tiles is not thread safe
        {
            std::lock_guard<std::mutex> lock(m_readMutex);
            m_vsiHandle->seekg(tile.offset);
            m_vsiHandle->read(buffer.get(), tile.numbytes);
        }
        jpeg_decompress_struct cinfo;
        jpeg_error_mgr jerr; //error handling
        jpeg_source_mgr src_mem;
        jerr.error_exit = jpegErrorExit;
        cinfo.err = jpeg_std_error(&jerr);
        try {
            jpeg_create_decompress(&cinfo);
            jpeg_mem_src(&cinfo, (uchar*)buffer.get(), tile.numbytes);
            int ret = jpeg_read_header(&cinfo, false);
            if(ret != 1) {
                throw Exception("Jpeg error..");
            }
            //cinfo.jpeg_color_space = JCS_YCbCr;
            //cinfo.jpeg_color_space = JCS_RGB;
            jpeg_start_decompress(&cinfo);
            unsigned char* line = data;
            while (cinfo.output_scanline < cinfo.output_height) {
                jpeg_read_scanlines (&cinfo, &line, 1);
                line += 3*cinfo.output_width;
            }
            jpeg_finish_decompress(&cinfo);
            jpeg_destroy_decompress(&cinfo);
        } catch(std::exception &e) {
            jpeg_destroy_decompress( &cinfo );
            throw Exception("JPEG error: " + std::string(e.what())); // or return an error code
        }
    } else if(m_compressionFormat == ImageCompression::RAW) { // Uncompressed
        // Reading VSI tiles is not thread safe
        {
            auto buffer = make_uninitialized_unique<char[]>(tile.numbytes);
            std::lock_guard<std::mutex> lock(m_readMutex);
            m_vsiHandle->seekg(tile.offset);
            m_vsiHandle->read(buffer.get(), tile.numbytes);
            // Data is stored as BGR, convert it to RGB
            // TODO could optimize this by doing it on the GPU instead..
            for(int i = 0; i < tile.numbytes/3; ++i) {
                data[i*3 + 0] = buffer[i*3+2];
                data[i*3 + 1] = buffer[i*3+1];
                data[i*3 + 2] = buffer[i*3+0];
            }
        }
    } else {
        throw Exception("Unknown image compression format in ImagePyramidAccess::readVSITileToBuffer: " + std::to_string((int)m_compressionFormat));
    }
}

std::unique_ptr<uchar[]> ImagePyramidAccess::getPatchData(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);
    if(m_tiffHandle != nullptr) {
        // Read all tiles within region, then crop
        if(!isPatchInitialized(level, x, y)) {
            // Tile has not be initialized, fill with zeros and return..
            // TODO Do not try render these patches..
            std::memset(data.get(), 0, width*height*channels);
            return data;
        }
        std::lock_guard<std::mutex> lock(m_readMutex);
        if(m_image->isOMETIFF()) {
            if(level == 0) {
                TIFFSetDirectory(m_tiffHandle, level);
            } else {
                TIFFSetSubDirectory(m_tiffHandle, m_levels[level].offset);
            }
        } else {
            TIFFSetDirectory(m_tiffHandle, level);
        }
        if(width == tileWidth && height == tileHeight && x % tileWidth == 0 && y % tileHeight == 0) {
            // From TIFFReadTile documentation: Return the data for the tile containing the specified coordinates.
            int bytesRead = TIFFReadTile(m_tiffHandle, (void *) data.get(), x, y, 0, 0);
        } else if((width < tileWidth || height < tileHeight) && x % tileWidth == 0 && y % tileHeight == 0) {
            auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*channels);
            {
                // 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..
                int bytesRead = TIFFReadTile(m_tiffHandle, (void *) tileData.get(), x, y, 0, 0);
            }
            // Remove extra
            for(int dy = 0; dy < height; ++dy) {
                for(int dx = 0; dx < width; ++dx) {
		    for(int channel = 0; channel < channels; ++channel) {
                        data[(dx + dy*width)*channels + channel] = tileData[(dx + dy*tileWidth)*channels + channel];
		    }
                }
            }
        } else {
            // Create buffer to contain all tiles
            int targetNumberOfTiles = std::ceil((float)width / tileWidth)*std::ceil((float)height / tileHeight);
            auto fullTileBuffer = make_uninitialized_unique<uchar[]>(tileWidth*tileHeight*targetNumberOfTiles*channels);
            // Does the buffer need to be initialized/padded?
            if(x+width >= levelWidth || x+height >= levelHeight) { // Some tiles are outside, fill it
                if(channels > 1) {
                    std::memset(fullTileBuffer.get(), 255, tileWidth*tileHeight*targetNumberOfTiles*channels);
                } else {
                    std::memset(fullTileBuffer.get(), 0, tileWidth*tileHeight*targetNumberOfTiles*channels);
                }
            }
            // Fill the full tile buffer
            int fullTileBufferWidth = std::ceil((float)width/tileWidth)*tileWidth;
            for(int i = 0; i < std::ceil((float)width/tileWidth); ++i) {
                for(int j = 0; j < std::ceil((float)height/tileHeight); ++j) {
                    auto tileData = std::make_unique<uchar[]>(tileWidth*tileHeight*channels);
                    int tileX = i*tileWidth;
                    int tileY = j*tileHeight;
                    int bytesRead = TIFFReadTile(m_tiffHandle, (void *) tileData.get(), x+tileX, y+tileY, 0, 0);
                    // 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];
                            }
{'fraction_non_alphanumeric': 0.11954909522396914, 'fraction_numerical': 0.005576980124592109, 'mean_word_length': 2.384738955823293, 'pattern_counts': {'":': 0, '<': 68, '<?xml version=': 0, '>': 78, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3444', 'n_tokens_mistral': 5174, 'n_tokens_neox': 5009, 'n_words': 1098}