Skip to content

Commit 7ef735b

Browse files
committed
ENH: support multi-dimensional tile configuration. Closes #127.
* Delegate 2D parsing and writing to template nD variants * Remove parseRow * Update tests to work with new parsing code 3D test has some negative pixels due to non-linear interpolation. If we cast it to unsigned short, some pixels have maximum brightness. Therefore add signed short as supported pixel type in the test.
1 parent 05d428f commit 7ef735b

13 files changed

+761
-612
lines changed

include/itkNMinimaMaximaImageCalculator.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ class ITK_TEMPLATE_EXPORT NMinimaMaximaImageCalculator : public Object
145145

146146
RegionType m_Region;
147147
bool m_RegionSetByUser = false;
148-
bool m_ComputeMaxima;
149-
bool m_ComputeMinima;
148+
bool m_ComputeMaxima = true;
149+
bool m_ComputeMinima = true;
150150
std::mutex m_Mutex;
151151
};
152152
} // end namespace itk

include/itkTileConfiguration.h

+294-1
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,20 @@
2525
#include <vector>
2626

2727
#include "itkPoint.h"
28+
#include "itkSize.h"
29+
30+
// move to .hxx file
31+
#include "double-conversion/double-conversion.h"
32+
33+
#include <cassert>
34+
#include <fstream>
35+
#include <limits>
36+
#include <sstream>
2837

