Skip to content

Instantly share code, notes, and snippets.

@ndvbd
Last active January 30, 2018 09:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ndvbd/905be3426b56d4dfdd0860df16608c61 to your computer and use it in GitHub Desktop.
Save ndvbd/905be3426b56d4dfdd0860df16608c61 to your computer and use it in GitHub Desktop.
NadavWatch - View a sequence of PNG files in a directory, allow zoom, pixel values and more.
/*///////////////////////////////////////////////////////////////////////////////////////
//
// NadavWatch
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
// Nadav Benedek, 2018
//*/
//To put in context menu: https://www.howtogeek.com/107965/how-to-add-any-application-shortcut-to-windows-explorers-context-menu/
#include "opencv2/opencv.hpp"
#include <ctime>
#include <conio.h>
#include <cstring>
#include <iomanip>
#include <filesystem>
#include <algorithm>
#include <string>
#include <sstream>
#include <thread>
#include <experimental/filesystem>
#include <windows.h>
using namespace std;
using namespace cv;
vector<string> get_dir_files(string dir_path, string mask) {
vector<string> img_filenames;
std::tr2::sys::path path(dir_path);
auto it = std::tr2::sys::directory_iterator(path);
for (auto it = std::tr2::sys::directory_iterator(path); it != std::tr2::sys::directory_iterator(); ++it)
{
auto& file = it->path();
#if _MSC_VER >= 1900
string extension = file.extension().string();
string base = file.filename().string();
#elif _MSC_VER == 1800
string extension = file.extension();
string base = file.filename();
#endif
bool is_png = extension.find("png") != std::string::npos;
bool mask_test = base.substr(0, mask.length()) == mask;
if (!std::tr2::sys::is_directory(file) && is_png && mask_test)
{
img_filenames.push_back(file.string());
}
}
if (img_filenames.size() == 0)
{
cout << "error: no files in directory " << path.string() << endl;
return vector<string>();
}
return img_filenames;
} // EOF get_dir_files
#define KEY_UP 72
#define KEY_DOWN 80
#define KEY_LEFT 75
#define KEY_RIGHT 77
float requestedScale = 1.0;
float actualScale = 1.0;
float centerX = -1;
float centerY = -1;
int width = -1;
int height = -1;
volatile bool change = true;
volatile bool reload = false;
int topLeftX;
int topLeftY;
int lowerRightX;
int lowerRightY;
int mouseImagePosX;
int mouseImagePosY;
bool showNumbers = true;
Mat matRead;
double lastResizedArea = 0;
int initialWidth = 0;
int initialHeight = 0;
const cv::String windowName = "NadavWatch";
int getWindowArea(int & width, int & height){
HWND win_handle = (HWND) cvGetWindowHandle(windowName.c_str());
if (!win_handle) {
printf("Failed FindWindow\n");
return -1;
}
RECT rect;
if (GetWindowRect(win_handle, &rect)) {
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
}
GetClientRect(win_handle, &rect);
//cout << "left: " << rect.left << " top: " << rect.top << " right: " << rect.right << " bottom: " << rect.bottom << " ratio: " << (rect.right/(float)rect.bottom) << endl;
width = rect.right;
height = rect.bottom;
return rect.right * rect.bottom;
}
//bool onlyonetime = true;
void CallBackFunc(int event, int x, int y, int flags, void* userdata) {
// Fix ratio
//cout << "EVENT: " << event << endl;
int tmpWidht, tmpHeight;
int resizedArea = getWindowArea(tmpWidht, tmpHeight);
//if (onlyonetime == true && (resizedArea > lastResizedArea*1.001 || resizedArea < lastResizedArea/1.001) ) {
if ((resizedArea > lastResizedArea*1.001 || resizedArea < lastResizedArea/1.001) ) {
double scaleFactor = sqrt(initialWidth * initialHeight / (double)resizedArea);
cout << "resizing. resizedArea = " << resizedArea << " scaleFactor = " << scaleFactor << endl;
int newWidht = (int)( initialWidth / scaleFactor);
int newHeight = initialHeight / scaleFactor;
resizeWindow(windowName,newWidht, newHeight);
//resizeWindow(windowName,200, 200);
cout << " new width = " << newWidht << " new height = " << newHeight << endl;
lastResizedArea = getWindowArea(tmpWidht, tmpHeight);
cout << "setting lastResizedArea="<< lastResizedArea<< endl;
//onlyonetime = false;
}
if (event == EVENT_LBUTTONDOWN) {
cout << "Left button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
centerX = mouseImagePosX;
centerY = mouseImagePosY;
//cout << "new center is at x: " << centerX << " y: " << centerY << endl;
cout << "new center is at x: " << centerX << " y: " << centerY << " value: " << (int)matRead.at<uchar>(centerY, centerX) << endl;
change = true;
} else if (event == EVENT_RBUTTONDOWN) {
cout << "Right button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
} else if (event == EVENT_MBUTTONDOWN) {
cout << "Middle button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
} else if (event == EVENT_MOUSEMOVE) {
mouseImagePosX = topLeftX + x / actualScale;
mouseImagePosY = topLeftY + y / actualScale;
//cout << "Mouse move over the window - position (" << x << ", " << y << ")" << " Actual mouseImagePosX: " << mouseImagePosX << " actualScale: "<< actualScale<< endl;
change = true;
} else if (event == EVENT_MOUSEWHEEL) {
if (flags > 0) { // zoom in
requestedScale *= 1.1;
} else {
requestedScale /= 1.1;
if (requestedScale < 1.0) requestedScale = 1.0;
}
change = true;
} else if (event == EVENT_MOUSEHWHEEL) {
cout << "EVENT_MOUSEHWHEEL - position (" << x << ", " << y << ")" << endl;
} else {
cout << "EVENT: " << event << endl;
}
}
std::string current_working_directory() {
char working_directory[300 + 1];
GetCurrentDirectoryA(sizeof(working_directory), working_directory); // **** win32 specific ****
return working_directory;
}
std::string currentImageTitle = "#";
int alpha_slider_max = 10;
int currentImage = 0;
void on_trackbar(int, void*) {
reload = true;
}
bool isHistogramOn = false;
std::string histogramWindowName = "NadavHistogram";
void histogram(Mat & src) {
Mat dst;
int numberOfBins = 256; /// number of bins
float range[] = { 0, 256 };
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
Mat b_hist;
calcHist(&src, 1, 0, Mat(), b_hist, 1, &numberOfBins, &histRange, uniform, accumulate);
int histogramImageWidth = 512;
int histogramImageHeight = 400;
int paddingWidth = 50;
int paddingHeight = 50;
int binWidth = cvRound((double)histogramImageWidth / numberOfBins);
Mat histImage(histogramImageHeight + paddingHeight * 2, histogramImageWidth + paddingWidth * 2, CV_8UC3, Scalar(0, 0, 0));
/// Normalize the result to [ 0, histImage.rows ]
normalize(b_hist, b_hist, 0, histogramImageHeight, NORM_MINMAX, -1, Mat());
float cumsum_factor = sum(b_hist)[0] / histogramImageHeight;
/// Draw for each channel
float cumsum = 0;
for (int i = 1; i < numberOfBins; i++) {
auto val = b_hist.at<float>(i - 1);
auto val2 = b_hist.at<float>(i);
cumsum += val/ cumsum_factor;
auto cumsum2 = cumsum + val2 / cumsum_factor;
line(histImage, Point(binWidth*(i - 1) + paddingWidth, paddingHeight + histogramImageHeight - cvRound(val)),
Point(binWidth*(i)+paddingWidth, paddingHeight + histogramImageHeight - cvRound(val2)),
Scalar(255, 0, 0), 2, 8, 0);
line(histImage, Point(binWidth*(i - 1) + paddingWidth, paddingHeight + histogramImageHeight - cvRound(cumsum)),
Point(binWidth*(i)+paddingWidth, paddingHeight + histogramImageHeight - cvRound(cumsum2)),
Scalar(0, 0, 255), 2, 8, 0);
}
namedWindow(histogramWindowName, CV_WINDOW_NORMAL);
imshow(histogramWindowName, histImage);
waitKey(10);
} // histogram
int main(int argc, char** argv) {
cout << "my directory is " << current_working_directory() << "\n";
cout << "Got " << argc << "arguments" << endl;
vector<string> files;
if (argc == 2) {
files = get_dir_files(argv[1], "");
} else {
files = get_dir_files(current_working_directory(), "");
}
cout << "found " << files.size() << " files" << endl;
alpha_slider_max = files.size() - 1;
namedWindow(windowName, CV_WINDOW_NORMAL | CV_WINDOW_KEEPRATIO | CV_GUI_EXPANDED); // doesn't work well
matRead = imread(files[currentImage], IMREAD_GRAYSCALE + IMREAD_ANYDEPTH);
// We need to resize it to small area first, because of OpenCV bug - When we add the slider after that, and increase the image, the slider increases but then we cant reduce it because of slider
resizeWindow(windowName, 300, 300 * matRead.rows / matRead.cols);
lastResizedArea = getWindowArea(initialWidth, initialHeight); // The openCV will limit the width according to screen!
cout << " initial area= " << lastResizedArea << " initialWidth= " << initialWidth<< " initialHeight = " << initialHeight << endl;
setMouseCallback(windowName, CallBackFunc);
createTrackbar("Frame #", windowName, &currentImage, alpha_slider_max, on_trackbar);
while (true) {
if (currentImage < 0) currentImage = 0;
if (currentImage >= files.size()) currentImage = files.size() - 1;
matRead = imread(files[currentImage], IMREAD_GRAYSCALE + IMREAD_ANYDEPTH);
width = matRead.cols; height = matRead.rows;
cout << "Loading image: " << files[currentImage] << " height: " << height << " width: " << width << endl;
if (centerX == -1) {
centerX = matRead.cols / 2;
centerY = matRead.rows / 2;
}
change = true;
setTrackbarPos("Frame #", windowName, currentImage);
while (true) {
//getWindowSize();
if (change) {
topLeftX = max((int)(centerX - (matRead.cols / 2.0) / requestedScale), 0);
topLeftY = max((int)(centerY - (matRead.rows / 2.0) / requestedScale), 0);
//cout << "topLeftX = " << topLeftX << " topLeftX = " << topLeftY << endl;
lowerRightX = min((int)(centerX + (matRead.cols / 2.0) / requestedScale), width - 1);
lowerRightY = min((int)(centerY + (matRead.rows / 2.0) / requestedScale), height - 1);
Rect roi(topLeftX, topLeftY, lowerRightX - topLeftX + 1, lowerRightY - topLeftY + 1); // x, y, width, height
//cout << "roi: " << roi << endl;
Mat imToShow = matRead.clone()(roi);
cv::cvtColor(imToShow, imToShow, CV_GRAY2RGB);
Mat resizedImage;
currentImageTitle = "NadavWatch #" + to_string(currentImage) + string(" s:") + to_string(requestedScale) + string(" x:") + to_string(mouseImagePosX) + string(" y:") + to_string(mouseImagePosY) + string(" value: ") + to_string(matRead.at<uchar>(mouseImagePosY, mouseImagePosX)) + string(" F: ") + files[currentImage];
//cv::putText(imToShow, "666", cv::Point(10, 70), cv::FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2);
if (1 == 1) {
// We want to scale the image so we have place to put pixels for text, rectangles, etc.
// If the image is very small, we still want to enforce major resize.
actualScale = max(requestedScale, (1600.0 / (float)min(roi.width, roi.height))); // Enforce minimum scale
//cout << "tmp scaling for " << actualScale << endl;
resize(imToShow, resizedImage, Size(), actualScale, actualScale, INTER_NEAREST);
int widthPerBoxInResizedImage = resizedImage.cols / roi.width;
int heightPerBoxInResizedImage = resizedImage.rows / roi.height;
if (roi.width < 30 && showNumbers) { // Show pixel numbers
const int font = cv::FONT_HERSHEY_PLAIN;
const float size = 2;
const int thickness = 2;
for (int roiPixelX = 0; roiPixelX < roi.width; roiPixelX++) {
for (int roiPixelY = 0; roiPixelY < roi.height; roiPixelY++) {
int textX = (roiPixelX + 1 - 0.8)*actualScale;
int textY = (roiPixelY + 1 - 0.4)*actualScale;
int textWidth = 4 * size * 8;
int textHeight = 2 * size * 8;
int realXCoord = topLeftX + roiPixelX;
int realYCoord = topLeftY + roiPixelY;
int currentPixelValue = matRead.at<uchar>(realYCoord, realXCoord);
cv::rectangle(resizedImage, cv::Point(roiPixelX*widthPerBoxInResizedImage, roiPixelY*heightPerBoxInResizedImage), cv::Point((roiPixelX + 1)*widthPerBoxInResizedImage, (roiPixelY + 1)*heightPerBoxInResizedImage), cv::Scalar(255, 255, 255), 3);
cv::putText(resizedImage, to_string(currentPixelValue), cv::Point(textX + 1, textY + 1), font, size, (0, 0, 0), thickness);
cv::putText(resizedImage, to_string(currentPixelValue), cv::Point(textX, textY), font, size, (0, 0, 255), thickness);
}
}
}
// Highlight center pixel
int centerXRoi = centerX - topLeftX;
int centerYRoi = centerY - topLeftY;
cv::rectangle(resizedImage, cv::Point(centerXRoi*widthPerBoxInResizedImage, centerYRoi*heightPerBoxInResizedImage), cv::Point((centerXRoi + 1)*widthPerBoxInResizedImage, (centerYRoi + 1)*heightPerBoxInResizedImage), cv::Scalar(0, 255, 0), 5);
//cv::rectangle(resizedImage, cv::Point(centerXRoi, centerYRoi), cv::Point(centerXRoi+requestedScale, centerYRoi+requestedScale), cv::Scalar(0, 255, 0));
} else {
actualScale = requestedScale;
resizedImage = imToShow;
}
setWindowTitle(windowName, currentImageTitle);
cv::imshow(windowName, resizedImage);
if (isHistogramOn) histogram(matRead);
change = false;
} // if change is true
if (reload) {
reload = false;
break;
}
if (!cvGetWindowHandle(windowName.c_str())) {
destroyAllWindows();
exit(1);
}
int got = waitKeyEx(10);
if (got == 2555904) {
//cout << "right " << endl;
got = waitKeyEx(1);
currentImage++; break;
} else if (got == 2424832) {
//cout << "left " << endl;
got = waitKeyEx(1);
currentImage--; break;
} else if (got == 113) { // quit
destroyAllWindows();
exit(1);
} else if (got == 110) { // n- show numbers
showNumbers = !showNumbers; change = true;
} else if (got == 104) { // h - histogram
if (isHistogramOn == false) {
isHistogramOn = true;
} else {
isHistogramOn = false;
destroyWindow(histogramWindowName);
}
change = true;
} else if (got == 99) { // c - centerize picture
centerX = width / 2;
centerY = height / 2;
cout << "new center is at x: " << centerX << " y: " << centerY << " value: " << (int)matRead.at<uchar>(centerY, centerX) << endl;
change = true;
} else if (got == -1) {
continue;
} else {
cout << "keycode: " << got << endl;
}
} // inner loop;
} // outer loop
//getch();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment