riCOM_cpp
This repository contains the C++ implementation of the riCOM (Real Time Centre Of Mass) algorithm for 4D Scanning electron microscopy.
Loading...
Searching...
No Matches
ImGuiImageWindow.cpp
Go to the documentation of this file.
1/* Copyright (C) 2021 Thomas Friedrich, Chu-Ping Yu,
2 * University of Antwerp - All Rights Reserved.
3 * You may use, distribute and modify
4 * this code under the terms of the GPL3 license.
5 * You should have received a copy of the GPL3 license with
6 * this file. If not, please visit:
7 * https://www.gnu.org/licenses/gpl-3.0.en.html
8 *
9 * Authors:
10 * Thomas Friedrich <thomas.friedrich@uantwerpen.be>
11 * Chu-Ping Yu <chu-ping.yu@uantwerpen.be>
12 */
13
14#ifdef _MSC_VER
15#define _CRT_SECURE_NO_DEPRECATE
16#define _CRT_SECURE_NO_WARNINGS
17#pragma warning(disable : 4067)
18#pragma warning(disable : 4333)
19#pragma warning(disable : 4312)
20#endif
21
22#if defined(__GNUC__)
23#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
24#pragma GCC diagnostic ignored "-Wformat-security"
25#endif
26
27#if defined(__clang__)
28#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
29#pragma GCC diagnostic ignored "-Wformat-security"
30#endif
31
32#include "ImGuiImageWindow.h"
33
34template <typename T>
35inline T pw(T val, T power)
36{
37 return copysign(1.0, val) * pow(abs(val), power);
38}
39
40namespace cmap = tinycolormap;
41
43{
44 return static_cast<GIM_Flags>(
45 static_cast<std::underlying_type_t<GIM_Flags>>(lhs) |
46 static_cast<std::underlying_type_t<GIM_Flags>>(rhs));
47}
48
50{
51 return static_cast<GIM_Flags>(
52 static_cast<std::underlying_type_t<GIM_Flags>>(lhs) &
53 static_cast<std::underlying_type_t<GIM_Flags>>(rhs));
54}
55
56template <typename T>
58{
59 return static_cast<bool>(flags & flag);
60}
61
62// Redraws the entire image
63template <typename T>
65{
66 if (b_data_set)
67 {
68 if (!auto_render)
69 {
70 set_min_max();
71 }
72 int binned_nx = (nx + bin_factor - 1) / bin_factor;
73 int binned_ny = (ny + bin_factor - 1) / bin_factor;
74 for (int by = 0; by < binned_ny; by++)
75 {
76 for (int bx = 0; bx < binned_nx; bx++)
77 {
78 set_pixel_binned(bx, by);
79 }
80 }
81 }
82}
83
84// Redraws the entire ricom image from line y0 to line ye
85template <typename T>
86void ImGuiImageWindow<T>::render_image(int last_idr)
87{
88 int last_yt = (last_idr / nx);
89 if (b_data_set)
90 {
91 set_min_max(last_idr);
92 int binned_nx = (nx + bin_factor - 1) / bin_factor;
93 int y_start = (std::max)(0, this->last_y - render_update_offset);
94 int y_end = (std::min)(last_yt + render_update_offset, ny);
95 int by_start = y_start / bin_factor;
96 int by_end = (y_end + bin_factor - 1) / bin_factor;
97 for (int by = by_start; by < by_end; by++)
98 {
99 for (int bx = 0; bx < binned_nx; bx++)
100 {
101 set_pixel_binned(bx, by);
102 }
103 }
104 }
105 this->last_y = last_yt;
106 this->last_idr = last_idr;
107}
108
109template <>
110void ImGuiImageWindow<float>::set_pixel(int idx, int idy)
111{
112 // determine location index of value in memory
113 int idr = idy * nx + idx;
114 float val = pw((data->at(idr) - data_min) / data_range, power);
115
116 // Update pixel at location
117 SDL_Utils::draw_pixel(sdl_srf, idx, idy, val, data_cmap);
118}
119
120template <>
121void ImGuiImageWindow<std::complex<float>>::set_pixel(int idx, int idy)
122{
123 // determine location index of value in memory
124 int idr = idy * nx + idx;
125
126 // Get magnitude and angle from complex
127 float mag = (abs(data->at(idr)) - data_min) / data_range;
128 float ang = arg(data->at(idr));
129 ang = (ang / M_PI + 1) / 2;
130 mag = pw(mag, power);
131
132 // Update pixel at location
133 SDL_Utils::draw_pixel(sdl_srf, idx, idy, ang, mag, data_cmap);
134}
135
136// Binned pixel setter for float data - averages bin_factor x bin_factor block
137template <>
139{
140 float sum = 0.0f;
141 int count = 0;
142 for (int dy = 0; dy < bin_factor; dy++)
143 {
144 for (int dx = 0; dx < bin_factor; dx++)
145 {
146 int x = bx * bin_factor + dx;
147 int y = by * bin_factor + dy;
148 if (x < nx && y < ny)
149 {
150 sum += data->at(y * nx + x);
151 count++;
152 }
153 }
154 }
155 float avg = (count > 0) ? sum / count : 0.0f;
156 float val = pw((avg - data_min) / data_range, power);
157 SDL_Utils::draw_pixel(sdl_srf, bx, by, val, data_cmap);
158}
159
160// Binned pixel setter for complex data - averages magnitude and angle separately
161template <>
162void ImGuiImageWindow<std::complex<float>>::set_pixel_binned(int bx, int by)
163{
164 float mag_sum = 0.0f;
165 float ang_sin_sum = 0.0f;
166 float ang_cos_sum = 0.0f;
167 int count = 0;
168 for (int dy = 0; dy < bin_factor; dy++)
169 {
170 for (int dx = 0; dx < bin_factor; dx++)
171 {
172 int x = bx * bin_factor + dx;
173 int y = by * bin_factor + dy;
174 if (x < nx && y < ny)
175 {
176 std::complex<float> val = data->at(y * nx + x);
177 mag_sum += abs(val);
178 float ang = arg(val);
179 ang_sin_sum += sin(ang);
180 ang_cos_sum += cos(ang);
181 count++;
182 }
183 }
184 }
185 float mag = (count > 0) ? (mag_sum / count - data_min) / data_range : 0.0f;
186 float ang = (count > 0) ? atan2(ang_sin_sum / count, ang_cos_sum / count) : 0.0f;
187 ang = (ang / M_PI + 1) / 2;
188 mag = pw(mag, power);
189 SDL_Utils::draw_pixel(sdl_srf, bx, by, ang, mag, data_cmap);
190}
191
192template <>
194{
195 return (*data)[idr];
196}
197
198template <>
199float ImGuiImageWindow<std::complex<float>>::get_val(int idr)
200{
201 return abs((*data)[idr]);
202}
203
204template <typename T>
205void ImGuiImageWindow<T>::set_min_max(int last_idr)
206{
207 for (int idr = this->last_idr; idr < last_idr; idr++)
208 {
209 float val = get_val(idr);
210 if (val < data_min)
211 {
212 data_min = val;
213 data_range = data_max - data_min;
214 b_trigger_update = true;
215 }
216 if (val > data_max)
217 {
218 data_max = val;
219 data_range = data_max - data_min;
220 b_trigger_update = true;
221 }
222 }
223}
224
225template <typename T>
227{
228 for (int idr = 0; idr < nxy; idr++)
229 {
230 float val = get_val(idr);
231 if (val < data_min)
232 {
233 data_min = val;
234 data_range = data_max - data_min;
235 b_trigger_update = true;
236 }
237 if (val > data_max)
238 {
239 data_max = val;
240 data_range = data_max - data_min;
241 b_trigger_update = true;
242 }
243 }
244}
245
246template <typename T>
247ImGuiImageWindow<T>::ImGuiImageWindow(const std::string &title, GLuint *tex_id, bool auto_render, int data_cmap, GIM_Flags flags, bool *visible)
248{
249
250 this->title = title;
251 this->flags = flags;
252 this->tex_id = tex_id;
253 this->pb_open = visible;
254 this->auto_render = auto_render;
255 this->data_cmap = data_cmap;
256 this->last_y = 0;
257 this->last_idr = 0;
258 this->last_img = 0;
259 this->zoom = 1.0f;
260 this->power = 1.0f;
261 this->ny = 1;
262 this->nx = 1;
263 this->nxy = 1;
264 this->render_update_offset = 0;
265 this->b_trigger_update = false;
266 this->bin_factor = 1;
267 this->bin_factor_idx = 0;
268 saveFileDialog = ImGui::FileBrowser(ImGuiFileBrowserFlags_EnterNewFilename | ImGuiFileBrowserFlags_CreateNewDir);
269 saveFileDialog.SetTitle("Save " + title + " image as .png");
270 saveDataDialog = ImGui::FileBrowser(ImGuiFileBrowserFlags_EnterNewFilename | ImGuiFileBrowserFlags_CreateNewDir);
271 saveDataDialog.SetTitle("Save " + title + "-data as numpy array (.npy)");
272
273 uv_min = ImVec2(0.0f, 0.0f); // Top-left
274 uv_max = ImVec2(1.0f, 1.0f); // Lower-right
275 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
276 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white
277 sdl_srf = SDL_CreateRGBSurface(0, this->nx, this->ny, 32, 0, 0, 0, 0);
278 if (sdl_srf == NULL)
279 {
280 std::cout << "Surface could not be created! SDL Error: " << SDL_GetError() << std::endl;
281 }
282 this->b_data_set = false;
283}
284
285template <typename T>
286void ImGuiImageWindow<T>::set_data(int width, int height, std::vector<T> *data)
287{
288 this->data = data;
289 set_nx_ny(width, height);
290 reset_limits();
291 if (!auto_render)
292 {
293 render_image();
294 };
295 b_data_set = true;
296}
297
298template <typename T>
300{
301 last_y = 0;
302 last_idr = 0;
303 last_img = 0;
304 data_min = FLT_MAX;
305 data_max = -FLT_MAX;
306 data_range = FLT_MAX;
307}
308
309template <typename T>
310void ImGuiImageWindow<T>::set_nx_ny(int width, int height)
311{
312 this->nx = width;
313 this->ny = height;
314 this->nxy = height * width;
315
316 data_fft.resize(nxy);
317 data_fft_f.resize(nxy);
318 data_val.resize(nxy);
319
320 reset_limits();
321 recreate_surface();
322}
323
324template <typename T>
326{
327 if (sdl_srf != NULL)
328 {
329 SDL_FreeSurface(sdl_srf);
330 }
331 int binned_nx = (nx + bin_factor - 1) / bin_factor;
332 int binned_ny = (ny + bin_factor - 1) / bin_factor;
333 sdl_srf = SDL_CreateRGBSurface(0, binned_nx, binned_ny, 32, 0, 0, 0, 0);
334 if (sdl_srf == NULL)
335 {
336 std::cout << "Surface could not be created! SDL Error: " << SDL_GetError() << std::endl;
337 }
338}
339
340template <typename T>
341void ImGuiImageWindow<T>::render_window(bool b_redraw, int last_y, int render_update_offset, bool b_trigger_update)
342{
343 this->render_update_offset = render_update_offset;
344 render_window(b_redraw, last_y, b_trigger_update);
345}
346
347template <typename T>
349{
350 data_min = FLT_MAX;
351 data_max = -FLT_MAX;
352 data_range = FLT_MAX;
353}
354
355template <>
357{
358 FFT2D fft2d(ny, nx);
359 FFT2D::r2c(*data, data_val);
360 fft2d.fft(data_val, data_fft);
361 FFT2D::c2abs(data_fft, data_fft_f);
362}
363
364template <>
365void ImGuiImageWindow<std::complex<float>>::compute_fft()
366{
367 FFT2D fft2d(ny, nx);
368 fft2d.fft(*data, data_fft);
369 FFT2D::c2abs(data_fft, data_fft_f);
370}
371
372// Deal with situation when process is finished (redraw==false) but not fully rendered
373// Should also render at end of a full cycle but not when it's finished completely
374template <typename T>
376{
377 int n_im = (fr_count) / nxy;
378 if (last_img < n_im)
379 {
380 last_img = n_im;
381 fr_count = nxy;
382 this->last_y = 0;
383 return true;
384 }
385 else
386 {
387 fr_count -= (n_im * nxy);
388 return false;
389 }
390}
391
392template <>
393void ImGuiImageWindow<float>::value_tooltip(const int x, const int y, const float zoom)
394{
395 float val = 0.0f;
396 if (b_data_set)
397 val = data->at(y * nx + x);
398 ImGui::BeginTooltip();
399 ImGui::Text("XY: %i, %i", x, y);
400 ImGui::Text("Value: %.2f", val);
401 ImGui::Text("Zoom: %.2f", zoom);
402 ImGui::EndTooltip();
403}
404
405template <>
406void ImGuiImageWindow<std::complex<float>>::value_tooltip(const int x, const int y, const float zoom)
407{
408 std::complex<float> val = 0.0;
409 if (b_data_set)
410 val = data->at(y * nx + x);
411 ImGui::BeginTooltip();
412 ImGui::Text("XY: %i, %i", x, y);
413 ImGui::Text("Angle: %.2f", arg(val));
414 ImGui::Text("Magnitude: %.2f", abs(val));
415 ImGui::Text("Zoom: %.2f", zoom);
416 ImGui::EndTooltip();
417}
418
419// b_redraw is the standard timer based update, b_trigger_ext can trigger a full redraw of the image
420template <typename T>
421void ImGuiImageWindow<T>::render_window(bool b_redraw, int fr_count, bool b_trigger_ext)
422{
423 ImGui::SetNextWindowSize(ImVec2{256, 256}, ImGuiCond_FirstUseEver);
424 bool t_open = ImGui::Begin(title.c_str(), pb_open, ImGuiWindowFlags_NoScrollbar);
425 if (t_open)
426 {
427 bool fr_switch = detect_frame_switch(fr_count);
428 b_trigger_update = b_trigger_update || b_trigger_ext || fr_switch;
429 if (b_trigger_update)
430 {
431 render_image();
432 }
433
434 if (this->has(GIM_Flags::SaveImButton))
435 {
436 ImGui::SameLine();
437 if (ImGui::Button("Save Image as..."))
438 {
439 saveFileDialog.Open();
440 }
441 saveFileDialog.Display();
442 if (saveFileDialog.HasSelected())
443 {
444 std::string img_file = saveFileDialog.GetSelected().string();
445 saveFileDialog.ClearSelected();
446 save_image(&img_file, sdl_srf);
447 }
448 }
449
450 if (this->has(GIM_Flags::SaveDataButton))
451 {
452 ImGui::SameLine();
453 if (ImGui::Button("Save Data as..."))
454 {
455 saveDataDialog.Open();
456 }
457 saveDataDialog.Display();
458 if (saveDataDialog.HasSelected())
459 {
460 std::string com_file = saveDataDialog.GetSelected().string();
461 saveDataDialog.ClearSelected();
462 save_numpy(&com_file, nx, ny, data);
463 }
464 }
465
466 if (this->has(GIM_Flags::FftButton))
467 {
468 ImGui::SameLine();
469 bool fft_button_press = ImGui::Button("Compute FFT");
470 if (fft_button_press)
471 {
472 *fft_window->pb_open = true;
473 }
474 if (fft_button_press || (*fft_window->pb_open && b_trigger_update))
475 {
476 if (b_data_set)
477 {
478 compute_fft();
479 fft_window->set_data(nx, ny, &data_fft_f);
480 fft_window->b_trigger_update = true;
481 }
482 else
483 {
484 std::cout << "FFT was not performed, because no data was found in " + this->title + "!" << std::endl;
485 }
486 }
487 }
488
489 if (this->has(GIM_Flags::PowerSlider))
490 {
491 ImGui::SameLine();
492 ImGui::SetNextItemWidth(64);
493 if (ImGui::DragFloat("Power", &power, 0.05f, 0.05f, 2.0f, "%.2f"))
494 {
495 this->last_idr = 0;
496 this->last_y = 0;
497 render_image((fr_count == 0) ? nxy : fr_count);
498 b_trigger_update = true;
499 }
500 }
501
502 if (this->has(GIM_Flags::ColormapSelector))
503 {
504 ImGui::SameLine();
505 ImGui::SetNextItemWidth(100);
506 if (ImGui::Combo("Colormap", &data_cmap, cmaps, IM_ARRAYSIZE(cmaps)))
507 {
508 this->last_idr = 0;
509 this->last_y = 0;
510 render_image((fr_count == 0) ? nxy : fr_count);
511 b_trigger_update = true;
512 }
513 }
514
515 // Binning selector
516 ImGui::SameLine();
517 ImGui::SetNextItemWidth(60);
518 if (ImGui::Combo("Bin", &bin_factor_idx, bin_options, IM_ARRAYSIZE(bin_options)))
519 {
520 bin_factor = 1 << bin_factor_idx; // 0->1, 1->2, 2->4, 3->8
521 recreate_surface();
522 this->last_idr = 0;
523 this->last_y = 0;
524 render_image((fr_count == 0) ? nxy : fr_count);
525 b_trigger_update = true;
526 }
527
528 if (b_redraw && auto_render && fr_count > 0)
529 render_image(fr_count);
530
531 ImGui::SetNextWindowBgAlpha(0.0f);
532 ImGui::BeginChildFrame(ImGui::GetID("ImageFrame"), ImVec2(0.0f, 0.0f), ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
533 ImVec2 vAvail = ImGui::GetContentRegionAvail();
534 float scale = (std::min)(vAvail.x / sdl_srf->w, vAvail.y / sdl_srf->h);
535 float tex_h = sdl_srf->h * scale;
536 float tex_w = sdl_srf->w * scale;
537 float tex_h_z = tex_h * zoom;
538 float tex_w_z = tex_w * zoom;
539 if (b_redraw || b_trigger_update)
540 {
541 glBindTexture(GL_TEXTURE_2D, (*tex_id));
542 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, sdl_srf->w, sdl_srf->h, 0,
543 GL_BGRA, GL_UNSIGNED_BYTE, sdl_srf->pixels);
544 }
545 ImVec2 pos = ImGui::GetCursorScreenPos();
546 ImGui::Image((ImTextureID)(*tex_id), ImVec2(tex_w_z, tex_h_z), uv_min, uv_max, tint_col, border_col);
547 if (ImGui::IsItemHovered())
548 {
549
550 // Get Mouse Inputs
551 float dz = (float)io.MouseWheel;
552 ImVec2 xy = ImGui::GetMousePos();
553
554 // Compute relative cursor positions
555 float rel_x = xy.x - pos.x - ImGui::GetScrollX();
556 float rel_y = xy.y - pos.y - ImGui::GetScrollY();
557
558 // Adjust Scroll positions
559 // Capture Start position of scroll or drag/pan
560 if ((std::abs(dz) > 0.0f) || ImGui::IsMouseClicked(ImGuiMouseButton_Left))
561 {
562 start_x = rel_x;
563 start_y = rel_y;
564 start_xs = ImGui::GetScrollX();
565 start_ys = ImGui::GetScrollY();
566 }
567
568 // Panning
569 if (ImGui::IsMouseDown(ImGuiMouseButton_Left))
570 {
571 ImGui::SetScrollX(start_xs - (rel_x - start_x));
572 ImGui::SetScrollY(start_ys - (rel_y - start_y));
573 }
574
575 // Zooming
576 if (std::abs(dz) > 0.0f)
577 {
578
579 float zoom2 = zoom + dz * 0.1;
580 zoom2 = (std::max)(1.0f, zoom2);
581
582 float dx = ((xy.x - pos.x) / tex_w_z) * tex_w * (zoom2 - zoom);
583 float dy = ((xy.y - pos.y) / tex_h_z) * tex_h * (zoom2 - zoom);
584
585 ImGui::SetScrollX(start_xs + dx);
586 ImGui::SetScrollY(start_ys + dy);
587
588 zoom = zoom2;
589 }
590
591 // Value Popup
592 if (ImGui::IsMouseDown(ImGuiMouseButton_Right))
593 {
594 float scale_fct = scale * zoom;
595 int x = (int)std::floor((xy.x - pos.x) / scale_fct);
596 int y = (int)std::floor((xy.y - pos.y) / scale_fct);
597 value_tooltip(x, y, zoom);
598 }
599 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
600 {
601 zoom = 1.0f;
602 ImGui::SetScrollX(0.0f);
603 ImGui::SetScrollY(0.0f);
604 }
605 }
606 ImGui::EndChildFrame();
607 }
608 ImGui::End();
609 b_trigger_update = false;
610}
611
612template class ImGuiImageWindow<float>;
void save_numpy(std::string *path, int nx, int ny, std::vector< T > *data)
Definition GuiUtils.cpp:17
void save_image(std::string *path, SDL_Surface *sdl_srf)
Definition GuiUtils.cpp:30
T pw(T val, T power)
GIM_Flags operator|(GIM_Flags lhs, GIM_Flags rhs)
GIM_Flags operator&(GIM_Flags lhs, GIM_Flags rhs)
GIM_Flags
@ ColormapSelector
ImGuiImageWindow(const std::string &title, GLuint *tex_id, bool auto_render, int data_cmap, GIM_Flags flags=GIM_Flags::None, bool *visible=nullptr)
void set_nx_ny(int width, int height)
void set_data(int width, int height, std::vector< T > *data)
void render_window(bool b_redraw, int last_y, int render_update_offset, bool b_trigger_update)
void draw_pixel(SDL_Surface *surface, int x, int y, float val, int col_map)
Definition GuiUtils.cpp:97