2938
namespace itk
3039
{
3140
template <unsigned Dimension>
32-
struct Tile
41+
struct ITK_TEMPLATE_EXPORT Tile
3342
{
3443
using PointType = Point<double, Dimension>;
3544

@@ -38,6 +47,290 @@ struct Tile
3847
std::string FileName;
3948
};
4049

50+
51+
template <unsigned Dimension>
52+
class ITK_TEMPLATE_EXPORT TileConfiguration
53+
{
54+
public:
55+
using PointType = typename Tile<Dimension>::PointType;
56+
using TileIndexType = Size<Dimension>;
57+
using TileND = Tile<Dimension>;
58+
59+
TileIndexType AxisSizes;
60+
61+
std::vector<TileND> Tiles;
62+
63+
size_t
64+
LinearSize() const
65+
{
66+
size_t linearSize = 1u;
67+
for (unsigned d = 0; d < Dimension; d++)
68+
{
69+
linearSize *= AxisSizes[d];
70+
}
71+
return linearSize;
72+
}
73+
74+
size_t
75+
nDIndexToLinearIndex(TileIndexType nDIndex) const
76+
{
77+
size_t ind = 0;
78+
SizeValueType stride = 1u;
79+
for (unsigned d = 0; d < Dimension; d++)
80+
{
81+
itkAssertOrThrowMacro(nDIndex[d] < AxisSizes[d],
82+
"Tile index " << nDIndex << " exceeds axis size " << AxisSizes << " at dimension " << d);
83+
ind += nDIndex[d] * stride;
84+
stride *= AxisSizes[d];
85+
}
86+
return ind;
87+
}
88+
89+
TileIndexType
90+
LinearIndexToNDIndex(size_t linearIndex) const
91+
{
92+
TileIndexType ind;
93+
SizeValueType stride = 1u;
94+
for (unsigned d = 0; d < Dimension; d++)
95+
{
96+
stride *= AxisSizes[d];
97+
ind[d] = linearIndex % AxisSizes[d];
98+
linearIndex /= AxisSizes[d];
99+
}
100+
itkAssertOrThrowMacro(linearIndex < stride,
101+
"Linear tile index " << linearIndex << " exceeds total montage size " << stride);
102+
return ind;
103+
}
104+
105+
// tries parsing the file, return first file name and set dimension
106+
static std::string
107+
TryParse(const std::string & pathToFile, unsigned & dimension)
108+
{
109+
std::ifstream tileFile(pathToFile);
110+
if (!tileFile)
111+
{
112+
throw std::runtime_error("Could not open for reading: " + pathToFile);
113+
}
114+
115+
std::string temp = getNextNonCommentLine(tileFile);
116+
if (temp.substr(0, 6) == "dim = ")
117+
{
118+
dimension = std::stoul(temp.substr(6));
119+
temp = TileConfiguration<Dimension>::getNextNonCommentLine(tileFile); // get next line
120+
}
121+
122+
std::string timePointID;
123+
Tile<Dimension> tile = parseLine(temp, timePointID);
124+
return tile.FileName;
125+
}
126+
127+
void
128+
Parse(const std::string & pathToFile)
129+
{
130+
std::ifstream tileFile(pathToFile);
131+
if (!tileFile)
132+
{
133+
throw std::runtime_error("Could not open for reading: " + pathToFile);
134+
}
135+
std::string line = getNextNonCommentLine(tileFile);
136+
if (line.substr(0, 6) == "dim = ")
137+
{
138+
unsigned dim = std::stoul(line.substr(6));
139+
if (dim != Dimension)
140+
{
141+
throw std::runtime_error("Expected dimension " + std::to_string(Dimension) + ", but got " +
142+
std::to_string(dim) + " from string:\n\n" + line);
143+
}
144+
line = TileConfiguration<Dimension>::getNextNonCommentLine(tileFile); // get next line
145+
}
146+
147+
AxisSizes.Fill(1);
148+
Tiles.clear();
149+
TileIndexType cInd;
150+
cInd.Fill(0);
151+
unsigned initializedDimensions = 0; // no dimension has been initialized
152+
153+
std::string timePoint;
154+
itk::Tile<Dimension> tile = parseLine(line, timePoint);
155+
Tiles.push_back(tile);
156+
line = getNextNonCommentLine(tileFile);
157+
158+
while (tileFile)
159+
{
160+
tile = parseLine(line, timePoint);
161+
// determine dominant axis change
162+
unsigned maxAxis = 0; // (0=x, 1=y, 2=z etc)
163+
double maxDiff = tile.Position[0] - Tiles.back().Position[0];
164+
for (unsigned d = 1; d < Dimension; d++)
165+
{
166+
double diff = tile.Position[d] - Tiles.back().Position[d];
167+
if (diff > maxDiff)
168+
{
169+
maxDiff = diff;
170+
maxAxis = d;
171+
}
172+
}
173+
174+
if (maxAxis > initializedDimensions) // we now know the size along this dimension
175+
{
176+
AxisSizes[maxAxis - 1] = cInd[maxAxis - 1] + 1;
177+
initializedDimensions = maxAxis;
178+
}
179+
180+
// check consistency with previously established size
181+
for (unsigned d = 0; d < maxAxis; d++)
182+
{
183+
SizeValueType axisSize = (d == maxAxis - 1) ? AxisSizes[d] : AxisSizes[d] - 1;
184+
itkAssertOrThrowMacro(cInd[d] == AxisSizes[d] - 1,
185+
"Axis sizes: " << AxisSizes << " current index: " << cInd
186+
<< ". We have reached the end along axis " << maxAxis
187+
<< "\nIndex along axis " << d << " is " << cInd[d] << ", but it should be "
188+
<< AxisSizes[d] - 1);
189+
}
190+
191+
// update current tile index
192+
for (unsigned d = 0; d < maxAxis; d++)
193+
{
194+
cInd[d] = 0;
195+
}
196+
++cInd[maxAxis];
197+
198+
199+
if (maxAxis < initializedDimensions) // check bounds, if bounds are established
200+
{
201+
itkAssertOrThrowMacro(cInd[maxAxis] < AxisSizes[maxAxis],
202+
"Axis sizes: " << AxisSizes << ", but we reached index " << cInd[maxAxis]
203+
<< ". Violation along axis " << maxAxis);
204+
}
205+
206+
Tiles.push_back(tile);
207+
line = getNextNonCommentLine(tileFile);
208+
}
209+
AxisSizes[Dimension - 1] = cInd[Dimension - 1] + 1;
210+
211+
size_t expectedSize = this->LinearSize();
212+
itkAssertOrThrowMacro(expectedSize == Tiles.size(),
213+
"Incorrect number of tiles: " << Tiles.size() << ". Expected: " << expectedSize);
214+
}
215+
216+
void
217+
Write(const std::string & pathToFile)
218+
{
219+
std::ofstream tileFile(pathToFile);
220+
if (!tileFile)
221+
{
222+
throw std::runtime_error("Could not open for writing: " + pathToFile);
223+
}
224+
225+
tileFile << "# Tile coordinates are in index space, not physical space\n";
226+
tileFile << "dim = " << Dimension << "\n\n";
227+
char buffer[20];
228+
double_conversion::StringBuilder conversionResult(buffer, 20);
229+
230+
size_t totalTiles = this->LinearSize();
231+
for (SizeValueType linearIndex = 0; linearIndex < totalTiles; linearIndex++)
232+
{
233+
TileIndexType ind = this->LinearIndexToNDIndex(linearIndex);
234+
tileFile << Tiles[linearIndex].FileName << ";;(";
235+
236+
for (unsigned d = 0; d < Dimension; d++)
237+
{
238+
if (d > 0)
239+
{
240+
tileFile << ", ";
241+
}
242+
243+
doubleConverter.ToShortest(Tiles[linearIndex].Position[d], &conversionResult);
244+
tileFile << conversionResult.Finalize();
245+
conversionResult.Reset();
246+
}
247+
tileFile << ')' << std::endl;
248+
}
249+
250+
if (!tileFile)
251+
{
252+
throw std::runtime_error("Writing not successful to: " + pathToFile);
253+
}
254+
}
255+
256+
protected:
257+
static double_conversion::StringToDoubleConverter stringConverter;
258+
static double_conversion::DoubleToStringConverter doubleConverter;
259+
260+
static std::string
261+
getNextNonCommentLine(std::istream & in)
262+
{
263+
std::string temp;
264+
while (std::getline(in, temp))
265+
{
266+
if (temp.empty() || temp[0] == '#')
267+
{
268+
continue; // this is either an empty line or a comment
269+
}
270+
if (temp.size() == 1 && temp[0] == '\r')
271+
{
272+
continue; // empty line ending in CRLF
273+
}
274+
if (temp[temp.size() - 1] == '\r')
275+
{
276+
temp.erase(temp.size() - 1, 1);
277+
}
278+
break; // temp has interesting content
279+
}
280+
return temp;
281+
}
282+
283+
static Tile<Dimension>
284+
parseLine(const std::string line, std::string & timePointID)
285+
{
286+
itk::Tile<Dimension> tile;
287+
std::stringstream ss(line);
288+
std::string temp;
289+
290+
std::getline(ss, temp, ';');
291+
tile.FileName = temp;
292+
std::getline(ss, temp, ';');
293+
if (timePointID.empty())
294+
{
295+
timePointID = temp;
296+
}
297+
else
298+
{
299+
itkAssertOrThrowMacro(temp == timePointID,
300+
"Only a single time point is supported. " << timePointID << " != " << temp);
301+
}
302+
std::getline(ss, temp, '(');
303+
304+
using PointType = itk::Point<double, Dimension>;
305+
PointType p;
306+
for (unsigned d = 0; d < Dimension; d++)
307+
{
308+
std::getline(ss, temp, ',');
309+
int processed = 0;
310+
p[d] = stringConverter.StringToDouble(temp.c_str(), temp.length(), &processed);
311+
}
312+
tile.Position = p;
313+
314+
return tile;
315+
}
316+
};
317+
318+
template <unsigned Dimension>
319+
double_conversion::StringToDoubleConverter TileConfiguration<Dimension>::stringConverter(
320+
double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK |
321+
double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES |
322+
double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES,
323+
0.0,
324+
std::numeric_limits<double>::quiet_NaN(),
325+
nullptr,
326+
nullptr);
327+
328+
template <unsigned Dimension>
329+
double_conversion::DoubleToStringConverter TileConfiguration<
330+
Dimension>::doubleConverter(double_conversion::DoubleToStringConverter::NO_FLAGS, nullptr, nullptr, 'e', 0, 17, 1, 0);
331+
332+
// add #ifdef legacy
333+
41334
using Tile2D = Tile<2>;
42335
using TileRow2D = std::vector<Tile2D>;
43336
using TileLayout2D = std::vector<TileRow2D>;

include/itkTileMergeImageFilter.h

+14-6
Original file line numberDiff line numberDiff line change
@@ -131,22 +131,30 @@ class ITK_TEMPLATE_EXPORT TileMergeImageFilter
131131
/** To be called for each tile position in the mosaic
132132
* before the call to Update(). */
133133
void
134-
SetInputTile(TileIndexType position, ImageType * image)
134+
SetInputTile(SizeValueType linearIndex, ImageType * image)
135135
{
136-
const SizeValueType linearIndex = this->nDIndexToLinearIndex(position);
137136
// image will be cast into DataObject* so this casting is not a problem
138-
Superclass::SetInputTile(position, reinterpret_cast<typename Superclass::ImageType *>(image));
137+
Superclass::SetInputTile(linearIndex, reinterpret_cast<typename Superclass::ImageType *>(image));
139138
m_Transforms[linearIndex] = nullptr;
140139
m_Tiles[linearIndex] = nullptr;
141140
}
142141
void
143-
SetInputTile(TileIndexType position, const std::string & imageFilename)
142+
SetInputTile(SizeValueType linearIndex, const std::string & imageFilename)
144143
{
145-
Superclass::SetInputTile(position, imageFilename);
146-
const SizeValueType linearIndex = this->nDIndexToLinearIndex(position);
144+
Superclass::SetInputTile(linearIndex, imageFilename);
147145
m_Transforms[linearIndex] = nullptr;
148146
m_Tiles[linearIndex] = nullptr;
149147
}
148+
void
149+
SetInputTile(TileIndexType position, ImageType * image)
150+
{
151+
this->SetInputTile(this->nDIndexToLinearIndex(position), image);
152+
}
153+
void
154+
SetInputTile(TileIndexType position, const std::string & imageFilename)
155+
{
156+
this->SetInputTile(this->nDIndexToLinearIndex(position), imageFilename);
157+
}
150158

151159
/** Input tiles' transforms, as calculated by \sa{Montage}.
152160
* To be called for each tile position in the mosaic

include/itkTileMontage.h

+13-5
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,27 @@ class ITK_TEMPLATE_EXPORT TileMontage : public ProcessObject
173173
/** To be called for each tile position in the mosaic
174174
* before the call to Update(). */
175175
void
176-
SetInputTile(TileIndexType position, ImageType * image)
176+
SetInputTile(SizeValueType linearIndex, ImageType * image)
177177
{
178-
SizeValueType linearIndex = this->nDIndexToLinearIndex(position);
179178
this->SetNthInput(linearIndex, image);
180179
m_FFTCache[linearIndex] = nullptr;
181180
m_Tiles[linearIndex] = nullptr;
182181
}
183182
void
184-
SetInputTile(TileIndexType position, const std::string & imageFilename)
183+
SetInputTile(SizeValueType linearIndex, const std::string & imageFilename)
185184
{
186-
SizeValueType linearIndex = this->nDIndexToLinearIndex(position);
187185
m_Filenames[linearIndex] = imageFilename;
188-
this->SetInputTile(position, m_Dummy);
186+
this->SetInputTile(linearIndex, m_Dummy);
187+
}
188+
void
189+
SetInputTile(TileIndexType position, ImageType * image)
190+
{
191+
this->SetInputTile(this->nDIndexToLinearIndex(position), image);
192+
}
193+
void
194+
SetInputTile(TileIndexType position, const std::string & imageFilename)
195+
{
196+
this->SetInputTile(this->nDIndexToLinearIndex(position), imageFilename);
189197
}
190198

191199
/** After Update(), the transform for each tile is available. */

0 commit comments

Comments
 (0)