Last active
January 30, 2018 09:05
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*/////////////////////////////////////////////////////////////////////////////////////// | |
// | |
// 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, ¤tImage, 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