Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/lenet-loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ int main() {
auto *output = EXIT_ON_ERR(loader.getSingleOutput());

// Read an example PNG and add it to an input batch.
auto image = glow::readPngImageAndPreprocess(
auto image = glow::readPngPpmImageAndPreprocess(
"tests/images/mnist/5_1087.png", glow::ImageNormalizationMode::k0to1,
glow::ImageChannelOrder::BGR, glow::ImageLayout::NCHW);
glow::Tensor batch(inputType);
Expand Down
2 changes: 1 addition & 1 deletion examples/resnet-runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ int main(int argc, char **argv) {

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

auto image = readPngImageAndPreprocess(
auto image = readPngPpmImageAndPreprocess(
path, ImageNormalizationMode::k0to1, ImageChannelOrder::BGR,
ImageLayout::NCHW, imagenetNormMean, imagenetNormStd);
std::unique_ptr<ExecutionContext> context =
Expand Down
2 changes: 1 addition & 1 deletion examples/resnet-verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ int main() {
Tester cpu{"CPU"};

// Read an example PNG and add it to an input batch.
auto image = readPngImageAndPreprocess(
auto image = readPngPpmImageAndPreprocess(
"tests/images/imagenet/cat_285.png", ImageNormalizationMode::k0to1,
ImageChannelOrder::BGR, ImageLayout::NCHW, imagenetNormMean,
imagenetNormStd);
Expand Down
2 changes: 1 addition & 1 deletion examples/tracing-compare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ int main(int argc, char **argv) {
f.wait_for(/* timeout_duration */ std::chrono::seconds(30));
}

auto image = readPngImageAndPreprocess(
auto image = readPngPpmImageAndPreprocess(
inputImage, ImageNormalizationMode::k0to1, ImageChannelOrder::BGR,
ImageLayout::NCHW, imagenetNormMean, imagenetNormStd);

Expand Down
74 changes: 47 additions & 27 deletions include/glow/Base/Image.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,23 @@ std::pair<float, float> normModeToRange(ImageNormalizationMode mode);

/// Reads a png image header from png file \p filename and \returns a tuple
/// containing height, width, and a bool if it is grayscale or not.
std::tuple<size_t, size_t, bool> getPngInfo(const char *filename);
std::tuple<dim_t, dim_t, bool> getPngInfo(const char *filename);

/// Reads a PPM image header from PPM file descriptor \p fp and \returns a tuple
/// containing height, width, and a bool if it is grayscale or not. \p filename
/// is passed only as a context to provide more detailed error reporting.
std::tuple<dim_t, dim_t, bool> getPpmInfo(FILE *fp, const char *filename);

/// Reads a PPM image header from PPM file \p filename and \returns a tuple
/// containing height, width, and a bool if it is grayscale or not.
std::tuple<dim_t, dim_t, bool> getPpmInfo(const char *filename);

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

/// Check if file \p filename is in PPM format.
bool isPpmFormat(const std::string &filename);

/// Reads a png image. \returns True if an error occurred. The values of the
/// image are in the range \p range.
bool readPngImage(Tensor *T, const char *filename,
Expand All @@ -123,48 +135,56 @@ bool writePngImage(Tensor *T, const char *filename,
llvm::ArrayRef<float> mean = zeroMean,
llvm::ArrayRef<float> stddev = oneStd);

/// Read a png image and preprocess it according to several parameters. Create a
/// tensor and store the preprocessed image data into this tensor.
/// \param filename the png file to read.
/// Reads a PPM image. \returns True if an error occurred. The values of the
/// image are in the range \p range. Performs pre-processing using \p mean and
/// \p stddev.
bool readPpmImage(Tensor *T, const char *filename,
std::pair<float, float> range,
llvm::ArrayRef<float> mean = zeroMean,
llvm::ArrayRef<float> stddev = oneStd);

/// Read a PNG/PPM image and preprocess it according to several parameters.
/// Create a tensor and store the preprocessed image data into this tensor.
/// \param filename the PNG/PPM file to read.
/// \param imageNormMode normalize values to this range.
/// \param imageChannelOrder the order of color channels.
/// \param imageLayout the order of dimensions (channel, height, and width).
/// \param mean use special mean to normalize.
/// \param stdev use special stddev to normalize.
Tensor readPngImageAndPreprocess(llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean = zeroMean,
llvm::ArrayRef<float> stddev = oneStd);

/// Read a png image and preprocess it according to several parameters. Take a
/// tensor as a parameter and store the preprocessed image data into this
Tensor readPngPpmImageAndPreprocess(llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean = zeroMean,
llvm::ArrayRef<float> stddev = oneStd);

/// Read a PNG/PPM image and preprocess it according to several parameters. Take
/// a tensor as a parameter and store the preprocessed image data into this
/// tensor.
/// \param imageData the tensor into which the preprocessed image data
/// will be stored.
/// \param filename the png file to read.
/// \param filename the PNG/PPM file to read.
/// \param imageNormMode normalize values to this range.
/// \param imageChannelOrder the order of color channels.
/// \param imageLayout the order of dimensions (channel, height, and width).
/// \param mean use special mean to normalize.
/// \param stdev use special stddev to normalize.
void readPngImageAndPreprocess(Tensor &imageData, llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean = zeroMean,
llvm::ArrayRef<float> stddev = oneStd);
void readPngPpmImageAndPreprocess(Tensor &imageData, llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean = zeroMean,
llvm::ArrayRef<float> stddev = oneStd);

/// \param mean use special mean to normalize.
/// \param stdev use special stddev to normalize.
void readPngImagesAndPreprocess(Tensor &inputImageData,
const llvm::ArrayRef<std::string> &filenames,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev);
void readPngPpmImagesAndPreprocess(Tensor &inputImageData,
const llvm::ArrayRef<std::string> &filenames,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev);

/// Returns whether file \p filename is in Numpy .npy format.
bool isNumpyNpyFormat(const std::string &filename);
Expand Down
175 changes: 147 additions & 28 deletions lib/Base/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,18 @@ bool glow::isPngFormat(const std::string &filename) {
return isPngHdrSignature(header);
}

std::tuple<size_t, size_t, bool> glow::getPngInfo(const char *filename) {
bool glow::isPpmFormat(const std::string &filename) {
// Open file and test for it being a PPM.
char magic[2];
FILE *fp = fopen(filename.c_str(), "rb");
CHECK(fp) << "Can't open image file with name: " << filename;
CHECK_EQ(fread(magic, sizeof(char), sizeof(magic), fp), sizeof(magic))
<< "Failed to read magic number from file: " << filename;
fclose(fp);
return magic[0] == 'P' && (magic[1] == '5' || magic[1] == '6');
}

std::tuple<dim_t, dim_t, bool> glow::getPngInfo(const char *filename) {
// open file and test for it being a png.
FILE *fp = fopen(filename, "rb");
CHECK(fp) << "Can't open image file with name: " << filename;
Expand Down Expand Up @@ -302,6 +313,61 @@ std::tuple<size_t, size_t, bool> glow::getPngInfo(const char *filename) {
return std::make_tuple(height, width, isGray);
}

static void skipSpacePPM(FILE *fp) {
int c = getc(fp);
while (c != EOF) {
if (c == '#') {
// Skip comment line.
do {
c = getc(fp);
} while (c != EOF && c != '\n');
} else if (!isspace(c)) {
ungetc(c, fp);
break;
}
c = getc(fp);
}
}

std::tuple<dim_t, dim_t, bool> glow::getPpmInfo(FILE *fp,
const char *filename) {
// Open file and test for it being a PPM.
char magic[2];
CHECK_EQ(fread(magic, sizeof(char), sizeof(magic), fp), sizeof(magic))
<< "Failed to read magic number from file: " << filename;
CHECK(magic[0] == 'P' && (magic[1] == '5' || magic[1] == '6'))
<< filename << " is not a PPM image";

// Gray-scale or color is determined by magic number.
bool isGray = magic[1] == '5';

// Read dimensions and color depth.
int32_t height, width, depth;
skipSpacePPM(fp);
CHECK_EQ(fscanf(fp, "%d", &width), 1)
<< "Can't read width from: " << filename;
skipSpacePPM(fp);
CHECK_EQ(fscanf(fp, "%d", &height), 1)
<< "Can't read height from: " << filename;
skipSpacePPM(fp);
CHECK_EQ(fscanf(fp, "%d", &depth), 1)
<< "Can't read color depth from: " << filename;
CHECK_EQ(depth, 255) << "Unsupported color depth " << depth
<< " in file: " << filename;

return std::make_tuple(dim_t(height), dim_t(width), isGray);
}

std::tuple<dim_t, dim_t, bool> glow::getPpmInfo(const char *filename) {
bool isGray;
dim_t width, height;
FILE *fp = fopen(filename, "rb");
CHECK(fp) << "Can't open image file with name: " << filename;
std::tie(height, width, isGray) = getPpmInfo(fp, filename);
fclose(fp);
return std::make_tuple(height, width, isGray);
}

bool glow::readPngImage(Tensor *T, const char *filename,
std::pair<float, float> range,
llvm::ArrayRef<float> mean,
Expand Down Expand Up @@ -415,6 +481,50 @@ bool glow::readPngImage(Tensor *T, const char *filename,
return false;
}

bool glow::readPpmImage(Tensor *T, const char *filename,
std::pair<float, float> range,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev) {
bool isGray;
dim_t height, width;
FILE *fp = fopen(filename, "rb");
if (!fp) {
return true;
}

// Get PPM info.
std::tie(height, width, isGray) = getPpmInfo(fp, filename);
const dim_t numChannels = isGray ? 1 : 3;
T->reset(ElemKind::FloatTy, {height, width, numChannels});

// Skip a single byte of space.
fgetc(fp);

// Read pixels and do pre-processing.
auto H = T->getHandle<>();
float scale = ((range.second - range.first) / 255.0);
float bias = range.first;
unsigned char *buf =
(unsigned char *)malloc(width * numChannels * sizeof(unsigned char));
for (dim_t h = 0; h < height; h++) {
if (fread(buf, width * numChannels, 1, fp) != 1) {
free(buf);
fclose(fp);
return true;
}
for (dim_t w = 0; w < width; w++) {
for (dim_t c = 0; c < numChannels; c++) {
float val = float(buf[w * numChannels + c]);
H.at({h, w, c}) = ((val - mean[c]) / stddev[c]) * scale + bias;
}
}
}

free(buf);
fclose(fp);
return false;
}

bool glow::writePngImage(Tensor *T, const char *filename,
std::pair<float, float> range,
llvm::ArrayRef<float> mean,
Expand Down Expand Up @@ -509,25 +619,25 @@ bool glow::writePngImage(Tensor *T, const char *filename,
return false;
}

Tensor glow::readPngImageAndPreprocess(llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev) {
Tensor glow::readPngPpmImageAndPreprocess(llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev) {
Tensor imageData;
readPngImageAndPreprocess(imageData, filename, imageNormMode,
imageChannelOrder, imageLayout, mean, stddev);
readPngPpmImageAndPreprocess(imageData, filename, imageNormMode,
imageChannelOrder, imageLayout, mean, stddev);
return imageData;
}

void glow::readPngImageAndPreprocess(Tensor &imageData,
llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev) {
void glow::readPngPpmImageAndPreprocess(Tensor &imageData,
llvm::StringRef filename,
ImageNormalizationMode imageNormMode,
ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout,
llvm::ArrayRef<float> mean,
llvm::ArrayRef<float> stddev) {

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

bool isPNG = isPngFormat(filename.data());
CHECK(isPNG || isPpmFormat(filename.data())) << "Unrecognized image format";
auto range = normModeToRange(imageNormMode);
bool loadSuccess =
!readPngImage(&imageData, filename.data(), range, meanRGB, stddevRGB);
bool loadSuccess = isPNG ? !readPngImage(&imageData, filename.data(), range,
meanRGB, stddevRGB)
: !readPpmImage(&imageData, filename.data(), range,
meanRGB, stddevRGB);

CHECK(loadSuccess) "Error reading input image from file: " << filename.str();
dim_t imgHeight = imageData.dims()[0];
dim_t imgWidth = imageData.dims()[1];
dim_t numChannels = imageData.dims()[2];

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

/// Entry point for the PNG images loader.
void glow::readPngImagesAndPreprocess(
/// Entry point for the PNG/PPM images loader.
void glow::readPngPpmImagesAndPreprocess(
Tensor &inputImageData, const llvm::ArrayRef<std::string> &filenames,
ImageNormalizationMode imageNormMode, ImageChannelOrder imageChannelOrder,
ImageLayout imageLayout, llvm::ArrayRef<float> meanRef,
Expand All @@ -584,7 +699,11 @@ void glow::readPngImagesAndPreprocess(
dim_t imgHeight;
dim_t imgWidth;
bool isGray;
std::tie(imgHeight, imgWidth, isGray) = getPngInfo(filenames[0].c_str());
bool isPNG = isPngFormat(filenames[0]);
CHECK(isPNG || isPpmFormat(filenames[0])) << "Unrecognized image format";
std::tie(imgHeight, imgWidth, isGray) =
isPNG ? getPngInfo(filenames[0].c_str())
: getPpmInfo(filenames[0].c_str());
const dim_t numChannels = isGray ? 1 : 3;

// Assign mean and stddev for input normalization.
Expand Down Expand Up @@ -632,8 +751,8 @@ void glow::readPngImagesAndPreprocess(
// Read images into local tensors and add to batch.
for (size_t n = 0; n < filenames.size(); n++) {
Tensor localCopy;
readPngImageAndPreprocess(localCopy, filenames[n], imageNormMode,
imageChannelOrder, imageLayout, mean, stddev);
readPngPpmImageAndPreprocess(localCopy, filenames[n], imageNormMode,
imageChannelOrder, imageLayout, mean, stddev);
DCHECK(std::equal(localCopy.dims().begin(), localCopy.dims().end(),
inputImageData.dims().begin() + 1))
<< "All images must have the same dimensions";
Expand Down Expand Up @@ -688,10 +807,10 @@ void glow::loadImagesAndPreprocess(
auto inputImageData = inputImageDataList[i];
// All files for an input must be of the same type, thus will just check
// the first one.
if (isPngFormat(filenames[0])) {
readPngImagesAndPreprocess(*inputImageData, filenames, imageNormMode[i],
imageChannelOrderOpt[i], imageLayoutOpt[i],
meanValuesOpt[i], stddevValuesOpt[i]);
if (isPngFormat(filenames[0]) || isPpmFormat(filenames[0])) {
readPngPpmImagesAndPreprocess(
*inputImageData, filenames, imageNormMode[i], imageChannelOrderOpt[i],
imageLayoutOpt[i], meanValuesOpt[i], stddevValuesOpt[i]);
} else if (isNumpyNpyFormat(filenames[0])) {
loadNumpyImagesAndPreprocess(filenames, *inputImageData, imageNormMode[i],
imageLayoutOpt[i], inputLayoutOpt[i],
Expand Down
Loading