Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed issues with ImagePyramidRenderer and SegmentationRenderer when used with RenderToImage #201

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,88 @@ void ImagePyramidRenderer::loadAttributes() {
setSharpening(getBooleanAttribute("sharpening"));
}

void
ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, float zNear, float zFar, bool mode2D,
int viewWidth,
int viewHeight) {
int ImagePyramidRenderer::loadTileTexture(std::string tileID) {
// Check if tile has been processed before
if(mTexturesToRender.count(tileID) > 0)
return -1;

//std::cout << "Loading tile " << tileID << " queue size: " << m_tileQueue.size() << std::endl;

// Create texture
auto parts = split(tileID, "_");
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]);
//std::cout << "Creating texture for tile " << tile_x << " " << tile_y << " at level " << level << std::endl;
Image::pointer tile;
{
auto access = m_input->getAccess(ACCESS_READ);
try {
tile = access->getPatchAsImage(level, tile_x, tile_y, false);
} catch(Exception &e) {
//reportWarning() << "Error occured while trying to open patch " << tile_x << " " << tile_y << reportEnd();
// Tile was missing, just skip it..
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
mTexturesToRender[tileID] = 0;
return -1;
}
if(m_postProcessingSharpening) {
m_sharpening->setInputData(tile);
tile = m_sharpening->updateAndGetOutputData<Image>();
}
}
auto tileAccess = tile->getImageAccess(ACCESS_READ);
// Copy data from CPU to GL texture
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

// TODO Why is this needed:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

// WSI data from openslide is stored as ARGB, need to handle this here: BGRA and reverse
if(m_input->isBGRA()) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_BGRA,
GL_UNSIGNED_BYTE,
tileAccess->get());
} else {
if(tile->getNrOfChannels() == 3) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, tile->getWidth(), tile->getHeight(), 0, GL_RGB,
GL_UNSIGNED_BYTE,
tileAccess->get());
} else if(tile->getNrOfChannels() == 4) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE,
tileAccess->get());
}
}
GLint compressedImageSize = 0;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedImageSize);
glBindTexture(GL_TEXTURE_2D, 0);
glFinish(); // Make sure texture is done before adding it

{
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
mTexturesToRender[tileID] = textureID;
}
return compressedImageSize;
}

void ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, float zNear, float zFar, bool mode2D,
int viewWidth,
int viewHeight) {
auto dataToRender = getDataToRender();
if(dataToRender.empty())
return;

if(!m_bufferThread) {
if(!m_bufferThread && m_view != nullptr) {
// Create thread to load patches
// Create a GL context for the thread which is sharing with the context of the view
auto context = new QGLContext(View::getGLFormat(), m_view);
Expand Down Expand Up @@ -139,77 +212,7 @@ ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, f
m_tileQueue.pop_back();
}

// Check if tile has been processed before
if(mTexturesToRender.count(tileID) > 0)
continue;

//std::cout << "Loading tile " << tileID << " queue size: " << m_tileQueue.size() << std::endl;

// Create texture
auto parts = split(tileID, "_");
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]);
//std::cout << "Creating texture for tile " << tile_x << " " << tile_y << " at level " << level << std::endl;
Image::pointer tile;
{
auto access = m_input->getAccess(ACCESS_READ);
try {
tile = access->getPatchAsImage(level, tile_x, tile_y, false);
} catch(Exception &e) {
//reportWarning() << "Error occured while trying to open patch " << tile_x << " " << tile_y << reportEnd();
// Tile was missing, just skip it..
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
mTexturesToRender[tileID] = 0;
continue;
}
if(m_postProcessingSharpening) {
m_sharpening->setInputData(tile);
tile = m_sharpening->updateAndGetOutputData<Image>();
}
}
auto tileAccess = tile->getImageAccess(ACCESS_READ);
// Copy data from CPU to GL texture
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

// TODO Why is this needed:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

// WSI data from openslide is stored as ARGB, need to handle this here: BGRA and reverse
if(m_input->isBGRA()) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_BGRA,
GL_UNSIGNED_BYTE,
tileAccess->get());
} else {
if(tile->getNrOfChannels() == 3) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, tile->getWidth(), tile->getHeight(), 0, GL_RGB,
GL_UNSIGNED_BYTE,
tileAccess->get());
} else if(tile->getNrOfChannels() == 4) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE,
tileAccess->get());
}
}
GLint compressedImageSize = 0;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedImageSize);
glBindTexture(GL_TEXTURE_2D, 0);
glFinish(); // Make sure texture is done before adding it

