#include "stdafx.h" #include "draw.h" #include <thread> #include "help.h" #include "calcul.h" using namespace std; ///Plots pair of the time series mapped on to the no/accumulated distance matrix. ///@param[in] input time series to be plotted ///@param[in] result dtw method results ///@param[in] info informations about the input time series ///@param[in] params parameters void draw::plotPair(inputMethod const &input, resultMethod const &result, inputInfo const &info, parameter const ¶ms) { CImg<short> imgA; CImg<short> imgB; auto min = std::min(calcul::vtrMin(input.A), calcul::vtrMin(input.B)); auto max = std::max(calcul::vtrMax(input.A), calcul::vtrMax(input.B)); auto minMean = std::min(calcul::vtrMin(calcul::vtrMean(input.A)), calcul::vtrMin(calcul::vtrMean(input.B))); auto maxMean = std::max(calcul::vtrMax(calcul::vtrMean(input.A)), calcul::vtrMax(calcul::vtrMean(input.B))); if (input.A[0].size() > 1) { imgA = (draw::drawSeriesBars(input.A, min, max).append(draw::drawSeriesMean(input.A, minMean, maxMean), 'y')).rotate(90); imgB = draw::drawSeriesBars(input.B, min, max).append(draw::drawSeriesMean(input.B, minMean, maxMean), 'y'); } else { imgA = draw::drawSeries(input.A, min, max).rotate(90); imgB = draw::drawSeries(input.B, min, max); } CImg<short> m2 = result.matrix_noacc; m2 = m2.append(imgB, 'y'); CImg<short> all; all = result.matrix_acc; all = all.append(imgA); all.append(m2); all.save(generateFileName(info, params.outDraw).c_str()); if(params.drawSleep > 0) std::this_thread::sleep_for(chrono::milliseconds(params.drawSleep)); } ///Plots segmented (voting experts) pair of time series mapped on to the no/accumulated distance matrix. ///@param[in] input time series to be plotted ///@param[in] result dtw method results ///@param[in] info informations about the input time series ///@param[in] params parameters void draw::plotSegmets(inputMethod const &input, vtr2<resultMethod> const &result, inputInfo const &info, parameter const ¶ms) { CImg<short> matrix; for (size_t i = 0; i < result.size(); i++) { CImg<short> row; for (size_t j = 0; j < result[i].size(); j++) { row = row.append(result[i][j].matrix_noacc); } matrix = matrix.append(row, 'y'); } const float red[] = { 255, 255,0 }; CImg<short> serieA; CImg<short> serieB; auto min = std::min(calcul::vtrMin(input.A), calcul::vtrMin(input.B)); auto max = std::max(calcul::vtrMax(input.A), calcul::vtrMax(input.B)); if (input.A[0].size() > 1) { serieA = (draw::drawSeriesBars(input.A, min, max).append(draw::drawSeriesMean(input.A, min, max))).rotate(90); serieB = draw::drawSeriesBars(input.B, min, max).append(draw::drawSeriesMean(input.B, min, max), 'y'); } else { serieA = draw::drawSeries(input.A, min, max); serieB = draw::drawSeries(input.B, min, max); drawVerticalBreaks(serieA, result, red); drawHorizontalBreaks(serieB, result, red); serieA.rotate(90); } auto all = matrix.append(serieB, 'y'); all = serieA.append(all); all.save(generateFileName(info, params.outDraw).c_str()); if (params.drawSleep > 0) std::this_thread::sleep_for(chrono::milliseconds(params.drawSleep)); } ///Plots minimums (Kocyan), flexible pass (Kocyan), and the distance matrix (internally used by dtw and lcss methods). ///@param[in] matrix distance matrix ///@param[in] warpings warping paths from the dtw or lcss methods ///@param[in] minims local minimums of the no-accumulated distance matrix ///@param[in] params parameters ///@return image CImg<short> draw::drawCombine(vtr2<node> const &matrix, vtr<resultPath> const &warpings, vtr<coord> const &minims, parameter const ¶ms) { CImg<short> img((int)matrix[0].size() - 1, (int)matrix.size() - 1, 1, 3, 0); const float green[] = { 0, 255, 0 }; const float purple[] = { 200, 0, 200 }; drawMatrix(img, matrix, params); if (params.drawFlex) drawFlexibleWindow(img, matrix, warpings, purple, params); drawWarpings(img, warpings, green); if (params.drawMin) drawMinimums(img, minims); return img; } ///Plots no/accumulated distance matrix. ///@param[in] img picture to plot into ///@param[in] matrix results from the dtw and lcss method ///@param[in] params parameters void draw::drawMatrix(CImg<short> &img, vtr2<node> const &matrix, parameter const ¶ms) { double min = getMin(matrix, params); double max = getMax(matrix, params); for (size_t i = 1; i < matrix.size(); i++) //row//y { for (size_t j = 1; j < matrix[i].size(); j++) //coll//x { auto colorRGB = draw::getColor(min, max, matrix[i][j].value); const float color[] = { colorRGB.r, colorRGB.g, colorRGB.b }; img.draw_point((int)j - 1, (int)i - 1, color); } } } ///Draws minimums (Kocyan) into the no/accumulated distance matrix. ///@param[in] img picture to draw into ///@param[in] minims minimums to be plotted void draw::drawMinimums(CImg<short> &img, vtr<coord> const &minims) { const float red[] = { 255, 0, 0 }; for (size_t i = 0; i < minims.size(); i++) { img.draw_point(minims[i].col - 1, minims[i].row - 1, red); } } ///Draws minimums (Kocyan) into the no/accumulated distance matrix. ///@param[in] img picture to draw into ///@param[in] warpings warping paths from the dtw or lcss methods ///@param[in] color warping paths color void draw::drawWarpings(CImg<short> &img, vtr<resultPath> const &warpings, const float *color) { for (size_t i = 0; i < warpings.size(); i++) { for (size_t j = 0; j < warpings[i].pathCoords.size(); j++) { img.draw_point(warpings[i].pathCoords[j].col - 1, warpings[i].pathCoords[j].row - 1, color); } } } ///Draws horizontal breaks of the segmented (Kocyan - voting experts) time series into the no/accumulated distance matrix. ///@param[in] img picture to draw into ///@param[in] result dtw sub matrix plots results (contains lengths of time series segments) ///@param[in] color horizontal breaks color void draw::drawHorizontalBreaks(CImg<short> &img, vtr2<resultMethod> const &result, const float *color) { int len = 0; for (int i = 0; i < (int)result[0].size(); i++) { len += result[0][i].matrix_noacc.width(); img.draw_line(len - 1, 0, len - 1, (int)(img.height()), color); } } ///Draws vertical breaks of the segmented (Kocyan - voting experts) time series into the no/accumulated distance matrix. ///@param[in] img picture to draw into ///@param[in] result dtw sub matrix plots results (contains lengths of time series segments) ///@param[in] color vertical breaks color void draw::drawVerticalBreaks(CImg<short> &img, vtr2<resultMethod> const &result, const float *color) { int len = 0; for (size_t i = 0; i < result.size(); i++) { len += result[i][0].matrix_noacc.height(); img.draw_line(len, 0, len, (int)(img.height()), color); } } ///Draws flexible warping pass (Kocyan) into the no/accumulated distance matrix. ///@param[in] img picture to draw into ///@param[in] matrix no/accumulated distance matrix ///@param[in] warpings warping path from the dtw method ///@param[in] color flexible pass color ///@param[in] params parameters void draw::drawFlexibleWindow(CImg<short> &img, vtr2<node> const &matrix, vtr<resultPath> const &warpings, const float *color, parameter const ¶ms) { for (size_t i = 0; i < warpings.size(); i++) { for (size_t k = 0; k < warpings[i].pathCoords.size(); k++) { int kd = (int)k + params.fd; coord past = kd < (int)warpings[i].pathCoords.size() ? warpings[i].pathCoords[kd] : warpings[i].pathCoords.back(); for (int j = 0; j < (int)matrix[warpings[i].pathCoords[k].row].size(); j++) { if (calcul::isFlexiblePass(warpings[i].pathCoords[k].row, j, past, params.fw)) img.draw_point(j, warpings[i].pathCoords[k].row, color); } } } } ///Draws time series graph. ///@param[in] series time series to be plotted ///@param[in] min minimal value found in the two input time series ///@param[in] max maximal value found in the two input time series ///@return image CImg<short> draw::drawSeries(vtr2<double> const &series, double min, double max) { int height = (int)ceil(series.size() * 0.1); CImg<short> img((int)series.size(), height, 1, 3, 0); double mul = (height - 1) / abs(max + abs(min)); int negativeFix = min < 0 ? (int)ceil(mul * abs(min)) : 0; const float color[] = { 255, 0, 0 }; for (size_t i = 0; i < series.size() - 1; i++) { int y1 = (int)(mul * series[i][0]); int y2 = (int)(mul * series[i + 1][0]); img.draw_line((int)i, y1 + negativeFix, (int)i + 1, y2 + negativeFix, color); } return img.mirror('y'); } ///Draws time series in bar format. ///@param[in] series time series to be plotted ///@param[in] min minimal value found in the two input time series ///@param[in] max maximal value found in the two input time series ///@return image CImg<short> draw::drawSeriesBars(vtr2<double> const &series, double min, double max) { int barSize = (int)ceil(series.size() * 0.01); CImg<short> img((int)series.size(), barSize * (int)series[0].size(), 1, 3, 0); size_t dims = series[0].size(); for (size_t i = 0; i < dims; i++) { for (size_t j = 0; j < series.size(); j++) //col x { color mycolor = getColor(min, max, series[j][i]); const float color[] = { mycolor.r, mycolor.g, mycolor.b }; img.draw_line((int)j, (int)i * barSize, (int)j, ((int)i + 1) * barSize, color); } } return img; } ///Draws averaged time series (used for plotting time series with multiple dimensions). ///@param[in] series time series to be plotted ///@param[in] min minimal value found in the two input time series ///@param[in] max maximal value found in the two input time series ///@return image CImg<short> draw::drawSeriesMean(vtr2<double> const &series, double min, double max) { int height = (int)ceil(series.size() * 0.1); CImg<short> img((int)series.size(), height, 1, 3, 0); double mul = (height - 1) / abs(max + abs(min)/*- floor(max - min)*/); // -2 ..means are double... so they overlap with bars auto means = calcul::vtrMean(series); int negativeFix = min < 0 ? (int)ceil(mul * abs(min)) : 0; const float color[] = { 255, 0, 0 }; for (size_t i = 0; i < series.size() - 1; i++) { int y1 = (int)floor(mul * means[i]); int y2 = (int)floor(mul * means[i + 1]); img.draw_line((int)i, y1 + negativeFix, (int)i + 1, y2 + negativeFix, color); } return img.mirror('y'); } ///Finds maximal value in the distance matrix. ///@param[in] matrix distance matrix ///@param[in] params parameters ///@return maximal value in the distance matrix double draw::getMax(vtr2<node> const &matrix, parameter const ¶ms) { int lenA = (int)matrix.size() - 1; int lenB = (int)matrix[0].size() - 1; double max = constant::MIN_double; const int w = (int)(lenB * params.w); double coef = lenB / static_cast<double>(lenA); for (int i = 1; i < lenA + 1; i++) // Iterating over rows { const size_t start = calcul::dtwPassStart(i, w, coef); const size_t end = calcul::dtwPassEnd(i, w, coef, lenB); for (size_t j = start; j < end; j++) { if (matrix[i][j].value > max) max = matrix[i][j].value; } } return max; } ///Finds minimal value in the distance matrix. ///@param[in] matrix distance matrix ///@param[in] params parameters ///@return maximal value in the distance matrix double draw::getMin(vtr2<node> const &matrix, parameter const ¶ms) { int lenA = (int)matrix.size() - 1; int lenB = (int)matrix[0].size() - 1; double min = constant::MAX_double; const int w = (int)(lenB * params.w); double coef = lenB / static_cast<double>(lenA); for (int i = 1; i < lenA + 1; i++) // Iterating over rows { const size_t start = calcul::dtwPassStart(i, w, coef); const size_t end = calcul::dtwPassEnd(i, w, coef, lenB); for (size_t j = start; j < end; j++) { if (matrix[i][j].value < min) min = matrix[i][j].value; } } return min; } ///Generates file name for output image file (Only bmp format is supported). ///@param[in] info informations about the input time series ///@param[in] path output file path (folder or specific file name) ///@return file name (path + name) string draw::generateFileName(inputInfo const &info, string path) { stringstream ss; if (help::isPath(path)) { if (help::isFolder(path)) { ss << path << "\\" << info.nameA << ((info.nameA == to_string(info.idxA)) ? "" : ("(" + to_string(info.idxA + 1) + ")")) << "-" << info.nameB << ((info.nameB == to_string(info.idxB)) ? "" : ("(" + to_string(info.idxB + 1) + ")")) << ".bmp"; } else { ss << path; } } else { size_t nameIdx = path.find_last_of("\\/") + 1; string nameOnly = path.substr(nameIdx, (int)path.size() - nameIdx); size_t extIdx = nameOnly.rfind(".bmp"); if (extIdx != string::npos && nameOnly.size() - (extIdx) == 4) { ss << path; } else { ss << path << ".bmp"; } } return ss.str(); } ///Generates distance matrix cell color. ///@param[in] min minimal value in the distance matrix ///@param[in] max maximal value in the distance matrix ///@param[in] value specific distance cell value ///@return color color draw::getColor(double min, double max, double value) { double mul = (max != 0) ? 1275 / abs(max - min) : 0; //0-x ... x+1colors float lvl = (float)(mul * value); int lvlBorder = (int)(lvl / 255) * 255; float exact = lvl - lvlBorder; exact = exact > 0 ? exact : 255; color c; if (lvl <= 255) //black - blue { c.r = 0; c.g = 0; c.b = lvl; } else if (lvl <= 2 * 255) //blue - purple { c.r = exact; c.g = 0; c.b = 255; } else if (lvl <= 3 * 255) //purple - red { c.r = 255; c.g = 0; c.b = 255 - exact; } else if (lvl <= 4 * 255) //red - orange - yellow { c.r = 255; c.g = exact; c.b = 0; } else if (lvl <= 5 * 255) //yellow - white { c.r = 255; c.g = 255; c.b = exact; } else //white (peaks) { c.r = 255; c.g = 255; c.b = 255; } return c; }