/* Convolution filter matrix example formats: {0, 1,2} { {0,1,2}, {1,2,1}, {0, 1, 0}} */ void extractConvolveFilterMatrix(topologyConfigSpec_t ¶ms, std::istringstream &ss) { char c; enum state_t { INIT, LEFTBRACE, RIGHTBRACE, COMMA, NUM }; enum action_t { SKIP, ILL, PLINC, PLDECX, STONYINC, STONXINC, ACCUM }; state_t lastState = INIT; state_t newState = INIT; int braceLevel = 0; vector<float> row; vector<vector<float>> mat; float num = 0.0; action_t table[5][5] = { /* INIT LEFTBRACE RIGHTBRACE COMMA NUM */ /* INIT */ { ILL, PLINC, ILL, ILL, ILL }, /* LEFTBRACE */ { ILL, PLINC, ILL, ILL, ACCUM }, /* RIGHTBRACE */ { ILL, ILL, PLDECX, SKIP, ILL }, /* COMMA */ { ILL, PLINC, ILL, ILL, ACCUM }, /* DIGIT */ { ILL, ILL, STONYINC, STONXINC, ACCUM }, }; bool done = false; while (!done && ss) { ss >> c; if (isspace(c)) { continue; } else if (c == '{') { newState = LEFTBRACE; } else if (c == '}') { newState = RIGHTBRACE; } else if (c == ',') { newState = COMMA; } else if (c == '-' || c == '+' || c == '.' || isdigit(c)) { newState = NUM; } else { configErrorThrow(params, "Warning: Internal error in parsing convolve filter matrix spec"); } action_t action = table[lastState][newState]; switch(action) { case SKIP: break; case ILL: configErrorThrow(params, "Syntax error in convolve filter matrix spec"); break; case PLINC: ++braceLevel; break; case PLDECX: --braceLevel; if (braceLevel != 0) { configErrorThrow(params, "Syntax error in convolve filter matrix spec"); } done = true; break; case STONYINC: row.push_back(num); // Add the element to the row mat.push_back(row); // Add the row to the matrix row.clear(); num = 0.0; // Start a new number after this if (--braceLevel == 0) { done = true; } break; case STONXINC: row.push_back(num); // Add the element to the row num = 0.0; // Start a new number after this break; case ACCUM: // We've got the first char of the number in c, which can be -, +, ., or a digit. // Now gather the rest of the numeric string: string numstr; numstr.clear(); numstr.push_back(c); while (ss.peek() == '.' || isdigit(ss.peek())) { char cc; ss >> cc; numstr.push_back(cc); } num = strtod(numstr.c_str(), NULL); break; } lastState = newState; } // Check that all rows have the same size: unsigned firstRowSize = mat[0].size(); if (0 != count_if(mat.begin() + 1, mat.end(), [firstRowSize](vector<float> row) { return row.size() != firstRowSize; })) { configErrorThrow(params, "Error in topology config file: inconsistent row size in convolve filter matrix spec"); } // We'll create (or recreate) a one-element flatConvolveMatrix in the params structure: // Convolution filtering only needs a single convolve matrix, so only element zero is // used in params.flatConvolveMatrix. That one element will be a flattened // container of the 2D convolve matrix: params.flatConvolveMatrix.clear(); params.flatConvolveMatrix.push_back(vector<float>()); // Start with one empty container for one kernel params.flatConvolveMatrix.back().assign(mat.size() * mat[0].size(), 0); // for (auto &row : mat) { // for (auto val : row) { for (uint32_t row = 0; row < mat.size(); ++row) { for (uint32_t col = 0; col < mat[row].size(); ++col) { params.flatConvolveMatrix.back()[flattenXY(col, row, mat.size())] = mat[row][col]; } } // The matrix is arranged so that we can access elements as [x][y]: params.kernelSize.x = mat.size(); params.kernelSize.y = mat[0].size(); }
// Extract the input data from the specified file and save the data in the data container. // Returns the nonzero image size if successful, else returns 0,0. // xySize ImageReaderBMP::getData(std::string const &filename, std::vector<float> &dataContainer, ColorChannel_t colorChannel) { FILE* f; fopen_s(&f, filename.c_str(), "rb"); if (f == NULL) { return { 0, 0 }; } // Read the BMP header to get the image dimensions: unsigned char info[54]; if (fread(info, sizeof(unsigned char), 54, f) != 54) { fclose(f); return { 0, 0 }; } if (info[0] != 'B' || info[1] != 'M') { fclose(f); return { 0, 0 }; } // Verify the offset to the pixel data. It should be the same size as the info[] data read above. size_t dataOffset = (info[13] << 24) + (info[12] << 16) + (info[11] << 8) + info[10]; // Verify that the file contains 24 bits (3 bytes) per pixel (red, green blue at 8 bits each): int pixelDepth = (info[29] << 8) + info[28]; if (pixelDepth != 24) { fclose(f); return { 0, 0 }; } // This method of converting 4 bytes to a uint32_t is portable for little- or // big-endian environments: uint32_t width = (info[21] << 24) + (info[20] << 16) + (info[19] << 8) + info[18]; uint32_t height = (info[25] << 24) + (info[24] << 16) + (info[23] << 8) + info[22]; // Position the read pointer to the first byte of pixel data: if (fseek(f, dataOffset, SEEK_SET) != 0) { fclose(f); return { 0, 0 }; } uint32_t rowLen_padded = (width*3 + 3) & (~3); std::unique_ptr<unsigned char[]> imageData {new unsigned char[rowLen_padded]}; dataContainer.clear(); dataContainer.assign(width * height, 0); // Pre-allocate to make random access easy // Fill the data container with 8-bit data taken from the image data: for (uint32_t y = 0; y < height; ++y) { if (fread(imageData.get(), sizeof(unsigned char), rowLen_padded, f) != rowLen_padded) { fclose(f); return { 0, 0 }; } // BMP pixels are arranged in memory in the order (B, G, R): unsigned val = 0; for (uint32_t x = 0; x < width; ++x) { if (colorChannel == NNet::R) { val = imageData[x * 3 + 2]; // Red } else if (colorChannel == NNet::G) { val = imageData[x * 3 + 1]; // Green } else if (colorChannel == NNet::B) { val = imageData[x * 3 + 0]; // Blue } else if (colorChannel == NNet::BW) { // Rounds down: val = (unsigned)(0.3 * imageData[x*3 + 2] + // Red 0.6 * imageData[x*3 + 1] + // Green 0.1 * imageData[x*3 + 0]); // Blue } else { err << "Error: unknown pixel conversion" << endl; throw exceptionImageFile(); } // Convert the pixel from the range 0..256 to a smaller // range that we can input into the neural net: // Also we'll invert the rows so that the origin is the upper left at 0,0: dataContainer[flattenXY(x, (height - y) - 1, height)] = pixelToNetworkInputRange(val); } } fclose(f); return { width, height }; }