Skip to content

Commit cc3ceb5

Browse files
committed
Fixed issues with ImagePyramidRenderer and SegmentationRenderer when used with RenderToImage
1 parent 254d65e commit cc3ceb5

File tree

5 files changed

+219
-159
lines changed

5 files changed

+219
-159
lines changed

source/FAST/Visualization/ImagePyramidRenderer/ImagePyramidRenderer.cpp

Lines changed: 99 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,88 @@ void ImagePyramidRenderer::loadAttributes() {
7676
setSharpening(getBooleanAttribute("sharpening"));
7777
}
7878

79-
void
80-
ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, float zNear, float zFar, bool mode2D,
81-
int viewWidth,
82-
int viewHeight) {
79+
int ImagePyramidRenderer::loadTileTexture(std::string tileID) {
80+
// Check if tile has been processed before
81+
if(mTexturesToRender.count(tileID) > 0)
82+
return -1;
83+
84+
//std::cout << "Loading tile " << tileID << " queue size: " << m_tileQueue.size() << std::endl;
85+
86+
// Create texture
87+
auto parts = split(tileID, "_");
88+
if(parts.size() != 3)
89+
throw Exception("incorrect tile format");
90+
91+
int level = std::stoi(parts[0]);
92+
int tile_x = std::stoi(parts[1]);
93+
int tile_y = std::stoi(parts[2]);
94+
//std::cout << "Creating texture for tile " << tile_x << " " << tile_y << " at level " << level << std::endl;
95+
Image::pointer tile;
96+
{
97+
auto access = m_input->getAccess(ACCESS_READ);
98+
try {
99+
tile = access->getPatchAsImage(level, tile_x, tile_y, false);
100+
} catch(Exception &e) {
101+
//reportWarning() << "Error occured while trying to open patch " << tile_x << " " << tile_y << reportEnd();
102+
// Tile was missing, just skip it..
103+
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
104+
mTexturesToRender[tileID] = 0;
105+
return -1;
106+
}
107+
if(m_postProcessingSharpening) {
108+
m_sharpening->setInputData(tile);
109+
tile = m_sharpening->updateAndGetOutputData<Image>();
110+
}
111+
}
112+
auto tileAccess = tile->getImageAccess(ACCESS_READ);
113+
// Copy data from CPU to GL texture
114+
GLuint textureID;
115+
glGenTextures(1, &textureID);
116+
glBindTexture(GL_TEXTURE_2D, textureID);
117+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
118+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
119+
120+
// TODO Why is this needed:
121+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
122+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
123+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
124+
125+
// WSI data from openslide is stored as ARGB, need to handle this here: BGRA and reverse
126+
if(m_input->isBGRA()) {
127+
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_BGRA,
128+
GL_UNSIGNED_BYTE,
129+
tileAccess->get());
130+
} else {
131+
if(tile->getNrOfChannels() == 3) {
132+
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, tile->getWidth(), tile->getHeight(), 0, GL_RGB,
133+
GL_UNSIGNED_BYTE,
134+
tileAccess->get());
135+
} else if(tile->getNrOfChannels() == 4) {
136+
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_RGBA,
137+
GL_UNSIGNED_BYTE,
138+
tileAccess->get());
139+
}
140+
}
141+
GLint compressedImageSize = 0;
142+
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedImageSize);
143+
glBindTexture(GL_TEXTURE_2D, 0);
144+
glFinish(); // Make sure texture is done before adding it
145+
146+
{
147+
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
148+
mTexturesToRender[tileID] = textureID;
149+
}
150+
return compressedImageSize;
151+
}
152+
153+
void ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, float zNear, float zFar, bool mode2D,
154+
int viewWidth,
155+
int viewHeight) {
83156
auto dataToRender = getDataToRender();
84157
if(dataToRender.empty())
85158
return;
86159

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

142-
// Check if tile has been processed before
143-
if(mTexturesToRender.count(tileID) > 0)
144-
continue;
145-
146-
//std::cout << "Loading tile " << tileID << " queue size: " << m_tileQueue.size() << std::endl;
147-
148-
// Create texture
149-
auto parts = split(tileID, "_");
150-
if(parts.size() != 3)
151-
throw Exception("incorrect tile format");
152-
153-
int level = std::stoi(parts[0]);
154-
int tile_x = std::stoi(parts[1]);
155-
int tile_y = std::stoi(parts[2]);
156-
//std::cout << "Creating texture for tile " << tile_x << " " << tile_y << " at level " << level << std::endl;
157-
Image::pointer tile;
158-
{
159-
auto access = m_input->getAccess(ACCESS_READ);
160-
try {
161-
tile = access->getPatchAsImage(level, tile_x, tile_y, false);
162-
} catch(Exception &e) {
163-
//reportWarning() << "Error occured while trying to open patch " << tile_x << " " << tile_y << reportEnd();
164-
// Tile was missing, just skip it..
165-
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
166-
mTexturesToRender[tileID] = 0;
167-
continue;
168-
}
169-
if(m_postProcessingSharpening) {
170-
m_sharpening->setInputData(tile);
171-
tile = m_sharpening->updateAndGetOutputData<Image>();
172-
}
173-
}
174-
auto tileAccess = tile->getImageAccess(ACCESS_READ);
175-
// Copy data from CPU to GL texture
176-
GLuint textureID;
177-
glGenTextures(1, &textureID);
178-
glBindTexture(GL_TEXTURE_2D, textureID);
179-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
180-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
181-
182-
// TODO Why is this needed:
183-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
184-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
185-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
186-
187-
// WSI data from openslide is stored as ARGB, need to handle this here: BGRA and reverse
188-
if(m_input->isBGRA()) {
189-
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_BGRA,
190-
GL_UNSIGNED_BYTE,
191-
tileAccess->get());
192-
} else {
193-
if(tile->getNrOfChannels() == 3) {
194-
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, tile->getWidth(), tile->getHeight(), 0, GL_RGB,
195-
GL_UNSIGNED_BYTE,
196-
tileAccess->get());
197-
} else if(tile->getNrOfChannels() == 4) {
198-
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, tile->getWidth(), tile->getHeight(), 0, GL_RGBA,
199-
GL_UNSIGNED_BYTE,
200-
tileAccess->get());
201-
}
202-
}
203-
GLint compressedImageSize = 0;
204-
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedImageSize);
205-
glBindTexture(GL_TEXTURE_2D, 0);
206-
glFinish(); // Make sure texture is done before adding it
207-
208-
{
209-
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
210-
mTexturesToRender[tileID] = textureID;
211-
}
212-
memoryUsage += compressedImageSize;
215+
memoryUsage += loadTileTexture(tileID);
213216
//std::cout << "Texture cache in ImagePyramidRenderer using " << (float)memoryUsage / (1024 * 1024) << " MB" << std::endl;
214217
}
215218
});
@@ -251,7 +254,7 @@ ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, f
251254
float percentageShownX = (float)width / fullWidth;
252255
float percentageShownY = (float)height / fullHeight;
253256
// With current level, do we have have enough pixels to fill the view?
254-
if(percentageShownX * levelWidth > m_view->width() && percentageShownY * levelHeight > m_view->height()) {
257+
if(percentageShownX * levelWidth > viewWidth && percentageShownY * levelHeight > viewHeight) {
255258
// If yes, stop here
256259
levelToUse = level;
257260
break;
@@ -337,26 +340,30 @@ ImagePyramidRenderer::draw(Matrix4f perspectiveMatrix, Matrix4f viewingMatrix, f
337340
))
338341
continue;
339342

340-
341-
// Is patch in cache?
342-
bool textureReady = false;
343343
uint textureID;
344-
{
345-
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
346-
textureReady = mTexturesToRender.count(tileString) > 0;
347-
}
348-
if(!textureReady) {
349-
// Add to queue if not in cache
344+
if(m_view != nullptr) {
345+
// Is patch in cache?
346+
bool textureReady = false;
350347
{
351348
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
352-
// Remove any duplicates first
353-
m_tileQueue.remove(tileString); // O(n) time complexity..
354-
m_tileQueue.push_back(tileString);
355-
//std::cout << "Added tile " << tileString << " to queue" << std::endl;
349+
textureReady = mTexturesToRender.count(tileString) > 0;
350+
}
351+
if(!textureReady) {
352+
// Add to queue if not in cache
353+
{
354+
std::lock_guard<std::mutex> lock(m_tileQueueMutex);
355+
// Remove any duplicates first
356+
m_tileQueue.remove(tileString); // O(n) time complexity..
357+
m_tileQueue.push_back(tileString);
358+
//std::cout << "Added tile " << tileString << " to queue" << std::endl;
359+
}
360+
m_queueEmptyCondition.notify_one();
361+
continue;
362+
} else {
363+
textureID = mTexturesToRender[tileString];
356364
}
357-
m_queueEmptyCondition.notify_one();
358-
continue;
359365
} else {
366+
int bytes = loadTileTexture(tileString);
360367
textureID = mTexturesToRender[tileString];
361368
}
362369

source/FAST/Visualization/ImagePyramidRenderer/ImagePyramidRenderer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class FAST_EXPORT ImagePyramidRenderer : public Renderer {
5252
std::shared_ptr<ImagePyramid> m_input;
5353

5454
void drawTextures(Matrix4f &perspectiveMatrix, Matrix4f &viewingMatrix, bool mode2D);
55+
int loadTileTexture(std::string tileID);
5556
};
5657

5758
}

source/FAST/Visualization/RenderToImage/Tests.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
#include <FAST/Visualization/VolumeRenderer/MaximumIntensityProjection.hpp>
1111
#include <FAST/Visualization/SliceRenderer/SliceRenderer.hpp>
1212
#include <FAST/Visualization/VolumeRenderer/ThresholdVolumeRenderer.hpp>
13+
#include <FAST/Visualization/ImagePyramidRenderer/ImagePyramidRenderer.hpp>
14+
#include <FAST/Importers/WholeSlideImageImporter.hpp>
15+
#include <FAST/Algorithms/ImagePatch/PatchGenerator.hpp>
16+
#include <FAST/Algorithms/TissueSegmentation/TissueSegmentation.hpp>
17+
#include <FAST/Algorithms/ImagePatch/PatchStitcher.hpp>
18+
#include <FAST/Algorithms/RunUntilFinished/RunUntilFinished.hpp>
1319

1420
using namespace fast;
1521

@@ -83,3 +89,40 @@ TEST_CASE("RenderToImage 3D geom", "[fast][RenderToImage]") {
8389
auto toImage = RenderToImage::create(Color::White(), 1024 )->connect(renderer2);
8490
ImageExporter::create("test_render_to_image_3d_geom.png")->connect(toImage)->run();
8591
}
92+
TEST_CASE("RenderToImage image pyramid", "[fast][RenderToImage]") {
93+
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");
94+
95+
auto renderer = ImagePyramidRenderer::create()->connect(importer);
96+
97+
auto toImage = RenderToImage::create()->connect(renderer);
98+
auto image = toImage->runAndGetOutputData<Image>();
99+
100+
//auto renderer2 = ImageRenderer::create()->connect(image);
101+
//SimpleWindow2D::create()->connect(renderer2)->run();
102+
103+
ImageExporter::create("test_render_to_image_image_pyramid.png")->connect(image)->run();
104+
}
105+
106+
TEST_CASE("RenderToImage image pyramid + segmentation", "[fast][RenderToImage]") {
107+
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");
108+
109+
auto generator = PatchGenerator::create(512, 512, 1, 1)->connect(importer);
110+
111+
auto segmentation = TissueSegmentation::create()->connect(generator);
112+
113+
auto stitcher = PatchStitcher::create()->connect(segmentation);
114+
115+
auto finish = RunUntilFinished::create()->connect(stitcher);
116+
117+
auto renderer = ImagePyramidRenderer::create()->connect(importer);
118+
119+
auto renderer2 = SegmentationRenderer::create()->connect(finish);
120+
121+
auto toImage = RenderToImage::create()->connect({renderer, renderer2});
122+
auto image = toImage->runAndGetOutputData<Image>();
123+
124+
//auto renderer3 = ImageRenderer::create()->connect(image);
125+
//SimpleWindow2D::create()->connect(renderer3)->run();
126+
127+
ImageExporter::create("test_render_to_image_image_pyramid_and_segmentation.png")->connect(image)->run();
128+
}

0 commit comments

Comments
 (0)