Skip to content

Commit

Permalink
[container] Add initial standalone image container.
Browse files Browse the repository at this point in the history
  • Loading branch information
zhanghb97 committed Aug 11, 2024
1 parent d43ddac commit f2d027d
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 32 deletions.
4 changes: 1 addition & 3 deletions examples/BuddyLeNet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ $ cmake -G Ninja .. \
-DLLVM_ENABLE_ASSERTIONS=ON \
-DCMAKE_BUILD_TYPE=RELEASE \
-DBUDDY_MLIR_ENABLE_PYTHON_PACKAGES=ON \
-DPython3_EXECUTABLE=$(which python3) \
-DBUDDY_ENABLE_OPENCV=ON \
-DOpenCV_DIR=</PATH/TO/OPENCV/BUILD/>
-DPython3_EXECUTABLE=$(which python3)
$ ninja
$ ninja check-buddy
```
Expand Down
36 changes: 8 additions & 28 deletions examples/BuddyLeNet/buddy-lenet-main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,24 @@
//===----------------------------------------------------------------------===//

#include <buddy/Core/Container.h>
#include <buddy/DIP/ImageContainer.h>
#include <buddy/DIP/ImgContainer.h>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <limits>
#include <opencv2/opencv.hpp>
#include <string>
#include <utility>
#include <vector>

constexpr size_t ParamsSize = 44426;
const std::string ImgName = "3.png";
const std::string ImgName = "8.bmp";

/// Declare LeNet forward function.
extern "C" void _mlir_ciface_forward(MemRef<float, 2> *output,
MemRef<float, 1> *arg0,
Img<float, 4> *input);

/// Function for preprocessing the image to match model input requirements.
const cv::Mat imagePreprocessing() {
// Get the directory of the LeNet example and construct the image path.
std::string lenetDir = getenv("LENET_EXAMPLE_PATH");
std::string imgPath = lenetDir + "/images/" + ImgName;
// Read the image in grayscale mode.
cv::Mat inputImage = cv::imread(imgPath, cv::IMREAD_GRAYSCALE);
assert(!inputImage.empty() && "Could not read the image.");
cv::Mat resizedImage;
int imageWidth = 28;
int imageHeight = 28;
// Resize the image to 28x28 pixels.
cv::resize(inputImage, resizedImage, cv::Size(imageWidth, imageHeight),
cv::INTER_LINEAR);
return resizedImage;
}
dip::Image<float, 4> *input);

/// Print [Log] label in bold blue format.
void printLogLabel() { std::cout << "\033[34;1m[Log] \033[0m"; }
Expand Down Expand Up @@ -112,19 +95,16 @@ int main() {
const std::string title = "LeNet Inference Powered by Buddy Compiler";
std::cout << "\033[33;1m" << title << "\033[0m" << std::endl;

// Preprocess the image to match the input requirements of the model.
cv::Mat image = imagePreprocessing();

// Define the sizes of the input and output tensors.
intptr_t sizesInput[4] = {1, 1, 28, 28};
// Define the sizes of the output tensors.
intptr_t sizesOutput[2] = {1, 10};

// Create input and output containers for the image and model output.
Img<float, 4> input(image, sizesInput, true);
std::string lenetDir = getenv("LENET_EXAMPLE_PATH");
std::string imgPath = lenetDir + "/images/" + ImgName;
dip::Image<float, 4> input(imgPath, dip::DIP_GRAYSCALE, true /* norm */);
MemRef<float, 2> output(sizesOutput);

// Load model parameters from the specified file.
std::string lenetDir = getenv("LENET_EXAMPLE_PATH");
std::string paramsDir = lenetDir + "/arg0.data";
MemRef<float, 1> paramsContainer({ParamsSize});
loadParameters(paramsDir, paramsContainer);
Expand Down
Binary file added examples/BuddyLeNet/images/8.bmp
Binary file not shown.
254 changes: 254 additions & 0 deletions frontend/Interfaces/buddy/DIP/ImgContainer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
//===- ImgContainer.h -----------------------------------------------------===//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//===----------------------------------------------------------------------===//
//
// Image container descriptor (without OpenCV dependency).
//
//===----------------------------------------------------------------------===//

#ifndef FRONTEND_INTERFACES_BUDDY_DIP_IMGCONTAINER
#define FRONTEND_INTERFACES_BUDDY_DIP_IMGCONTAINER

#include "buddy/Core/Container.h"
#include <cstring>
#include <fstream>
#include <memory>

namespace dip {
enum ImageModes {
DIP_GRAYSCALE = 0,
DIP_RGB = 1,
};

template <typename T, size_t N> class Image : public MemRef<T, N> {
public:
// Constructor initializes the image by loading from a file.
// Params:
// filename: Specifies the path to the image file.
// mode: Specifies the image mode (e.g., DIP_GRAYSCALE, DIP_RGB).
// norm: Indicates whether to normalize pixel values (default is false).
Image(std::string filename, ImageModes mode, bool norm = false);

// Retrieves the name of the current image format as a string.
std::string getFormatName() const {
switch (this->imageFormat) {
case ImageFormat::BMP:
return "BMP";
default:
return "Unsupported format";
}
}
// Returns the width of the image in pixels.
size_t getWidth() const { return this->width; }
// Returns the height of the image in pixels.
size_t getHeight() const { return this->height; }
// Returns the bit depth of the image.
int getBitDepth() const { return this->bitDepth; }

private:
// Enum to represent supported image formats.
enum class ImageFormat {
ERROR, // Represents an error or unsupported format.
BMP, // BMP file format.
} imageFormat;
// Mode of the image (e.g., DIP_GRAYSCALE, DIP_RGB).
ImageModes imageMode;
// Width of the image in pixels.
size_t width;
// Height of the image in pixels.
size_t height;
// Bit depth of the image.
int bitDepth;
// Normalization flag.
bool isNorm;
// Determines the image format from raw file data.
void determineFormat(const std::vector<uint8_t> &fileData);
// Decodes a BMP image from raw file data.
bool decodeBMP(const std::vector<uint8_t> &fileData);
};

// Image Container Constructor
// Constructs an image container object from the image file path.
template <typename T, std::size_t N>
Image<T, N>::Image(std::string filePath, ImageModes mode, bool norm)
: imageMode(mode), isNorm(norm) {
// ---------------------------------------------------------------------------
// 1. Read the image file into a std::vector.
// ---------------------------------------------------------------------------
// Open the file in binary mode and position the file pointer at the end of
// the file.
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
// Check if the file was successfully opened.
if (!file) {
throw std::runtime_error("Error: Unable to open file at " + filePath);
}
// Get the size of the file.
size_t dataLength = file.tellg();
// Move file pointer to the beginning of the file.
file.seekg(0, std::ios::beg);
// Create a vector to store the data.
std::vector<uint8_t> fileData(dataLength);
// Read the data.
if (!file.read(reinterpret_cast<char *>(fileData.data()), dataLength)) {
throw std::runtime_error("Error: Unable to read data from " + filePath);
}
file.close();

// ---------------------------------------------------------------------------
// 2. Determine the image format and decode the image data into MemRef.
// ---------------------------------------------------------------------------
// Determine the image format from the raw file data.
determineFormat(fileData);
if (this->imageFormat == ImageFormat::BMP) {
bool success = decodeBMP(fileData);
if (!success) {
this->imageFormat = ImageFormat::ERROR;
throw std::runtime_error("Failed to decode BMP file from " + filePath);
};
} else {
throw std::runtime_error("Unsupported image file format.");
}
}

// Determines the image format by inspecting the header of the file data.
template <typename T, std::size_t N>
void Image<T, N>::determineFormat(const std::vector<uint8_t> &fileData) {
if (fileData.size() > 2 && fileData[0] == 'B' && fileData[1] == 'M') {
this->imageFormat = ImageFormat::BMP;
} else {
this->imageFormat = ImageFormat::ERROR;
}
}

// BMP Image File Decoder
template <typename T, std::size_t N>
bool Image<T, N>::decodeBMP(const std::vector<uint8_t> &fileData) {
// Check if the provided data is large enough to contain a minimal BMP header
// (54 bytes).
if (fileData.size() < 54) {
throw std::runtime_error("Invalid BMP File: too small to contain header");
}

// Extract image information from BMP header
this->width = *reinterpret_cast<const int32_t *>(&fileData[18]);
this->height = *reinterpret_cast<const int32_t *>(&fileData[22]);
this->bitDepth = *reinterpret_cast<const uint16_t *>(&fileData[28]);
uint32_t compression = *reinterpret_cast<const uint32_t *>(&fileData[30]);
size_t pixelDataOffset = *reinterpret_cast<const uint32_t *>(&fileData[10]);

// Currently, only the BI_RGB (value 0) compression method is supported.
if (compression != 0) {
std::cerr << "Unsupported BMP file compression method." << std::endl;
return false;
}

// Currently, only the NCHW format with 4 dimensions is supported.
if (N == 4) {
if (this->imageMode == ImageModes::DIP_GRAYSCALE) {
// TODO: Add batch setting.
this->sizes[0] = 1;
this->sizes[1] = 1;
this->sizes[2] = this->height;
this->sizes[3] = this->width;
this->setStrides();
size_t size = this->product(this->sizes);
this->allocated = (T *)malloc(sizeof(T) * size);
this->aligned = this->allocated;
// Fullfill data to memref container.
size_t memrefIndex = 0;
if (this->bitDepth == 32) {
// BMP file is upside-down storage.
for (size_t i = this->height; i > 0; i--) {
for (size_t j = 0; j < this->width; j++) {
// Locate the current pixel.
size_t pixelIndex =
pixelDataOffset + (((i - 1) * this->width) + j) * 4;
// Extract the blue, green, and red value from the current pixel.
int bluePixel =
*reinterpret_cast<const uint8_t *>(&fileData[pixelIndex]);
int greenPixel =
*reinterpret_cast<const uint8_t *>(&fileData[pixelIndex + 1]);
int redPixel =
*reinterpret_cast<const uint8_t *>(&fileData[pixelIndex + 2]);
// Calculate the gray scale value.
int grayScaleValue = static_cast<int>(
0.299 * redPixel + 0.587 * greenPixel + 0.114 * bluePixel);
// Store the gray scale value into memref container.
this->aligned[memrefIndex] =
this->isNorm ? static_cast<T>(grayScaleValue) / 255
: static_cast<T>(grayScaleValue);
memrefIndex++;
}
}
} else {
std::cerr << "Unsupported: " << this->bitDepth << "bit depth."
<< std::endl;
return false;
}
} else if (this->imageMode == ImageModes::DIP_RGB) {
// TODO: Add batch setting.
this->sizes[0] = 1;
this->sizes[1] = 3;
this->sizes[2] = this->height;
this->sizes[3] = this->width;
this->setStrides();
size_t size = this->product(this->sizes);
this->allocated = (T *)malloc(sizeof(T) * size);
this->aligned = this->allocated;
// Fullfill data to memref container.
size_t memrefIndex = 0;
size_t colorStride = this->height * this->width;
if (this->bitDepth == 32) {
// BMP file is upside-down storage.
for (size_t i = height; i > 0; i--) {
for (size_t j = 0; j < width; j++) {
// Locate the current pixel.
size_t pixelIndex = pixelDataOffset + (((i - 1) * width) + j) * 4;
// Extract the blue, green, and red value from the current pixel.
int bluePixel =
*reinterpret_cast<const uint8_t *>(&fileData[pixelIndex]);
int greenPixel =
*reinterpret_cast<const uint8_t *>(&fileData[pixelIndex + 1]);
int redPixel =
*reinterpret_cast<const uint8_t *>(&fileData[pixelIndex + 2]);
// Store the values into memref container as RGB order. (BGR -> RGB)
this->aligned[memrefIndex] = this->isNorm
? static_cast<T>(redPixel) / 255
: static_cast<T>(redPixel);
this->aligned[memrefIndex + colorStride] =
this->isNorm ? static_cast<T>(greenPixel) / 255
: static_cast<T>(greenPixel);
this->aligned[memrefIndex + 2 * colorStride] =
this->isNorm ? static_cast<T>(bluePixel) / 255
: static_cast<T>(bluePixel);
memrefIndex++;
}
}
} else {
std::cerr << "Unsupported: " << this->bitDepth << "bit depth."
<< std::endl;
return false;
}
}
} else {
std::cerr << "Unsupported: " << N << " dimension layout." << std::endl;
return false;
}
return true;
}

} // namespace dip

#endif // FRONTEND_INTERFACES_BUDDY_DIP_IMGCONTAINER
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ set(BUDDY_TEST_DEPENDS
buddy-translate
buddy-container-test
buddy-audio-container-test
buddy-new-image-container-test
buddy-text-container-test
mlir-cpu-runner
)
Expand Down
6 changes: 5 additions & 1 deletion tests/Interface/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ if(BUDDY_MLIR_ENABLE_DIP_LIB OR BUDDY_ENABLE_OPENCV)
)
endif()

_add_test_executable(buddy-new-image-container-test
NewImageContainerTest.cpp
)

_add_test_executable(buddy-audio-container-test
AudioContainerTest.cpp
)

_add_test_executable(buddy-text-container-test
TextContainerTest.cpp
)
)
Loading

0 comments on commit f2d027d

Please sign in to comment.