Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit fceb6a1

Browse files
aturetskfacebook-github-bot
authored andcommitted
Add support for PPM images. (#5559)
Summary: Add support for PPM images as input for models. Documentation: N/A Pull Request resolved: #5559 Test Plan: Added an ImageTest unit test. Reviewed By: jfix71 Differential Revision: D27788676 Pulled By: zrphercule fbshipit-source-id: 6d61845318000472acd159f9eba98b81a1f4c25b
1 parent 50b5949 commit fceb6a1

File tree

11 files changed

+237
-73
lines changed

11 files changed

+237
-73
lines changed

examples/lenet-loader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ int main() {
4242
auto *output = EXIT_ON_ERR(loader.getSingleOutput());
4343

4444
// Read an example PNG and add it to an input batch.
45-
auto image = glow::readPngImageAndPreprocess(
45+
auto image = glow::readPngPpmImageAndPreprocess(
4646
"tests/images/mnist/5_1087.png", glow::ImageNormalizationMode::k0to1,
4747
glow::ImageChannelOrder::BGR, glow::ImageLayout::NCHW);
4848
glow::Tensor batch(inputType);

examples/resnet-runtime.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ int main(int argc, char **argv) {
190190

191191
std::string path = dirIt->path();
192192

193-
auto image = readPngImageAndPreprocess(
193+
auto image = readPngPpmImageAndPreprocess(
194194
path, ImageNormalizationMode::k0to1, ImageChannelOrder::BGR,
195195
ImageLayout::NCHW, imagenetNormMean, imagenetNormStd);
196196
std::unique_ptr<ExecutionContext> context =

examples/resnet-verify.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ int main() {
8989
Tester cpu{"CPU"};
9090

9191
// Read an example PNG and add it to an input batch.
92-
auto image = readPngImageAndPreprocess(
92+
auto image = readPngPpmImageAndPreprocess(
9393
"tests/images/imagenet/cat_285.png", ImageNormalizationMode::k0to1,
9494
ImageChannelOrder::BGR, ImageLayout::NCHW, imagenetNormMean,
9595
imagenetNormStd);

examples/tracing-compare.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ int main(int argc, char **argv) {
135135
f.wait_for(/* timeout_duration */ std::chrono::seconds(30));
136136
}
137137

138-
auto image = readPngImageAndPreprocess(
138+
auto image = readPngPpmImageAndPreprocess(
139139
inputImage, ImageNormalizationMode::k0to1, ImageChannelOrder::BGR,
140140
ImageLayout::NCHW, imagenetNormMean, imagenetNormStd);
141141

include/glow/Base/Image.h

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,23 @@ std::pair<float, float> normModeToRange(ImageNormalizationMode mode);
104104

105105
/// Reads a png image header from png file \p filename and \returns a tuple
106106
/// containing height, width, and a bool if it is grayscale or not.
107-
std::tuple<size_t, size_t, bool> getPngInfo(const char *filename);
107+
std::tuple<dim_t, dim_t, bool> getPngInfo(const char *filename);
108+
109+
/// Reads a PPM image header from PPM file descriptor \p fp and \returns a tuple
110+
/// containing height, width, and a bool if it is grayscale or not. \p filename
111+
/// is passed only as a context to provide more detailed error reporting.
112+
std::tuple<dim_t, dim_t, bool> getPpmInfo(FILE *fp, const char *filename);
113+
114+
/// Reads a PPM image header from PPM file \p filename and \returns a tuple
115+
/// containing height, width, and a bool if it is grayscale or not.
116+
std::tuple<dim_t, dim_t, bool> getPpmInfo(const char *filename);
108117

109118
/// Returns whether file \p filename is in png format.
110119
bool isPngFormat(const std::string &filename);
111120

121+
/// Check if file \p filename is in PPM format.
122+
bool isPpmFormat(const std::string &filename);
123+
112124
/// Reads a png image. \returns True if an error occurred. The values of the
113125
/// image are in the range \p range.
114126
bool readPngImage(Tensor *T, const char *filename,
@@ -123,48 +135,56 @@ bool writePngImage(Tensor *T, const char *filename,
123135
llvm::ArrayRef<float> mean = zeroMean,
124136
llvm::ArrayRef<float> stddev = oneStd);
125137

126-
/// Read a png image and preprocess it according to several parameters. Create a
127-
/// tensor and store the preprocessed image data into this tensor.
128-
/// \param filename the png file to read.
138+
/// Reads a PPM image. \returns True if an error occurred. The values of the
139+
/// image are in the range \p range. Performs pre-processing using \p mean and
140+
/// \p stddev.
141+
bool readPpmImage(Tensor *T, const char *filename,
142+
std::pair<float, float> range,
143+
llvm::ArrayRef<float> mean = zeroMean,
144+
llvm::ArrayRef<float> stddev = oneStd);
145+
146+
/// Read a PNG/PPM image and preprocess it according to several parameters.
147+
/// Create a tensor and store the preprocessed image data into this tensor.
148+
/// \param filename the PNG/PPM file to read.
129149
/// \param imageNormMode normalize values to this range.
130150
/// \param imageChannelOrder the order of color channels.
131151
/// \param imageLayout the order of dimensions (channel, height, and width).
132152
/// \param mean use special mean to normalize.
133153
/// \param stdev use special stddev to normalize.
134-
Tensor readPngImageAndPreprocess(llvm::StringRef filename,
135-
ImageNormalizationMode imageNormMode,
136-
ImageChannelOrder imageChannelOrder,
137-
ImageLayout imageLayout,
138-
llvm::ArrayRef<float> mean = zeroMean,
139-
llvm::ArrayRef<float> stddev = oneStd);
140-
141-
/// Read a png image and preprocess it according to several parameters. Take a
142-
/// tensor as a parameter and store the preprocessed image data into this
154+
Tensor readPngPpmImageAndPreprocess(llvm::StringRef filename,
155+
ImageNormalizationMode imageNormMode,
156+
ImageChannelOrder imageChannelOrder,
157+
ImageLayout imageLayout,
158+
llvm::ArrayRef<float> mean = zeroMean,
159+
llvm::ArrayRef<float> stddev = oneStd);
160+
161+
/// Read a PNG/PPM image and preprocess it according to several parameters. Take
162+
/// a tensor as a parameter and store the preprocessed image data into this
143163
/// tensor.
144164
/// \param imageData the tensor into which the preprocessed image data
145165
/// will be stored.
146-
/// \param filename the png file to read.
166+
/// \param filename the PNG/PPM file to read.
147167
/// \param imageNormMode normalize values to this range.
148168
/// \param imageChannelOrder the order of color channels.
149169
/// \param imageLayout the order of dimensions (channel, height, and width).
150170
/// \param mean use special mean to normalize.
151171
/// \param stdev use special stddev to normalize.
152-
void readPngImageAndPreprocess(Tensor &imageData, llvm::StringRef filename,
153-
ImageNormalizationMode imageNormMode,
154-
ImageChannelOrder imageChannelOrder,
155-
ImageLayout imageLayout,
156-
llvm::ArrayRef<float> mean = zeroMean,
157-
llvm::ArrayRef<float> stddev = oneStd);
172+
void readPngPpmImageAndPreprocess(Tensor &imageData, llvm::StringRef filename,
173+
ImageNormalizationMode imageNormMode,
174+
ImageChannelOrder imageChannelOrder,
175+
ImageLayout imageLayout,
176+
llvm::ArrayRef<float> mean = zeroMean,
177+
llvm::ArrayRef<float> stddev = oneStd);
158178

159179
/// \param mean use special mean to normalize.
160180
/// \param stdev use special stddev to normalize.
161-
void readPngImagesAndPreprocess(Tensor &inputImageData,
162-
const llvm::ArrayRef<std::string> &filenames,
163-
ImageNormalizationMode imageNormMode,
164-
ImageChannelOrder imageChannelOrder,
165-
ImageLayout imageLayout,
166-
llvm::ArrayRef<float> mean,
167-
llvm::ArrayRef<float> stddev);
181+
void readPngPpmImagesAndPreprocess(Tensor &inputImageData,
182+
const llvm::ArrayRef<std::string> &filenames,
183+
ImageNormalizationMode imageNormMode,
184+
ImageChannelOrder imageChannelOrder,
185+
ImageLayout imageLayout,
186+
llvm::ArrayRef<float> mean,
187+
llvm::ArrayRef<float> stddev);
168188

169189
/// Returns whether file \p filename is in Numpy .npy format.
170190
bool isNumpyNpyFormat(const std::string &filename);

lib/Base/Image.cpp

Lines changed: 147 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,18 @@ bool glow::isPngFormat(const std::string &filename) {
265265
return isPngHdrSignature(header);
266266
}
267267

268-
std::tuple<size_t, size_t, bool> glow::getPngInfo(const char *filename) {
268+
bool glow::isPpmFormat(const std::string &filename) {
269+
// Open file and test for it being a PPM.
270+
char magic[2];
271+
FILE *fp = fopen(filename.c_str(), "rb");
272+
CHECK(fp) << "Can't open image file with name: " << filename;
273+
CHECK_EQ(fread(magic, sizeof(char), sizeof(magic), fp), sizeof(magic))
274+
<< "Failed to read magic number from file: " << filename;
275+
fclose(fp);
276+
return magic[0] == 'P' && (magic[1] == '5' || magic[1] == '6');
277+
}
278+
279+
std::tuple<dim_t, dim_t, bool> glow::getPngInfo(const char *filename) {
269280
// open file and test for it being a png.
270281
FILE *fp = fopen(filename, "rb");
271282
CHECK(fp) << "Can't open image file with name: " << filename;
@@ -302,6 +313,61 @@ std::tuple<size_t, size_t, bool> glow::getPngInfo(const char *filename) {
302313
return std::make_tuple(height, width, isGray);
303314
}
304315

316+
static void skipSpacePPM(FILE *fp) {
317+
int c = getc(fp);
318+
while (c != EOF) {
319+
if (c == '#') {
320+
// Skip comment line.
321+
do {
322+
c = getc(fp);
323+
} while (c != EOF && c != '\n');
324+
} else if (!isspace(c)) {
325+
ungetc(c, fp);
326+
break;
327+
}
328+
c = getc(fp);
329+
}
330+
}
331+
332+
std::tuple<dim_t, dim_t, bool> glow::getPpmInfo(FILE *fp,
333+
const char *filename) {
334+
// Open file and test for it being a PPM.
335+
char magic[2];
336+
CHECK_EQ(fread(magic, sizeof(char), sizeof(magic), fp), sizeof(magic))
337+
<< "Failed to read magic number from file: " << filename;
338+
CHECK(magic[0] == 'P' && (magic[1] == '5' || magic[1] == '6'))
339+
<< filename << " is not a PPM image";
340+
341+
// Gray-scale or color is determined by magic number.
342+
bool isGray = magic[1] == '5';
343+
344+
// Read dimensions and color depth.
345+
int32_t height, width, depth;
346+
skipSpacePPM(fp);
347+
CHECK_EQ(fscanf(fp, "%d", &width), 1)
348+
<< "Can't read width from: " << filename;
349+
skipSpacePPM(fp);
350+
CHECK_EQ(fscanf(fp, "%d", &height), 1)
351+
<< "Can't read height from: " << filename;
352+
skipSpacePPM(fp);
353+
CHECK_EQ(fscanf(fp, "%d", &depth), 1)
354+
<< "Can't read color depth from: " << filename;
355+
CHECK_EQ(depth, 255) << "Unsupported color depth " << depth
356+
<< " in file: " << filename;
357+
358+
return std::make_tuple(dim_t(height), dim_t(width), isGray);
359+
}
360+
361+
std::tuple<dim_t, dim_t, bool> glow::getPpmInfo(const char *filename) {
362+
bool isGray;
363+
dim_t width, height;
364+
FILE *fp = fopen(filename, "rb");
365+
CHECK(fp) << "Can't open image file with name: " << filename;
366+
std::tie(height, width, isGray) = getPpmInfo(fp, filename);
367+
fclose(fp);
368+
return std::make_tuple(height, width, isGray);
369+
}
370+
305371
bool glow::readPngImage(Tensor *T, const char *filename,
306372
std::pair<float, float> range,
307373
llvm::ArrayRef<float> mean,
@@ -415,6 +481,50 @@ bool glow::readPngImage(Tensor *T, const char *filename,
415481
return false;
416482
}
417483

484+
bool glow::readPpmImage(Tensor *T, const char *filename,
485+
std::pair<float, float> range,
486+
llvm::ArrayRef<float> mean,
487+
llvm::ArrayRef<float> stddev) {
488+
bool isGray;
489+
dim_t height, width;
490+
FILE *fp = fopen(filename, "rb");
491+
if (!fp) {
492+
return true;
493+
}
494+
495+
// Get PPM info.
496+
std::tie(height, width, isGray) = getPpmInfo(fp, filename);
497+
const dim_t numChannels = isGray ? 1 : 3;
498+
T->reset(ElemKind::FloatTy, {height, width, numChannels});
499+
500+
// Skip a single byte of space.
501+
fgetc(fp);
502+
503+
// Read pixels and do pre-processing.
504+
auto H = T->getHandle<>();
505+
float scale = ((range.second - range.first) / 255.0);
506+
float bias = range.first;
507+
unsigned char *buf =
508+
(unsigned char *)malloc(width * numChannels * sizeof(unsigned char));
509+
for (dim_t h = 0; h < height; h++) {
510+
if (fread(buf, width * numChannels, 1, fp) != 1) {
511+
free(buf);
512+
fclose(fp);
513+
return true;
514+
}
515+
for (dim_t w = 0; w < width; w++) {
516+
for (dim_t c = 0; c < numChannels; c++) {
517+
float val = float(buf[w * numChannels + c]);
518+
H.at({h, w, c}) = ((val - mean[c]) / stddev[c]) * scale + bias;
519+
}
520+
}
521+
}
522+
523+
free(buf);
524+
fclose(fp);
525+
return false;
526+
}
527+
418528
bool glow::writePngImage(Tensor *T, const char *filename,
419529
std::pair<float, float> range,
420530
llvm::ArrayRef<float> mean,
@@ -509,25 +619,25 @@ bool glow::writePngImage(Tensor *T, const char *filename,
509619
return false;
510620
}
511621

512-
Tensor glow::readPngImageAndPreprocess(llvm::StringRef filename,
513-
ImageNormalizationMode imageNormMode,
514-
ImageChannelOrder imageChannelOrder,
515-
ImageLayout imageLayout,
516-
llvm::ArrayRef<float> mean,
517-
llvm::ArrayRef<float> stddev) {
622+
Tensor glow::readPngPpmImageAndPreprocess(llvm::StringRef filename,
623+
ImageNormalizationMode imageNormMode,
624+
ImageChannelOrder imageChannelOrder,
625+
ImageLayout imageLayout,
626+
llvm::ArrayRef<float> mean,
627+
llvm::ArrayRef<float> stddev) {
518628
Tensor imageData;
519-
readPngImageAndPreprocess(imageData, filename, imageNormMode,
520-
imageChannelOrder, imageLayout, mean, stddev);
629+
readPngPpmImageAndPreprocess(imageData, filename, imageNormMode,
630+
imageChannelOrder, imageLayout, mean, stddev);
521631
return imageData;
522632
}
523633

524-
void glow::readPngImageAndPreprocess(Tensor &imageData,
525-
llvm::StringRef filename,
526-
ImageNormalizationMode imageNormMode,
527-
ImageChannelOrder imageChannelOrder,
528-
ImageLayout imageLayout,
529-
llvm::ArrayRef<float> mean,
530-
llvm::ArrayRef<float> stddev) {
634+
void glow::readPngPpmImageAndPreprocess(Tensor &imageData,
635+
llvm::StringRef filename,
636+
ImageNormalizationMode imageNormMode,
637+
ImageChannelOrder imageChannelOrder,
638+
ImageLayout imageLayout,
639+
llvm::ArrayRef<float> mean,
640+
llvm::ArrayRef<float> stddev) {
531641

532642
// PNG images are RGB, so shuffle mean and stddev values to be in RGB order
533643
// as well, prior applying them to input image.
@@ -538,15 +648,20 @@ void glow::readPngImageAndPreprocess(Tensor &imageData,
538648
std::reverse(stddevRGB.begin(), stddevRGB.end());
539649
}
540650

651+
bool isPNG = isPngFormat(filename.data());
652+
CHECK(isPNG || isPpmFormat(filename.data())) << "Unrecognized image format";
541653
auto range = normModeToRange(imageNormMode);
542-
bool loadSuccess =
543-
!readPngImage(&imageData, filename.data(), range, meanRGB, stddevRGB);
654+
bool loadSuccess = isPNG ? !readPngImage(&imageData, filename.data(), range,
655+
meanRGB, stddevRGB)
656+
: !readPpmImage(&imageData, filename.data(), range,
657+
meanRGB, stddevRGB);
658+
544659
CHECK(loadSuccess) "Error reading input image from file: " << filename.str();
545660
dim_t imgHeight = imageData.dims()[0];
546661
dim_t imgWidth = imageData.dims()[1];
547662
dim_t numChannels = imageData.dims()[2];
548663

549-
// PNG images are NHWC and RGB. Convert if needed.
664+
// PNG/PPM images are NHWC and RGB. Convert if needed.
550665
// Convert to requested channel ordering.
551666
if (imageChannelOrder == ImageChannelOrder::BGR) {
552667
Tensor swizzled(imageData.getType());
@@ -569,8 +684,8 @@ void glow::readPngImageAndPreprocess(Tensor &imageData,
569684
}
570685
}
571686

572-
/// Entry point for the PNG images loader.
573-
void glow::readPngImagesAndPreprocess(
687+
/// Entry point for the PNG/PPM images loader.
688+
void glow::readPngPpmImagesAndPreprocess(
574689
Tensor &inputImageData, const llvm::ArrayRef<std::string> &filenames,
575690
ImageNormalizationMode imageNormMode, ImageChannelOrder imageChannelOrder,
576691
ImageLayout imageLayout, llvm::ArrayRef<float> meanRef,
@@ -584,7 +699,11 @@ void glow::readPngImagesAndPreprocess(
584699
dim_t imgHeight;
585700
dim_t imgWidth;
586701
bool isGray;
587-
std::tie(imgHeight, imgWidth, isGray) = getPngInfo(filenames[0].c_str());
702+
bool isPNG = isPngFormat(filenames[0]);
703+
CHECK(isPNG || isPpmFormat(filenames[0])) << "Unrecognized image format";
704+
std::tie(imgHeight, imgWidth, isGray) =
705+
isPNG ? getPngInfo(filenames[0].c_str())
706+
: getPpmInfo(filenames[0].c_str());
588707
const dim_t numChannels = isGray ? 1 : 3;
589708

590709
// Assign mean and stddev for input normalization.
@@ -632,8 +751,8 @@ void glow::readPngImagesAndPreprocess(
632751
// Read images into local tensors and add to batch.
633752
for (size_t n = 0; n < filenames.size(); n++) {
634753
Tensor localCopy;
635-
readPngImageAndPreprocess(localCopy, filenames[n], imageNormMode,
636-
imageChannelOrder, imageLayout, mean, stddev);
754+
readPngPpmImageAndPreprocess(localCopy, filenames[n], imageNormMode,
755+
imageChannelOrder, imageLayout, mean, stddev);
637756
DCHECK(std::equal(localCopy.dims().begin(), localCopy.dims().end(),
638757
inputImageData.dims().begin() + 1))
639758
<< "All images must have the same dimensions";
@@ -688,10 +807,10 @@ void glow::loadImagesAndPreprocess(
688807
auto inputImageData = inputImageDataList[i];
689808
// All files for an input must be of the same type, thus will just check
690809
// the first one.
691-
if (isPngFormat(filenames[0])) {
692-
readPngImagesAndPreprocess(*inputImageData, filenames, imageNormMode[i],
693-
imageChannelOrderOpt[i], imageLayoutOpt[i],
694-
meanValuesOpt[i], stddevValuesOpt[i]);
810+
if (isPngFormat(filenames[0]) || isPpmFormat(filenames[0])) {
811+
readPngPpmImagesAndPreprocess(
812+
*inputImageData, filenames, imageNormMode[i], imageChannelOrderOpt[i],
813+
imageLayoutOpt[i], meanValuesOpt[i], stddevValuesOpt[i]);
695814
} else if (isNumpyNpyFormat(filenames[0])) {
696815
loadNumpyImagesAndPreprocess(filenames, *inputImageData, imageNormMode[i],
697816
imageLayoutOpt[i], inputLayoutOpt[i],

0 commit comments

Comments
 (0)