Skip to content

Commit d87d637

Browse files
committed
WIP: Prototype of wrapping NRRD parsers in C++ locale
Used Claude to implement a prototype/test of using a string stream with a set locale instead of sscanf.
1 parent 1096547 commit d87d637

File tree

5 files changed

+229
-415
lines changed

5 files changed

+229
-415
lines changed

Modules/IO/NRRD/test/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,8 @@ itk_add_test(
356356
itkNrrdMetaDataTest
357357
${ITK_TEST_OUTPUT_DIR}
358358
)
359+
360+
# GTest tests
361+
set(ITKIONRRDGTests itkNrrdLocaleGTest.cxx)
362+
363+
creategoogletestdriver(ITKIONRRD "${ITKIONRRD-Test_LIBRARIES}" "${ITKIONRRDGTests}")
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
#include <locale.h>
20+
#include "itkImage.h"
21+
#include "itkImageFileReader.h"
22+
#include "itkImageFileWriter.h"
23+
#include "itkNrrdImageIO.h"
24+
#include "itksys/SystemTools.hxx"
25+
26+
#include "itkGTest.h"
27+
28+
#define _STRING(s) #s
29+
#define TOSTRING(s) std::string(_STRING(s))
30+
31+
namespace
32+
{
33+
34+
constexpr unsigned int Dimension = 3;
35+
using PixelType = float;
36+
using ImageType = itk::Image<PixelType, Dimension>;
37+
38+
class NrrdLocaleTest : public ::testing::Test
39+
{
40+
public:
41+
void
42+
SetUp() override
43+
{
44+
itksys::SystemTools::ChangeDirectory(TOSTRING(ITK_TEST_OUTPUT_DIR));
45+
}
46+
47+
ImageType::Pointer
48+
CreateTestImage()
49+
{
50+
auto image = ImageType::New();
51+
52+
ImageType::SizeType size;
53+
size.Fill(16);
54+
55+
ImageType::IndexType start;
56+
start.Fill(0);
57+
58+
ImageType::RegionType region(start, size);
59+
image->SetRegions(region);
60+
image->Allocate();
61+
image->FillBuffer(42.0f);
62+
63+
// Set spacing with fractional values that would be misparsed
64+
// in locales using comma as decimal separator
65+
ImageType::SpacingType spacing;
66+
spacing[0] = 3.5; // Would be parsed correctly even with truncation
67+
spacing[1] = 0.878906; // Would become 0.0 in de_DE locale, causing error
68+
spacing[2] = 2.2; // Would be parsed incorrectly
69+
70+
image->SetSpacing(spacing);
71+
72+
// Set origin with fractional values
73+
ImageType::PointType origin;
74+
origin[0] = 1.5;
75+
origin[1] = 2.75;
76+
origin[2] = 0.5;
77+
image->SetOrigin(origin);
78+
79+
return image;
80+
}
81+
};
82+
83+
TEST_F(NrrdLocaleTest, ReadWriteWithCLocale)
84+
{
85+
auto image = CreateTestImage();
86+
const auto expectedSpacing = image->GetSpacing();
87+
const auto expectedOrigin = image->GetOrigin();
88+
89+
const std::string filename = "locale_test.nrrd";
90+
91+
// Write the image with C locale
92+
setlocale(LC_NUMERIC, "C");
93+
94+
auto writer = itk::ImageFileWriter<ImageType>::New();
95+
writer->SetFileName(filename);
96+
writer->SetInput(image);
97+
writer->SetImageIO(itk::NrrdImageIO::New());
98+
EXPECT_NO_THROW(writer->Update());
99+
100+
// Read with C locale
101+
auto reader = itk::ImageFileReader<ImageType>::New();
102+
reader->SetFileName(filename);
103+
reader->SetImageIO(itk::NrrdImageIO::New());
104+
EXPECT_NO_THROW(reader->Update());
105+
106+
const auto readImage = reader->GetOutput();
107+
const auto readSpacing = readImage->GetSpacing();
108+
const auto readOrigin = readImage->GetOrigin();
109+
110+
// Verify spacing
111+
for (unsigned int i = 0; i < Dimension; ++i)
112+
{
113+
EXPECT_NEAR(readSpacing[i], expectedSpacing[i], 1e-6) << "Spacing mismatch in C locale at index " << i;
114+
}
115+
116+
// Verify origin
117+
for (unsigned int i = 0; i < Dimension; ++i)
118+
{
119+
EXPECT_NEAR(readOrigin[i], expectedOrigin[i], 1e-6) << "Origin mismatch in C locale at index " << i;
120+
}
121+
}
122+
123+
TEST_F(NrrdLocaleTest, ReadWithGermanLocale)
124+
{
125+
auto image = CreateTestImage();
126+
const auto expectedSpacing = image->GetSpacing();
127+
const auto expectedOrigin = image->GetOrigin();
128+
129+
const std::string filename = "locale_test_de.nrrd";
130+
131+
// Write the image with C locale
132+
setlocale(LC_NUMERIC, "C");
133+
134+
auto writer = itk::ImageFileWriter<ImageType>::New();
135+
writer->SetFileName(filename);
136+
writer->SetInput(image);
137+
writer->SetImageIO(itk::NrrdImageIO::New());
138+
EXPECT_NO_THROW(writer->Update());
139+
140+
// Try to set German locale; skip test if not available
141+
if (setlocale(LC_NUMERIC, "de_DE.UTF-8") == nullptr)
142+
{
143+
GTEST_SKIP() << "de_DE.UTF-8 locale not available, skipping locale-specific test";
144+
}
145+
146+
// Read with de_DE locale - this is the critical test
147+
auto reader = itk::ImageFileReader<ImageType>::New();
148+
reader->SetFileName(filename);
149+
reader->SetImageIO(itk::NrrdImageIO::New());
150+
EXPECT_NO_THROW(reader->Update());
151+
152+
const auto readImage = reader->GetOutput();
153+
const auto readSpacing = readImage->GetSpacing();
154+
const auto readOrigin = readImage->GetOrigin();
155+
156+
// Verify spacing - this would fail without locale-independent parsing
157+
for (unsigned int i = 0; i < Dimension; ++i)
158+
{
159+
EXPECT_NEAR(readSpacing[i], expectedSpacing[i], 1e-6)
160+
<< "Spacing mismatch in de_DE locale at index " << i
161+
<< ". This indicates locale-dependent parsing is still occurring!";
162+
}
163+
164+
// Verify origin
165+
for (unsigned int i = 0; i < Dimension; ++i)
166+
{
167+
EXPECT_NEAR(readOrigin[i], expectedOrigin[i], 1e-6)
168+
<< "Origin mismatch in de_DE locale at index " << i
169+
<< ". This indicates locale-dependent parsing is still occurring!";
170+
}
171+
172+
// Restore C locale
173+
setlocale(LC_NUMERIC, "C");
174+
}
175+
176+
} // namespace

Modules/ThirdParty/NrrdIO/src/NrrdIO/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ INCLUDE_REGULAR_EXPRESSION("^.*.h$")
88
# package. See http://teem.sourceforge.net for more information.
99
#
1010

11-
SET(nrrdio_SRCS 754.c mop.c array.c parseAir.c dio.c sane.c endianAir.c
11+
SET(nrrdio_SRCS 754.c mop.c array.c parseAir.cxx dio.c sane.c endianAir.c
1212
string.c enum.c miscAir.c biffbiff.c biffmsg.c accessors.c defaultsNrrd.c
1313
enumsNrrd.c arraysNrrd.c methodsNrrd.c reorder.c axis.c simple.c comment.c
1414
keyvalue.c endianNrrd.c parseNrrd.c gzio.c read.c write.c format.c

0 commit comments

Comments
 (0)