31 is.read(
reinterpret_cast<char*
>(&length), std::streamsize(
sizeof(
difference_type)));
34 img.filename_ptr = std::make_shared<std::string>(length,
' ');
37 is.read(
const_cast<char*
>(img.filename_ptr->c_str()), std::streamsize(length));
47 if (!filter_config.
empty()) {
50 processed = mat.clone();
55 if (processed.channels() == 3) {
56 cv::cvtColor(processed, gray, cv::COLOR_BGR2GRAY);
61 return Image2D(std::move(gray), name);
65 cv::Mat img = cv::imread(filename, cv::IMREAD_GRAYSCALE);
67 throw std::invalid_argument(
"Image file: " + filename +
" cannot be found or read.");
70 if (!filter_config.
empty()) {
75 std::filesystem::path p(filename);
76 return Image2D(std::move(img), p.filename().string());
81 os << *img.filename_ptr;
83 os <<
" [in-memory cv::Mat]";
85 os <<
" [in-memory Array2D]";
96 if (img1.storage_mode != img2.storage_mode) {
101 return *img1.filename_ptr == *img2.filename_ptr;
108 if (gs1.height() != gs2.height() || gs1.width() != gs2.width()) {
114 if (std::abs(gs1(r, c) - gs2(r, c)) > 1e-10) {
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()));
135 switch (storage_mode) {
149 cv::Mat cv_img = cv::imread((*filename_ptr), cv::IMREAD_GRAYSCALE);
151 throw std::invalid_argument(
"Image file : " + *filename_ptr +
" cannot be found or read.");
157 double conversion = 1.0 / 255.0;
160 A(p2, p1) = cv_img.data[p1 + p2 * cv_img.cols] * conversion;
169 switch (storage_mode) {
171 return mat_ptr->clone();
180 cv::Mat img = cv::imread(*filename_ptr, cv::IMREAD_GRAYSCALE);
182 throw std::invalid_argument(
"Image file: " + *filename_ptr +
" cannot be found or read.");
191 return cv::imwrite(filename, mat);
201 if (input.channels() == 3) {
202 cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY);
204 gray = input.clone();
208 if (gray.type() != CV_8UC1) {
209 gray.convertTo(gray, CV_8UC1);
212 cv::Mat output = gray.clone();
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);
220 output.at<uint8_t>(y, x) =
static_cast<uint8_t
>(level);
229 std::vector<cv::Mat> output;
230 output.reserve(input.size());
232 for (
const auto& img : input) {
233 output.push_back(
saturate(img, level));
240 if (params.size() < 2) {
241 throw std::invalid_argument(
"Bandpass filter requires two parameters: {r1, r2}");
246 input.convertTo(float_img, CV_64F);
248 double r1 =
static_cast<double>(params[0]);
249 double r2 =
static_cast<double>(params[1]);
251 int rows = float_img.rows;
252 int cols = float_img.cols;
255 cv::Mat filter_mask(rows, cols, CV_64F);
259 for (
int y = 0; y < rows; ++y) {
260 for (
int x = 0; x < cols; ++x) {
263 double r_sq = dx * dx + dy * dy;
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;
273 cv::Mat planes[] = {float_img, cv::Mat::zeros(float_img.size(), CV_64F)};
275 cv::merge(planes, 2, complex_img);
276 cv::dft(complex_img, complex_img);
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));
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)));
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);
299 cv::Mat filtered_img;
300 cv::idft(complex_img, filtered_img, cv::DFT_SCALE);
301 cv::split(filtered_img, planes);
307 const std::vector<int>& params) {
308 std::vector<cv::Mat> output;
309 output.reserve(input.size());
311 for (
const auto& img : input) {
319 cv::Mat result = input.clone();
330 if (result.type() != CV_8UC1) {
331 cv::normalize(result, result, 0, 255, cv::NORM_MINMAX);
332 result.convertTo(result, CV_8UC1);
340 std::vector<cv::Mat> output;
341 output.reserve(input.size());
343 for (
const auto& img : input) {
350std::pair<std::vector<cv::Mat>, std::pair<double, double>>
353 const std::vector<int>& params,
354 const std::pair<double, double>* gs_boundaries) {
356 std::vector<cv::Mat> filtered_images;
357 std::pair<double, double> boundaries;
360 for (
const auto& img : input) {
362 filtered_images.push_back(filtered);
366 if (gs_boundaries ==
nullptr) {
368 NLOG_INFO <<
" Computed filter boundaries: [" << boundaries.first <<
", " << boundaries.second <<
"]";
370 boundaries = *gs_boundaries;
374 for (
size_t i = 0; i < filtered_images.size(); ++i) {
378 return {filtered_images, boundaries};
383 const std::vector<int>& params,
384 const std::pair<double, double>* gs_boundaries) {
385 std::vector<cv::Mat> input_vec = {input};
387 return result.first[0];
391 const cv::Mat& mask) {
393 image.convertTo(float_img, CV_32F);
395 double min_val = std::numeric_limits<double>::max();
396 double max_val = std::numeric_limits<double>::lowest();
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);
408 return {min_val, max_val};
413 double lower_percentile,
414 double upper_percentile) {
416 std::vector<double> values;
420 if (image.type() == CV_64F) {
423 image.convertTo(double_img, CV_64F);
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));
434 if (values.empty()) {
435 NLOG_WARN <<
"Warning: No pixels in mask for percentile computation";
440 std::sort(values.begin(), values.end());
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);
446 lower_idx = std::min(lower_idx, values.size() - 1);
447 upper_idx = std::min(upper_idx, values.size() - 1);
449 return {values[lower_idx], values[upper_idx]};
453 const std::pair<double, double>& boundaries) {
455 if (image.type() == CV_64F) {
458 image.convertTo(double_img, CV_64F);
462 double range = boundaries.second - boundaries.first;
464 normalized = (double_img - boundaries.first) / range;
466 cv::threshold(normalized, normalized, 1.0, 1.0, cv::THRESH_TRUNC);
467 cv::threshold(normalized, normalized, 0.0, 0.0, cv::THRESH_TOZERO);
469 normalized = cv::Mat::zeros(image.size(), CV_64F);
474 normalized.convertTo(output, CV_8UC1, 255.0);
480 cv::Mat mat(
static_cast<int>(arr.
height()),
static_cast<int>(arr.
width()), CV_8UC1);
482 for (
int r = 0; r < mat.rows; ++r) {
483 for (
int c = 0; c < mat.cols; ++c) {
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);
496 if (mat.channels() == 3) {
497 cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY);
503 if (gray.type() != CV_8UC1) {
505 gray.convertTo(temp, CV_8UC1);
510 double conversion = 1.0 / 255.0;
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;
526 cv::VideoCapture cap(video_path);
527 if (!cap.isOpened()) {
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);
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);
547cv::Mat VideoImporter::extract_grayscale_frame(
const cv::Mat& frame,
bool use_red_channel) {
550 if (frame.channels() == 1) {
551 gray = frame.clone();
552 }
else if (use_red_channel) {
554 std::vector<cv::Mat> channels;
555 cv::split(frame, channels);
558 cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
565 const std::string& video_path,
568 const std::string& name_prefix) {
570 std::vector<Image2D> images;
572 cv::VideoCapture cap(video_path);
573 if (!cap.isOpened()) {
574 NLOG_ERROR <<
"Failed to open video file: " << video_path;
578 int total_frames =
static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
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);
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 <<
")";
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);
592 if (!cap.read(frame)) {
593 NLOG_WARN <<
" Warning: Failed to read frame " << f;
601 if (!filter_config.
empty()) {
606 std::ostringstream name;
607 name << name_prefix <<
"_" << std::setw(6) << std::setfill(
'0') << f;
609 images.emplace_back(std::move(gray), name.str());
614 NLOG_INFO <<
" Imported " << imported_count <<
" frames";
620 const std::string& video_path,
621 const std::string& output_dir,
624 const std::string& name_prefix) {
626 std::vector<Image2D> images;
629 std::filesystem::create_directories(output_dir);
631 cv::VideoCapture cap(video_path);
632 if (!cap.isOpened()) {
633 NLOG_ERROR <<
"Failed to open video file: " << video_path;
637 int total_frames =
static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
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);
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 <<
")";
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);
652 if (!cap.read(frame)) {
653 NLOG_WARN <<
" Warning: Failed to read frame " << f;
661 if (!filter_config.
empty()) {
666 std::ostringstream filepath;
667 filepath << output_dir <<
"/" << name_prefix <<
"_" << std::setw(6) << std::setfill(
'0') << f <<
".png";
670 cv::imwrite(filepath.str(), gray);
673 images.emplace_back(filepath.str());
678 NLOG_INFO <<
" Imported and saved " << imported_count <<
" frames";
difference_type width() const
difference_type height() const
bool save_to_file(const std::string &filename) const
static Image2D load(std::ifstream &)
static Image2D from_mat(const cv::Mat &mat, const std::string &name="in_memory", const FilterConfig &filter_config=FilterConfig())
static Image2D from_file_filtered(const std::string &filename, const FilterConfig &filter_config)
std::ptrdiff_t difference_type
Array2D< double > get_gs() const
static cv::Mat normalize_and_clamp(const cv::Mat &image, const std::pair< double, double > &boundaries)
static cv::Mat filter_like_ben_single(const cv::Mat &input, const cv::Mat &mask, const std::vector< int > ¶ms={5, 50}, const std::pair< double, double > *gs_boundaries=nullptr)
static cv::Mat apply_bandpass_filter(const cv::Mat &input, const std::vector< int > ¶ms={5, 50})
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 > ¶ms={5, 50}, const std::pair< double, double > *gs_boundaries=nullptr)
static cv::Mat saturate(const cv::Mat &input, int level=200)
static Array2D< double > mat_to_array2d(const cv::Mat &mat)
static std::pair< double, double > compute_grayscale_boundaries(const cv::Mat &image, const cv::Mat &mask)
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)
static cv::Mat apply_filters(const cv::Mat &input, const FilterConfig &config)
static cv::Mat array2d_to_mat(const Array2D< double > &arr)
static std::optional< VideoInfo > get_video_info(const std::string &video_path)
static std::vector< Image2D > import_video_to_files(const std::string &video_path, const std::string &output_dir, const VideoImportParams ¶ms=VideoImportParams(), const FilterConfig &filter_config=FilterConfig(), const std::string &name_prefix="frame")
static std::vector< Image2D > import_video(const std::string &video_path, const VideoImportParams ¶ms=VideoImportParams(), const FilterConfig &filter_config=FilterConfig(), const std::string &name_prefix="frame")
Lightweight, dependency-free logging facility for CppNCorr.
bool isequal(const Data2D &data1, const Data2D &data2)
void imshow(const Data2D &data, Data2D::difference_type delay)
void save(const Data2D &data, std::ofstream &os)
std::ostream & operator<<(std::ostream &os, const Data2D &data)
std::vector< int > bandpass_params
bool has_saturation() const
bool has_bandpass() const