CppNCorr
C++ ncorr Digital Image Correlation engine
Loading...
Searching...
No Matches
Image2D.cpp
Go to the documentation of this file.
1/*
2 * File: Image2D.cpp
3 * Author: justin
4 *
5 * Created on January 28, 2015, 3:10 PM
6 * Extended to support in-memory images, video import, and image filtering
7 */
8
9#include "Image2D.h"
10#include "ncorr/log.h"
11#include <filesystem>
12#include <algorithm>
13#include <numeric>
14#include <cmath>
15#include <iomanip>
16#include <sstream>
17
18namespace ncorr {
19
20// ============================================================================
21// Image2D Implementation
22// ============================================================================
23
24// Static factory methods ----------------------------------------------------//
25Image2D Image2D::load(std::ifstream &is) {
26 // Form empty Image2D then fill in values in accordance to how they are saved
27 Image2D img;
28
29 // Load length
30 difference_type length = 0;
31 is.read(reinterpret_cast<char*>(&length), std::streamsize(sizeof(difference_type)));
32
33 // Allocate new string
34 img.filename_ptr = std::make_shared<std::string>(length,' ');
35
36 // Read data
37 is.read(const_cast<char*>(img.filename_ptr->c_str()), std::streamsize(length));
38
39 // Loaded images are always file-based
40 img.storage_mode = StorageMode::FILE_PATH;
41
42 return img;
43}
44
45Image2D Image2D::from_mat(const cv::Mat& mat, const std::string& name, const FilterConfig& filter_config) {
46 cv::Mat processed;
47 if (!filter_config.empty()) {
48 processed = ImageProcessor::apply_filters(mat, filter_config);
49 } else {
50 processed = mat.clone();
51 }
52
53 // Ensure grayscale
54 cv::Mat gray;
55 if (processed.channels() == 3) {
56 cv::cvtColor(processed, gray, cv::COLOR_BGR2GRAY);
57 } else {
58 gray = processed;
59 }
60
61 return Image2D(std::move(gray), name);
62}
63
64Image2D Image2D::from_file_filtered(const std::string& filename, const FilterConfig& filter_config) {
65 cv::Mat img = cv::imread(filename, cv::IMREAD_GRAYSCALE);
66 if (img.empty()) {
67 throw std::invalid_argument("Image file: " + filename + " cannot be found or read.");
68 }
69
70 if (!filter_config.empty()) {
71 img = ImageProcessor::apply_filters(img, filter_config);
72 }
73
74 // Extract just the filename for naming
75 std::filesystem::path p(filename);
76 return Image2D(std::move(img), p.filename().string());
77}
78
79// Operations interface ------------------------------------------------------//
80std::ostream& operator<<(std::ostream &os, const Image2D &img) {
81 os << *img.filename_ptr;
82 if (img.storage_mode == Image2D::StorageMode::IN_MEMORY_MAT) {
83 os << " [in-memory cv::Mat]";
84 } else if (img.storage_mode == Image2D::StorageMode::IN_MEMORY_GS) {
85 os << " [in-memory Array2D]";
86 }
87 return os;
88}
89
90void imshow(const Image2D &img, Image2D::difference_type delay) {
91 imshow(img.get_gs(), delay);
92}
93
94bool isequal(const Image2D &img1, const Image2D &img2) {
95 // For in-memory images, compare data; for file-based, compare filenames
96 if (img1.storage_mode != img2.storage_mode) {
97 return false;
98 }
99
100 if (img1.storage_mode == Image2D::StorageMode::FILE_PATH) {
101 return *img1.filename_ptr == *img2.filename_ptr;
102 }
103
104 // For in-memory, do a data comparison
105 auto gs1 = img1.get_gs();
106 auto gs2 = img2.get_gs();
107
108 if (gs1.height() != gs2.height() || gs1.width() != gs2.width()) {
109 return false;
110 }
111
112 for (Image2D::difference_type r = 0; r < gs1.height(); ++r) {
113 for (Image2D::difference_type c = 0; c < gs1.width(); ++c) {
114 if (std::abs(gs1(r, c) - gs2(r, c)) > 1e-10) {
115 return false;
116 }
117 }
118 }
119
120 return true;
121}
122
123void save(const Image2D &img, std::ofstream &os) {
125
126 // For in-memory images, we save the name but note that load() will fail
127 // unless the data has been saved to disk
128 difference_type length = img.filename_ptr->size();
129 os.write(reinterpret_cast<const char*>(&length), std::streamsize(sizeof(difference_type)));
130 os.write(img.filename_ptr->c_str(), std::streamsize(img.filename_ptr->size()));
131}
132
133// Access --------------------------------------------------------------------//
135 switch (storage_mode) {
137 // Already have grayscale data, return a copy
138 return *gs_ptr;
139 }
140
142 // Convert cv::Mat to Array2D
143 return ImageProcessor::mat_to_array2d(*mat_ptr);
144 }
145
147 default: {
148 // Original behavior: load from file
149 cv::Mat cv_img = cv::imread((*filename_ptr), cv::IMREAD_GRAYSCALE);
150 if (!cv_img.data) {
151 throw std::invalid_argument("Image file : " + *filename_ptr + " cannot be found or read.");
152 }
153
154 // Images will be read as 8-bit grayscale values; convert these to double
155 // precision with values ranging from 0 - 1.
156 Array2D<double> A(cv_img.rows, cv_img.cols);
157 double conversion = 1.0 / 255.0;
158 for (difference_type p2 = 0; p2 < cv_img.rows; ++p2) {
159 for (difference_type p1 = 0; p1 < cv_img.cols; ++p1) {
160 A(p2, p1) = cv_img.data[p1 + p2 * cv_img.cols] * conversion;
161 }
162 }
163 return A;
164 }
165 }
166}
167
168cv::Mat Image2D::get_mat() const {
169 switch (storage_mode) {
171 return mat_ptr->clone();
172 }
173
175 return ImageProcessor::array2d_to_mat(*gs_ptr);
176 }
177
179 default: {
180 cv::Mat img = cv::imread(*filename_ptr, cv::IMREAD_GRAYSCALE);
181 if (img.empty()) {
182 throw std::invalid_argument("Image file: " + *filename_ptr + " cannot be found or read.");
183 }
184 return img;
185 }
186 }
187}
188
189bool Image2D::save_to_file(const std::string& filename) const {
190 cv::Mat mat = get_mat();
191 return cv::imwrite(filename, mat);
192}
193
194// ============================================================================
195// ImageProcessor Implementation
196// ============================================================================
197
198cv::Mat ImageProcessor::saturate(const cv::Mat& input, int level) {
199 // Convert to grayscale if needed
200 cv::Mat gray;
201 if (input.channels() == 3) {
202 cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY);
203 } else {
204 gray = input.clone();
205 }
206
207 // Ensure uint8 format
208 if (gray.type() != CV_8UC1) {
209 gray.convertTo(gray, CV_8UC1);
210 }
211
212 cv::Mat output = gray.clone();
213
214 // Saturate: clip values above level to level
215 // MATLAB satur.m: var(var > level) = level
216 for (int y = 0; y < output.rows; ++y) {
217 for (int x = 0; x < output.cols; ++x) {
218 uint8_t val = output.at<uint8_t>(y, x);
219 if (val > level) {
220 output.at<uint8_t>(y, x) = static_cast<uint8_t>(level);
221 }
222 }
223 }
224
225 return output;
226}
227
228std::vector<cv::Mat> ImageProcessor::saturate(const std::vector<cv::Mat>& input, int level) {
229 std::vector<cv::Mat> output;
230 output.reserve(input.size());
231
232 for (const auto& img : input) {
233 output.push_back(saturate(img, level));
234 }
235
236 return output;
237}
238
239cv::Mat ImageProcessor::apply_bandpass_filter(const cv::Mat& input, const std::vector<int>& params) {
240 if (params.size() < 2) {
241 throw std::invalid_argument("Bandpass filter requires two parameters: {r1, r2}");
242 }
243
244 // Convert to float for processing
245 cv::Mat float_img;
246 input.convertTo(float_img, CV_64F);
247
248 double r1 = static_cast<double>(params[0]);
249 double r2 = static_cast<double>(params[1]);
250
251 int rows = float_img.rows;
252 int cols = float_img.cols;
253
254 // Create Gaussian bandpass filter (matching MATLAB bandpassfft.m)
255 cv::Mat filter_mask(rows, cols, CV_64F);
256 int cx = cols / 2;
257 int cy = rows / 2;
258
259 for (int y = 0; y < rows; ++y) {
260 for (int x = 0; x < cols; ++x) {
261 double dx = x - cx;
262 double dy = y - cy;
263 double r_sq = dx * dx + dy * dy;
264
265 // Gaussian bandpass: exp(-r^2/(2*r2^2)) * (1 - exp(-r^2/(2*r1^2)))
266 double low_pass = std::exp(-r_sq / (2.0 * r2 * r2));
267 double high_pass = (r1 != 0) ? (1.0 - std::exp(-r_sq / (2.0 * r1 * r1))) : 1.0;
268 filter_mask.at<double>(y, x) = low_pass * high_pass;
269 }
270 }
271
272 // Perform FFT
273 cv::Mat planes[] = {float_img, cv::Mat::zeros(float_img.size(), CV_64F)};
274 cv::Mat complex_img;
275 cv::merge(planes, 2, complex_img);
276 cv::dft(complex_img, complex_img);
277
278 // fftshift the filter
279 int cx2 = filter_mask.cols / 2;
280 int cy2 = filter_mask.rows / 2;
281 cv::Mat q0(filter_mask, cv::Rect(0, 0, cx2, cy2));
282 cv::Mat q1(filter_mask, cv::Rect(cx2, 0, cols - cx2, cy2));
283 cv::Mat q2(filter_mask, cv::Rect(0, cy2, cx2, rows - cy2));
284 cv::Mat q3(filter_mask, cv::Rect(cx2, cy2, cols - cx2, rows - cy2));
285
286 cv::Mat filter_shifted(rows, cols, CV_64F);
287 q3.copyTo(filter_shifted(cv::Rect(0, 0, cols - cx2, rows - cy2)));
288 q0.copyTo(filter_shifted(cv::Rect(cols - cx2, rows - cy2, cx2, cy2)));
289 q1.copyTo(filter_shifted(cv::Rect(0, rows - cy2, cols - cx2, cy2)));
290 q2.copyTo(filter_shifted(cv::Rect(cols - cx2, 0, cx2, rows - cy2)));
291
292 // Apply filter in frequency domain
293 cv::split(complex_img, planes);
294 planes[0] = planes[0].mul(filter_shifted);
295 planes[1] = planes[1].mul(filter_shifted);
296 cv::merge(planes, 2, complex_img);
297
298 // Inverse DFT
299 cv::Mat filtered_img;
300 cv::idft(complex_img, filtered_img, cv::DFT_SCALE);
301 cv::split(filtered_img, planes);
302
303 return planes[0]; // Return real part
304}
305
306std::vector<cv::Mat> ImageProcessor::apply_bandpass_filter(const std::vector<cv::Mat>& input,
307 const std::vector<int>& params) {
308 std::vector<cv::Mat> output;
309 output.reserve(input.size());
310
311 for (const auto& img : input) {
312 output.push_back(apply_bandpass_filter(img, params));
313 }
314
315 return output;
316}
317
318cv::Mat ImageProcessor::apply_filters(const cv::Mat& input, const FilterConfig& config) {
319 cv::Mat result = input.clone();
320
321 // Apply saturation first (if enabled)
322 if (config.has_saturation()) {
323 result = saturate(result, config.saturation_level);
324 }
325
326 // Apply bandpass filter (if enabled)
327 if (config.has_bandpass()) {
328 result = apply_bandpass_filter(result, config.bandpass_params);
329 // Convert back to 8-bit after bandpass (which produces float output)
330 if (result.type() != CV_8UC1) {
331 cv::normalize(result, result, 0, 255, cv::NORM_MINMAX);
332 result.convertTo(result, CV_8UC1);
333 }
334 }
335
336 return result;
337}
338
339std::vector<cv::Mat> ImageProcessor::apply_filters(const std::vector<cv::Mat>& input, const FilterConfig& config) {
340 std::vector<cv::Mat> output;
341 output.reserve(input.size());
342
343 for (const auto& img : input) {
344 output.push_back(apply_filters(img, config));
345 }
346
347 return output;
348}
349
350std::pair<std::vector<cv::Mat>, std::pair<double, double>>
351ImageProcessor::filter_like_ben(const std::vector<cv::Mat>& input,
352 const cv::Mat& mask,
353 const std::vector<int>& params,
354 const std::pair<double, double>* gs_boundaries) {
355
356 std::vector<cv::Mat> filtered_images;
357 std::pair<double, double> boundaries;
358
359 // Apply bandpass filter to all images first
360 for (const auto& img : input) {
361 cv::Mat filtered = apply_bandpass_filter(img, params);
362 filtered_images.push_back(filtered);
363 }
364
365 // Compute boundaries from FIRST FILTERED image using percentiles (5th, 95th) if not provided
366 if (gs_boundaries == nullptr) {
367 boundaries = compute_percentile_boundaries(filtered_images[0], mask, 5.0, 95.0);
368 NLOG_INFO << " Computed filter boundaries: [" << boundaries.first << ", " << boundaries.second << "]";
369 } else {
370 boundaries = *gs_boundaries;
371 }
372
373 // Normalize each filtered image using the boundaries
374 for (size_t i = 0; i < filtered_images.size(); ++i) {
375 filtered_images[i] = normalize_and_clamp(filtered_images[i], boundaries);
376 }
377
378 return {filtered_images, boundaries};
379}
380
381cv::Mat ImageProcessor::filter_like_ben_single(const cv::Mat& input,
382 const cv::Mat& mask,
383 const std::vector<int>& params,
384 const std::pair<double, double>* gs_boundaries) {
385 std::vector<cv::Mat> input_vec = {input};
386 auto result = filter_like_ben(input_vec, mask, params, gs_boundaries);
387 return result.first[0];
388}
389
390std::pair<double, double> ImageProcessor::compute_grayscale_boundaries(const cv::Mat& image,
391 const cv::Mat& mask) {
392 cv::Mat float_img;
393 image.convertTo(float_img, CV_32F);
394
395 double min_val = std::numeric_limits<double>::max();
396 double max_val = std::numeric_limits<double>::lowest();
397
398 for (int y = 0; y < image.rows; ++y) {
399 for (int x = 0; x < image.cols; ++x) {
400 if (mask.empty() || mask.at<uint8_t>(y, x) > 0) {
401 double val = float_img.at<float>(y, x);
402 min_val = std::min(min_val, val);
403 max_val = std::max(max_val, val);
404 }
405 }
406 }
407
408 return {min_val, max_val};
409}
410
411std::pair<double, double> ImageProcessor::compute_percentile_boundaries(const cv::Mat& image,
412 const cv::Mat& mask,
413 double lower_percentile,
414 double upper_percentile) {
415 // Collect all pixel values within masked region
416 std::vector<double> values;
417
418 // Convert to double if needed
419 cv::Mat double_img;
420 if (image.type() == CV_64F) {
421 double_img = image;
422 } else {
423 image.convertTo(double_img, CV_64F);
424 }
425
426 for (int y = 0; y < double_img.rows; ++y) {
427 for (int x = 0; x < double_img.cols; ++x) {
428 if (mask.empty() || mask.at<uint8_t>(y, x) > 0) {
429 values.push_back(double_img.at<double>(y, x));
430 }
431 }
432 }
433
434 if (values.empty()) {
435 NLOG_WARN << "Warning: No pixels in mask for percentile computation";
436 return {0.0, 255.0};
437 }
438
439 // Sort values
440 std::sort(values.begin(), values.end());
441
442 // Compute percentile indices
443 size_t lower_idx = static_cast<size_t>(values.size() * lower_percentile / 100.0);
444 size_t upper_idx = static_cast<size_t>(values.size() * upper_percentile / 100.0);
445
446 lower_idx = std::min(lower_idx, values.size() - 1);
447 upper_idx = std::min(upper_idx, values.size() - 1);
448
449 return {values[lower_idx], values[upper_idx]};
450}
451
452cv::Mat ImageProcessor::normalize_and_clamp(const cv::Mat& image,
453 const std::pair<double, double>& boundaries) {
454 cv::Mat double_img;
455 if (image.type() == CV_64F) {
456 double_img = image;
457 } else {
458 image.convertTo(double_img, CV_64F);
459 }
460
461 cv::Mat normalized;
462 double range = boundaries.second - boundaries.first;
463 if (range > 0) {
464 normalized = (double_img - boundaries.first) / range;
465 // Saturate (clamp) to [0, 1]
466 cv::threshold(normalized, normalized, 1.0, 1.0, cv::THRESH_TRUNC);
467 cv::threshold(normalized, normalized, 0.0, 0.0, cv::THRESH_TOZERO);
468 } else {
469 normalized = cv::Mat::zeros(image.size(), CV_64F);
470 }
471
472 // Convert to uint8 [0, 255]
473 cv::Mat output;
474 normalized.convertTo(output, CV_8UC1, 255.0);
475
476 return output;
477}
478
480 cv::Mat mat(static_cast<int>(arr.height()), static_cast<int>(arr.width()), CV_8UC1);
481
482 for (int r = 0; r < mat.rows; ++r) {
483 for (int c = 0; c < mat.cols; ++c) {
484 // Array2D stores values in 0-1 range, convert to 0-255
485 double val = arr(r, c) * 255.0;
486 val = std::max(0.0, std::min(255.0, val));
487 mat.at<uint8_t>(r, c) = static_cast<uint8_t>(val);
488 }
489 }
490
491 return mat;
492}
493
495 cv::Mat gray;
496 if (mat.channels() == 3) {
497 cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY);
498 } else {
499 gray = mat;
500 }
501
502 // Ensure 8-bit format
503 if (gray.type() != CV_8UC1) {
504 cv::Mat temp;
505 gray.convertTo(temp, CV_8UC1);
506 gray = temp;
507 }
508
509 Array2D<double> arr(gray.rows, gray.cols);
510 double conversion = 1.0 / 255.0;
511
512 for (int r = 0; r < gray.rows; ++r) {
513 for (int c = 0; c < gray.cols; ++c) {
514 arr(r, c) = gray.at<uint8_t>(r, c) * conversion;
515 }
516 }
517
518 return arr;
519}
520
521// ============================================================================
522// VideoImporter Implementation
523// ============================================================================
524
525std::optional<VideoImporter::VideoInfo> VideoImporter::get_video_info(const std::string& video_path) {
526 cv::VideoCapture cap(video_path);
527 if (!cap.isOpened()) {
528 return std::nullopt;
529 }
530
531 VideoInfo info;
532 info.total_frames = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
533 info.width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
534 info.height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
535 info.fps = cap.get(cv::CAP_PROP_FPS);
536
537 int fourcc = static_cast<int>(cap.get(cv::CAP_PROP_FOURCC));
538 info.codec = std::string(1, fourcc & 255) +
539 std::string(1, (fourcc >> 8) & 255) +
540 std::string(1, (fourcc >> 16) & 255) +
541 std::string(1, (fourcc >> 24) & 255);
542
543 cap.release();
544 return info;
545}
546
547cv::Mat VideoImporter::extract_grayscale_frame(const cv::Mat& frame, bool use_red_channel) {
548 cv::Mat gray;
549
550 if (frame.channels() == 1) {
551 gray = frame.clone();
552 } else if (use_red_channel) {
553 // OpenCV reads as BGR, so R is at index 2
554 std::vector<cv::Mat> channels;
555 cv::split(frame, channels);
556 gray = channels[2]; // R channel (BGR -> index 2)
557 } else {
558 cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
559 }
560
561 return gray;
562}
563
564std::vector<Image2D> VideoImporter::import_video(
565 const std::string& video_path,
566 const VideoImportParams& params,
567 const FilterConfig& filter_config,
568 const std::string& name_prefix) {
569
570 std::vector<Image2D> images;
571
572 cv::VideoCapture cap(video_path);
573 if (!cap.isOpened()) {
574 NLOG_ERROR << "Failed to open video file: " << video_path;
575 return images;
576 }
577
578 int total_frames = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
579 int frame_start = std::max(1, params.frame_start);
580 int frame_end = (params.frame_end < 0) ? total_frames : std::min(params.frame_end, total_frames);
581 int frame_jump = std::max(1, params.frame_jump);
582
583 NLOG_INFO << "Importing video: " << video_path;
584 NLOG_INFO << " Total frames: " << total_frames;
585 NLOG_INFO << " Import range: " << frame_start << " to " << frame_end << " (step " << frame_jump << ")";
586
587 int imported_count = 0;
588 for (int f = frame_start; f <= frame_end; f += frame_jump) {
589 cap.set(cv::CAP_PROP_POS_FRAMES, f - 1); // 0-indexed
590
591 cv::Mat frame;
592 if (!cap.read(frame)) {
593 NLOG_WARN << " Warning: Failed to read frame " << f;
594 break;
595 }
596
597 // Extract grayscale
598 cv::Mat gray = extract_grayscale_frame(frame, params.use_red_channel);
599
600 // Apply filters if configured
601 if (!filter_config.empty()) {
602 gray = ImageProcessor::apply_filters(gray, filter_config);
603 }
604
605 // Create frame name
606 std::ostringstream name;
607 name << name_prefix << "_" << std::setw(6) << std::setfill('0') << f;
608
609 images.emplace_back(std::move(gray), name.str());
610 imported_count++;
611 }
612
613 cap.release();
614 NLOG_INFO << " Imported " << imported_count << " frames";
615
616 return images;
617}
618
620 const std::string& video_path,
621 const std::string& output_dir,
622 const VideoImportParams& params,
623 const FilterConfig& filter_config,
624 const std::string& name_prefix) {
625
626 std::vector<Image2D> images;
627
628 // Create output directory if it doesn't exist
629 std::filesystem::create_directories(output_dir);
630
631 cv::VideoCapture cap(video_path);
632 if (!cap.isOpened()) {
633 NLOG_ERROR << "Failed to open video file: " << video_path;
634 return images;
635 }
636
637 int total_frames = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
638 int frame_start = std::max(1, params.frame_start);
639 int frame_end = (params.frame_end < 0) ? total_frames : std::min(params.frame_end, total_frames);
640 int frame_jump = std::max(1, params.frame_jump);
641
642 NLOG_INFO << "Importing video to files: " << video_path;
643 NLOG_INFO << " Output directory: " << output_dir;
644 NLOG_INFO << " Total frames: " << total_frames;
645 NLOG_INFO << " Import range: " << frame_start << " to " << frame_end << " (step " << frame_jump << ")";
646
647 int imported_count = 0;
648 for (int f = frame_start; f <= frame_end; f += frame_jump) {
649 cap.set(cv::CAP_PROP_POS_FRAMES, f - 1); // 0-indexed
650
651 cv::Mat frame;
652 if (!cap.read(frame)) {
653 NLOG_WARN << " Warning: Failed to read frame " << f;
654 break;
655 }
656
657 // Extract grayscale
658 cv::Mat gray = extract_grayscale_frame(frame, params.use_red_channel);
659
660 // Apply filters if configured
661 if (!filter_config.empty()) {
662 gray = ImageProcessor::apply_filters(gray, filter_config);
663 }
664
665 // Create file path
666 std::ostringstream filepath;
667 filepath << output_dir << "/" << name_prefix << "_" << std::setw(6) << std::setfill('0') << f << ".png";
668
669 // Save to file
670 cv::imwrite(filepath.str(), gray);
671
672 // Create Image2D pointing to the saved file
673 images.emplace_back(filepath.str());
674 imported_count++;
675 }
676
677 cap.release();
678 NLOG_INFO << " Imported and saved " << imported_count << " frames";
679
680 return images;
681}
682
683}
difference_type width() const
Definition Array2D.h:410
difference_type height() const
Definition Array2D.h:409
bool save_to_file(const std::string &filename) const
Definition Image2D.cpp:189
static Image2D load(std::ifstream &)
Definition Image2D.cpp:25
static Image2D from_mat(const cv::Mat &mat, const std::string &name="in_memory", const FilterConfig &filter_config=FilterConfig())
Definition Image2D.cpp:45
cv::Mat get_mat() const
Definition Image2D.cpp:168
static Image2D from_file_filtered(const std::string &filename, const FilterConfig &filter_config)
Definition Image2D.cpp:64
Image2D()=default
std::ptrdiff_t difference_type
Definition Image2D.h:61
Array2D< double > get_gs() const
Definition Image2D.cpp:134
static cv::Mat normalize_and_clamp(const cv::Mat &image, const std::pair< double, double > &boundaries)
Definition Image2D.cpp:452
static cv::Mat filter_like_ben_single(const cv::Mat &input, const cv::Mat &mask, const std::vector< int > &params={5, 50}, const std::pair< double, double > *gs_boundaries=nullptr)
Definition Image2D.cpp:381
static cv::Mat apply_bandpass_filter(const cv::Mat &input, const std::vector< int > &params={5, 50})
Definition Image2D.cpp:239
static std::pair< std::vector< cv::Mat >, std::pair< double, double > > filter_like_ben(const std::vector< cv::Mat > &input, const cv::Mat &mask, const std::vector< int > &params={5, 50}, const std::pair< double, double > *gs_boundaries=nullptr)
Definition Image2D.cpp:351
static cv::Mat saturate(const cv::Mat &input, int level=200)
Definition Image2D.cpp:198
static Array2D< double > mat_to_array2d(const cv::Mat &mat)
Definition Image2D.cpp:494
static std::pair< double, double > compute_grayscale_boundaries(const cv::Mat &image, const cv::Mat &mask)
Definition Image2D.cpp:390
static std::pair< double, double > compute_percentile_boundaries(const cv::Mat &image, const cv::Mat &mask, double lower_pct=5.0, double upper_pct=95.0)
Definition Image2D.cpp:411
static cv::Mat apply_filters(const cv::Mat &input, const FilterConfig &config)
Definition Image2D.cpp:318
static cv::Mat array2d_to_mat(const Array2D< double > &arr)
Definition Image2D.cpp:479
static std::optional< VideoInfo > get_video_info(const std::string &video_path)
Definition Image2D.cpp:525
static std::vector< Image2D > import_video_to_files(const std::string &video_path, const std::string &output_dir, const VideoImportParams &params=VideoImportParams(), const FilterConfig &filter_config=FilterConfig(), const std::string &name_prefix="frame")
Definition Image2D.cpp:619
static std::vector< Image2D > import_video(const std::string &video_path, const VideoImportParams &params=VideoImportParams(), const FilterConfig &filter_config=FilterConfig(), const std::string &name_prefix="frame")
Definition Image2D.cpp:564
Lightweight, dependency-free logging facility for CppNCorr.
#define NLOG_INFO
Definition log.h:162
#define NLOG_ERROR
Definition log.h:164
#define NLOG_WARN
Definition log.h:163
bool isequal(const Data2D &data1, const Data2D &data2)
Definition Data2D.cpp:72
void imshow(const Data2D &data, Data2D::difference_type delay)
Definition Data2D.cpp:58
void save(const Data2D &data, std::ofstream &os)
Definition Data2D.cpp:78
std::ostream & operator<<(std::ostream &os, const Data2D &data)
Definition Data2D.cpp:50
bool empty() const
Definition Image2D.h:43
std::vector< int > bandpass_params
Definition Image2D.h:33
bool has_saturation() const
Definition Image2D.h:41
bool has_bandpass() const
Definition Image2D.h:42