{
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
mTexturesToRender[tileID] = textureID;
}
memoryUsage += compressedImageSize;
memoryUsage += loadTileTexture(tileID);
//std::cout << "Texture cache in ImagePyramidRenderer using " << (float)memoryUsage / (1024 * 1024) << " MB" << std::endl;
}
});
Expand Down Expand Up @@ -251,7 +254,7 @@ ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, f
float percentageShownX = (float)width / fullWidth;
float percentageShownY = (float)height / fullHeight;
// With current level, do we have have enough pixels to fill the view?
if(percentageShownX * levelWidth > m_view->width() && percentageShownY * levelHeight > m_view->height()) {
if(percentageShownX * levelWidth > viewWidth && percentageShownY * levelHeight > viewHeight) {
// If yes, stop here
levelToUse = level;
break;
Expand Down Expand Up @@ -337,26 +340,30 @@ ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, f
))
continue;


// Is patch in cache?
bool textureReady = false;
uint textureID;
{
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
textureReady = mTexturesToRender.count(tileString) > 0;
}
if(!textureReady) {
// Add to queue if not in cache
if(m_view != nullptr) {
// Is patch in cache?
bool textureReady = false;
{
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
// Remove any duplicates first
m_tileQueue.remove(tileString); // O(n) time complexity..
m_tileQueue.push_back(tileString);
//std::cout << "Added tile " << tileString << " to queue" << std::endl;
textureReady = mTexturesToRender.count(tileString) > 0;
}
if(!textureReady) {
// Add to queue if not in cache
{
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
// Remove any duplicates first
m_tileQueue.remove(tileString); // O(n) time complexity..
m_tileQueue.push_back(tileString);
//std::cout << "Added tile " << tileString << " to queue" << std::endl;
}
m_queueEmptyCondition.notify_one();
continue;
} else {
textureID = mTexturesToRender[tileString];
}
m_queueEmptyCondition.notify_one();
continue;
} else {
int bytes = loadTileTexture(tileString);
textureID = mTexturesToRender[tileString];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class FAST_EXPORT ImagePyramidRenderer : public Renderer {
std::shared_ptr<ImagePyramid> m_input;

void drawTextures(Matrix4f &perspectiveMatrix, Matrix4f &viewingMatrix, bool mode2D);
int loadTileTexture(std::string tileID);
};

}
43 changes: 43 additions & 0 deletions source/FAST/Visualization/RenderToImage/Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
#include <FAST/Visualization/VolumeRenderer/MaximumIntensityProjection.hpp>
#include <FAST/Visualization/SliceRenderer/SliceRenderer.hpp>
#include <FAST/Visualization/VolumeRenderer/ThresholdVolumeRenderer.hpp>
#include <FAST/Visualization/ImagePyramidRenderer/ImagePyramidRenderer.hpp>
#include <FAST/Importers/WholeSlideImageImporter.hpp>
#include <FAST/Algorithms/ImagePatch/PatchGenerator.hpp>
#include <FAST/Algorithms/TissueSegmentation/TissueSegmentation.hpp>
#include <FAST/Algorithms/ImagePatch/PatchStitcher.hpp>
#include <FAST/Algorithms/RunUntilFinished/RunUntilFinished.hpp>

using namespace fast;

Expand Down Expand Up @@ -83,3 +89,40 @@ TEST_CASE("RenderToImage 3D geom", "[fast][RenderToImage]") {
auto toImage = RenderToImage::create(Color::White(), 1024 )->connect(renderer2);
ImageExporter::create("test_render_to_image_3d_geom.png")->connect(toImage)->run();
}
TEST_CASE("RenderToImage image pyramid", "[fast][RenderToImage]") {
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");

auto renderer = ImagePyramidRenderer::create()->connect(importer);

auto toImage = RenderToImage::create()->connect(renderer);
auto image = toImage->runAndGetOutputData<Image>();

//auto renderer2 = ImageRenderer::create()->connect(image);
//SimpleWindow2D::create()->connect(renderer2)->run();

ImageExporter::create("test_render_to_image_image_pyramid.png")->connect(image)->run();
}

TEST_CASE("RenderToImage image pyramid + segmentation", "[fast][RenderToImage]") {
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");

auto generator = PatchGenerator::create(512, 512, 1, 1)->connect(importer);

auto segmentation = TissueSegmentation::create()->connect(generator);

auto stitcher = PatchStitcher::create()->connect(segmentation);

auto finish = RunUntilFinished::create()->connect(stitcher);

auto renderer = ImagePyramidRenderer::create()->connect(importer);

auto renderer2 = SegmentationRenderer::create()->connect(finish);

auto toImage = RenderToImage::create()->connect({renderer, renderer2});
auto image = toImage->runAndGetOutputData<Image>();

//auto renderer3 = ImageRenderer::create()->connect(image);
//SimpleWindow2D::create()->connect(renderer3)->run();

ImageExporter::create("test_render_to_image_image_pyramid_and_segmentation.png")->connect(image)->run();
}
Loading
Loading