Skip to content

Commit 254d65e

Browse files
committed
Enabled PatchGenerator to run on any magnification
as long as it is lower than the magnification of level 0 in the image pyramid.
1 parent 6d2c81c commit 254d65e

File tree

5 files changed

+107
-23
lines changed

5 files changed

+107
-23
lines changed

source/FAST/Algorithms/ImagePatch/PatchGenerator.cpp

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <FAST/Data/ImagePyramid.hpp>
22
#include <FAST/Data/Image.hpp>
3+
#include <FAST/Algorithms/ImageResizer/ImageResizer.hpp>
34
#include "PatchGenerator.hpp"
45

56
namespace fast {
@@ -20,13 +21,13 @@ PatchGenerator::PatchGenerator() {
2021

2122
createIntegerAttribute("patch-size", "Patch size", "", 0);
2223
createIntegerAttribute("patch-level", "Patch level", "Patch level used for image pyramid inputs", m_level);
23-
createIntegerAttribute("patch-magnification", "Patch magnification", "Patch magnification to be used for image pyramid inputs", m_magnification);
24+
createFloatAttribute("patch-magnification", "Patch magnification", "Patch magnification to be used for image pyramid inputs", m_magnification);
2425
createFloatAttribute("patch-overlap", "Patch overlap", "Patch overlap in percent", m_overlapPercent);
2526
createFloatAttribute("mask-threshold", "Mask threshold", "Threshold, in percent, for how much of the candidate patch must be inside the mask to be accepted", m_maskThreshold);
2627
createIntegerAttribute("padding-value", "Padding value", "Value to pad patches with when out-of-bounds. Default is negative, meaning it will use (white)255 for color images, and (black)0 for grayscale images", m_paddingValue);
2728
}
2829

29-
PatchGenerator::PatchGenerator(int width, int height, int depth, int level, int magnification, float percent, float maskThreshold, int paddingValue) : PatchGenerator() {
30+
PatchGenerator::PatchGenerator(int width, int height, int depth, int level, float magnification, float percent, float maskThreshold, int paddingValue) : PatchGenerator() {
3031
setPatchSize(width, height, depth);
3132
setPatchLevel(level);
3233
setOverlap(percent);
@@ -82,9 +83,34 @@ void PatchGenerator::generateStream() {
8283
throw Exception("Patch size must be dividable by 2");
8384

8485
int level = m_level;
86+
float resampleFactor = 1.0f;
8587
if(m_magnification > 0) {
86-
level = m_inputImagePyramid->getLevelForMagnification(m_magnification);
87-
reportInfo() << "Choose level " << level << " for image pyramid for magnification " << m_magnification << reportEnd();
88+
try {
89+
level = m_inputImagePyramid->getLevelForMagnification(m_magnification);
90+
reportInfo() << "Choose level " << level << " for image pyramid for magnification " << m_magnification << reportEnd();
91+
} catch(Exception &e) {
92+
// Magnification level not available
93+
// Have to sample for a higher level if possible
94+
reportWarning() << "Requested magnification level does not exist in image pyramid. " <<
95+
"Will now try to sample from a lower level and resize. This may increase runtime." << reportEnd();
96+
// First find level which is larger than request magnification
97+
float targetSpacing = 0.00025f * (40.0f / (float)m_magnification);
98+
level = 0;
99+
float level0spacing = m_inputImagePyramid->getSpacing().x();
100+
for(int i = 0; i < m_inputImagePyramid->getNrOfLevels(); ++i) {
101+
float levelSpacing = m_inputImagePyramid->getLevelScale(i)*level0spacing;
102+
level = i;
103+
resampleFactor = targetSpacing / levelSpacing; // Scale between level and the magnification level we want
104+
if(i+1 < m_inputImagePyramid->getNrOfLevels() &&
105+
m_inputImagePyramid->getLevelScale(i+1)*level0spacing > targetSpacing) {
106+
break;
107+
}
108+
}
109+
if(level < 0)
110+
throw Exception("Unable to generate patches for magnification level " +
111+
std::to_string(m_magnification) + " because level 0 was at a lower magnification ");
112+
reportInfo() << "Sampling patches from level " << level << " and using a resampling factor of " << resampleFactor << reportEnd();
113+
}
88114
}
89115

90116
const int levelWidth = m_inputImagePyramid->getLevelWidth(level);
@@ -106,19 +132,19 @@ void PatchGenerator::generateStream() {
106132
for(int patchY = 0; patchY < patchesY; ++patchY) {
107133
for(int patchX = 0; patchX < patchesX; ++patchX) {
108134
mRuntimeManager->startRegularTimer("create patch");
109-
int patchWidth = m_width;
110-
if(patchX*patchWidthWithoutOverlap + patchWidth - overlapInPixelsX >= levelWidth) {
111-
patchWidth = levelWidth - patchX * patchWidthWithoutOverlap + overlapInPixelsX;
135+
int patchWidth = m_width*resampleFactor;
136+
if(patchWidth + (patchX*patchWidthWithoutOverlap - overlapInPixelsX)*resampleFactor >= levelWidth) {
137+
patchWidth = levelWidth - (patchX * patchWidthWithoutOverlap - overlapInPixelsX)*resampleFactor;
112138
}
113-
int patchHeight = m_height;
114-
if(patchY*patchHeightWithoutOverlap + patchHeight - overlapInPixelsY >= levelHeight) {
115-
patchHeight = levelHeight - patchY * patchHeightWithoutOverlap + overlapInPixelsY;
139+
int patchHeight = m_height*resampleFactor;
140+
if(patchHeight + (patchY*patchHeightWithoutOverlap - overlapInPixelsY)*resampleFactor >= levelHeight) {
141+
patchHeight = levelHeight - (patchY * patchHeightWithoutOverlap - overlapInPixelsY)*resampleFactor;
116142
}
117-
int patchOffsetX = patchX * patchWidthWithoutOverlap - overlapInPixelsX;
143+
int patchOffsetX = (patchX * patchWidthWithoutOverlap - overlapInPixelsX)*resampleFactor;
118144
if(patchX == 0 && overlapInPixelsX > 0) {
119145
patchOffsetX = 0;
120146
}
121-
int patchOffsetY = patchY * patchHeightWithoutOverlap - overlapInPixelsY;
147+
int patchOffsetY = (patchY * patchHeightWithoutOverlap - overlapInPixelsY)*resampleFactor;
122148
if(patchY == 0 && overlapInPixelsY > 0) {
123149
patchOffsetY = 0;
124150
}
@@ -165,8 +191,12 @@ void PatchGenerator::generateStream() {
165191
paddingValue = 0;
166192
}
167193
}
168-
if(patch->getWidth() != m_width || patch->getHeight() != m_height) {
169-
patch = patch->crop(Vector2i(0, 0), Vector2i(m_width, m_height), true, paddingValue);
194+
if(patch->getWidth() != (int)(m_width*resampleFactor) || patch->getHeight() != (int)(m_height*resampleFactor)) {
195+
// Edge cases, patches may not be the target patch size. Need to pad.
196+
patch = patch->crop(Vector2i(0, 0), Vector2i(m_width*resampleFactor, m_height*resampleFactor), true, paddingValue);
197+
}
198+
if(resampleFactor > 1.0f) {
199+
patch = ImageResizer::create(m_width, m_height)->connect(patch)->runAndGetOutputData<Image>();
170200
}
171201
if(m_overlapPercent > 0.0f && (patchX == 0 || patchY == 0)) {
172202
int offsetX = patchX == 0 ? -overlapInPixelsX : 0;
@@ -369,7 +399,7 @@ void PatchGenerator::setPaddingValue(int paddingValue) {
369399
setModified(true);
370400
}
371401

372-
void PatchGenerator::setPatchMagnification(int magnification) {
402+
void PatchGenerator::setPatchMagnification(float magnification) {
373403
m_magnification = magnification;
374404
setModified(true);
375405
}

source/FAST/Algorithms/ImagePatch/PatchGenerator.hpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ class FAST_EXPORT PatchGenerator : public Streamer {
3131
* @param level Which level of an ImagePyramid to generate patches from.
3232
* @param magnification Which magnification to extract patches from.
3333
* Setting this value for instance to 20, will trigger a search through all levels
34-
* to find the image pyramid level which is closest to 20X magnification, 0.0005 mm pixel spacing.
35-
* If no such level exist an exception is thrown.
34+
* to find an image pyramid level which is close to 20X magnification (0.0005 mm pixel spacing).
35+
* If such a level doesn't exist, FAST will look for a higher magnification level (e.g. 40X) and then
36+
* resize 40X patches to create 20X patches. This will off course come at an increased runtime cost.
3637
* This parameter overrides the level parameter.
3738
* @param overlapPercent Amount of patch overlap in percent.
3839
* @param maskThreshold Threshold to accept a patch if the additional mask is provided.
@@ -45,7 +46,7 @@ class FAST_EXPORT PatchGenerator : public Streamer {
4546
int, height,,
4647
int, depth, = 1,
4748
int, level, = 0,
48-
int, magnification, = -1,
49+
float, magnification, = -1,
4950
float, overlapPercent, = 0.0f,
5051
float, maskThreshold, = 0.5f,
5152
int, paddingValue, = -1
@@ -57,7 +58,7 @@ class FAST_EXPORT PatchGenerator : public Streamer {
5758
*/
5859
void setOverlap(float percent);
5960
void setPatchLevel(int level);
60-
void setPatchMagnification(int magnification);
61+
void setPatchMagnification(float magnification);
6162
void setMaskThreshold(float percent);
6263
void setPaddingValue(int paddingValue);
6364
~PatchGenerator();
@@ -72,7 +73,7 @@ class FAST_EXPORT PatchGenerator : public Streamer {
7273
float m_overlapPercent = 0;
7374
float m_maskThreshold = 0.5;
7475
int m_paddingValue = -1;
75-
int m_magnification = -1;
76+
float m_magnification = -1;
7677
float m_progress = 0.0f;
7778

7879
std::shared_ptr<ImagePyramid> m_inputImagePyramid;

source/FAST/Algorithms/ImagePatch/Tests.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,57 @@ TEST_CASE("Patch generator, sticher and image to batch generator for WSI", "[fas
164164
std::cout << "Got a batch" << std::endl;
165165
}
166166
std::cout << "Done" << std::endl;
167-
}
167+
}
168+
169+
/*
170+
TEST_CASE("Patch generator for WSI wrong magnification", "[fast][wsi][PatchGenerator]") {
171+
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");
172+
auto wsi = importer->runAndGetOutputData<ImagePyramid>();
173+
174+
auto generator = PatchGenerator::create(512, 512, 1, 0, 40)
175+
->connect(wsi);
176+
// TODO Exception is thrown in thread..
177+
//REQUIRE_THROWS(generator->run());
178+
}
179+
*/
180+
181+
TEST_CASE("Patch generator for WSI at specific magnification 2.5x", "[fast][wsi][PatchGenerator]") {
182+
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");
183+
auto wsi = importer->runAndGetOutputData<ImagePyramid>();
184+
185+
int width = 512;
186+
int height = 512;
187+
const int nrOfPatches = std::ceil((float)wsi->getLevelWidth(1)/width/2.0f)*std::ceil((float)wsi->getLevelHeight(1)/height/2.0f);
188+
auto generator = PatchGenerator::create(width, height, 1, 0, 2.5f)
189+
->connect(wsi);
190+
auto stream = DataStream(generator);
191+
int counter = 0;
192+
while(!stream.isDone()) {
193+
auto image = stream.getNextFrame<Image>();
194+
REQUIRE(image->getWidth() == width);
195+
REQUIRE(image->getHeight() == height);
196+
++counter;
197+
}
198+
REQUIRE(counter == nrOfPatches);
199+
}
200+
201+
TEST_CASE("Patch generator for WSI at specific magnification 1.25x", "[fast][wsi][PatchGenerator]") {
202+
auto importer = WholeSlideImageImporter::create(Config::getTestDataPath() + "/WSI/CMU-1.svs");
203+
auto wsi = importer->runAndGetOutputData<ImagePyramid>();
204+
205+
int width = 512;
206+
int height = 512;
207+
auto generator = PatchGenerator::create(width, height, 1, 0, 1.25f)
208+
->connect(wsi);
209+
auto stream = DataStream(generator);
210+
int counter = 0;
211+
int level = 2;
212+
const int nrOfPatches = std::ceil((float)wsi->getLevelWidth(level)/width)*std::ceil((float)wsi->getLevelHeight(level)/height);
213+
while(!stream.isDone()) {
214+
auto image = stream.getNextFrame<Image>();
215+
REQUIRE(image->getWidth() == width);
216+
REQUIRE(image->getHeight() == height);
217+
++counter;
218+
}
219+
REQUIRE(counter == nrOfPatches);
220+
}

source/FAST/Data/ImagePyramid.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ DataBoundingBox ImagePyramid::getBoundingBox() const {
451451
return SpatialDataObject::getBoundingBox().getTransformedBoundingBox(T);
452452
}
453453

454-
int ImagePyramid::getLevelForMagnification(int magnification, float slackPercentage) {
454+
int ImagePyramid::getLevelForMagnification(float magnification, float slackPercentage) {
455455
if(magnification <= 0)
456456
throw Exception("Magnification must be larger than 0 in getLevleForMagnification");
457457
const Vector3f spacing = getSpacing();

source/FAST/Data/ImagePyramid.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class FAST_EXPORT ImagePyramid : public SpatialDataObject {
4141
* If distance between closest level and target magnification is larger than this, an exception is thrown.
4242
* @return level
4343
*/
44-
int getLevelForMagnification(int magnification, float slackPercentage = 0.5f);
44+
int getLevelForMagnification(float magnification, float slackPercentage = 0.5f);
4545
int getFullWidth();
4646
int getFullHeight();
4747
int getNrOfChannels() const;

0 commit comments

Comments
 (0)