riCOM_cpp
This repository contains the C++ implementation of the riCOM (Real Time Centre Of Mass) algorithm for 4D Scanning electron microscopy.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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 
34 template <typename T>
35 inline T pw(T val, T power)
36 {
37  return copysign(1.0, val) * pow(abs(val), power);
38 }
39 
40 namespace 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 
56 template <typename T>
58 {
59  return static_cast<bool>(flags & flag);
60 }
61 
62 // Redraws the entire image
63 template <typename T>
65 {
66  if (b_data_set)
67  {
68  if (!auto_render)
69  {
70  set_min_max();
71  }
72  for (int y = 0; y < ny; y++)
73  {
74  for (int x = 0; x < nx; x++)
75  {
76  set_pixel(x, y);
77  }
78  }
79  }
80 }
81 
82 // Redraws the entire ricom image from line y0 to line ye
83 template <typename T>
84 void ImGuiImageWindow<T>::render_image(int last_idr)
85 {
86  int last_yt = (last_idr / nx);
87  if (b_data_set)
88  {
89  set_min_max(last_idr);
90  for (int y = (std::max)(0, this->last_y - render_update_offset); y < (std::min)(last_yt + render_update_offset, ny); y++)
91  {
92  for (int x = 0; x < nx; x++)
93  {
94  set_pixel(x, y);
95  }
96  }
97  }
98  this->last_y = last_yt;
99  this->last_idr = last_idr;
100 }
101 
102 template <>
103 void ImGuiImageWindow<float>::set_pixel(int idx, int idy)
104 {
105  // determine location index of value in memory
106  int idr = idy * nx + idx;
107  float val = pw((data->at(idr) - data_min) / data_range, power);
108 
109  // Update pixel at location
110  SDL_Utils::draw_pixel(sdl_srf, idx, idy, val, data_cmap);
111 }
112 
113 template <>
114 void ImGuiImageWindow<std::complex<float>>::set_pixel(int idx, int idy)
115 {
116  // determine location index of value in memory
117  int idr = idy * nx + idx;
118 
119  // Get magnitude and angle from complex
120  float mag = (abs(data->at(idr)) - data_min) / data_range;
121  float ang = arg(data->at(idr));
122  ang = (ang / M_PI + 1) / 2;
123  mag = pw(mag, power);
124 
125  // Update pixel at location
126  SDL_Utils::draw_pixel(sdl_srf, idx, idy, ang, mag, data_cmap);
127 }
128 
129 template <>
131 {
132  return (*data)[idr];
133 }
134 
135 template <>
136 float ImGuiImageWindow<std::complex<float>>::get_val(int idr)
137 {
138  return abs((*data)[idr]);
139 }
140 
141 template <typename T>
142 void ImGuiImageWindow<T>::set_min_max(int last_idr)
143 {
144  for (int idr = this->last_idr; idr < last_idr; idr++)
145  {
146  float val = get_val(idr);
147  if (val < data_min)
148  {
149  data_min = val;
150  data_range = data_max - data_min;
151  b_trigger_update = true;
152  }
153  if (val > data_max)
154  {
155  data_max = val;
156  data_range = data_max - data_min;
157  b_trigger_update = true;
158  }
159  }
160 }
161 
162 template <typename T>
164 {
165  for (int idr = 0; idr < nxy; idr++)
166  {
167  float val = get_val(idr);
168  if (val < data_min)
169  {
170  data_min = val;
171  data_range = data_max - data_min;
172  b_trigger_update = true;
173  }
174  if (val > data_max)
175  {
176  data_max = val;
177  data_range = data_max - data_min;
178  b_trigger_update = true;
179  }
180  }
181 }
182 
183 template <typename T>
184 ImGuiImageWindow<T>::ImGuiImageWindow(const std::string &title, GLuint *tex_id, bool auto_render, int data_cmap, GIM_Flags flags, bool *visible)
185 {
186 
187  this->title = title;
188  this->flags = flags;
189  this->tex_id = tex_id;
190  this->pb_open = visible;
191  this->auto_render = auto_render;
192  this->data_cmap = data_cmap;
193  this->last_y = 0;
194  this->last_idr = 0;
195  this->last_img = 0;
196  this->zoom = 1.0f;
197  this->power = 1.0f;
198  this->ny = 1;
199  this->nx = 1;
200  this->nxy = 1;
201  this->render_update_offset = 0;
202  this->b_trigger_update = false;
203  saveFileDialog = ImGui::FileBrowser(ImGuiFileBrowserFlags_EnterNewFilename | ImGuiFileBrowserFlags_CreateNewDir);
204  saveFileDialog.SetTitle("Save " + title + " image as .png");
205  saveDataDialog = ImGui::FileBrowser(ImGuiFileBrowserFlags_EnterNewFilename | ImGuiFileBrowserFlags_CreateNewDir);
206  saveDataDialog.SetTitle("Save " + title + "-data as numpy array (.npy)");
207 
208  uv_min = ImVec2(0.0f, 0.0f); // Top-left
209  uv_max = ImVec2(1.0f, 1.0f); // Lower-right
210  tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
211  border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white
212  sdl_srf = SDL_CreateRGBSurface(0, this->nx, this->ny, 32, 0, 0, 0, 0);
213  if (sdl_srf == NULL)
214  {
215  std::cout << "Surface could not be created! SDL Error: " << SDL_GetError() << std::endl;
216  }
217  this->b_data_set = false;
218 }
219 
220 template <typename T>
221 void ImGuiImageWindow<T>::set_data(int width, int height, std::vector<T> *data)
222 {
223  this->data = data;
224  set_nx_ny(width, height);
225  reset_limits();
226  if (!auto_render)
227  {
228  render_image();
229  };
230  b_data_set = true;
231 }
232 
233 template <typename T>
235 {
236  last_y = 0;
237  last_idr = 0;
238  last_img = 0;
239  data_min = FLT_MAX;
240  data_max = -FLT_MAX;
241  data_range = FLT_MAX;
242 }
243 
244 template <typename T>
245 void ImGuiImageWindow<T>::set_nx_ny(int width, int height)
246 {
247  this->nx = width;
248  this->ny = height;
249  this->nxy = height * width;
250 
251  data_fft.resize(nxy);
252  data_fft_f.resize(nxy);
253  data_val.resize(nxy);
254 
255  reset_limits();
256 
257  sdl_srf = SDL_CreateRGBSurface(0, this->nx, this->ny, 32, 0, 0, 0, 0);
258  if (sdl_srf == NULL)
259  {
260  std::cout << "Surface could not be created! SDL Error: " << SDL_GetError() << std::endl;
261  }
262 }
263 
264 template <typename T>
265 void ImGuiImageWindow<T>::render_window(bool b_redraw, int last_y, int render_update_offset, bool b_trigger_update)
266 {
267  this->render_update_offset = render_update_offset;
268  render_window(b_redraw, last_y, b_trigger_update);
269 }
270 
271 template <typename T>
273 {
274  data_min = FLT_MAX;
275  data_max = -FLT_MAX;
276  data_range = FLT_MAX;
277 }
278 
279 template <>
281 {
282  FFT2D fft2d(ny, nx);
283  FFT2D::r2c(*data, data_val);
284  fft2d.fft(data_val, data_fft);
285  FFT2D::c2abs(data_fft, data_fft_f);
286 }
287 
288 template <>
289 void ImGuiImageWindow<std::complex<float>>::compute_fft()
290 {
291  FFT2D fft2d(ny, nx);
292  fft2d.fft(*data, data_fft);
293  FFT2D::c2abs(data_fft, data_fft_f);
294 }
295 
296 // Deal with situation when process is finished (redraw==false) but not fully rendered
297 // Should also render at end of a full cycle but not when it's finished completely
298 template <typename T>
300 {
301  int n_im = (fr_count) / nxy;
302  if (last_img < n_im)
303  {
304  last_img = n_im;
305  fr_count = nxy;
306  this->last_y = 0;
307  return true;
308  }
309  else
310  {
311  fr_count -= (n_im * nxy);
312  return false;
313  }
314 }
315 
316 template <>
317 void ImGuiImageWindow<float>::value_tooltip(const int x, const int y, const float zoom)
318 {
319  float val = 0.0f;
320  if (b_data_set)
321  val = data->at(y * nx + x);
322  ImGui::BeginTooltip();
323  ImGui::Text("XY: %i, %i", x, y);
324  ImGui::Text("Value: %.2f", val);
325  ImGui::Text("Zoom: %.2f", zoom);
326  ImGui::EndTooltip();
327 }
328 
329 template <>
330 void ImGuiImageWindow<std::complex<float>>::value_tooltip(const int x, const int y, const float zoom)
331 {
332  std::complex<float> val = 0.0;
333  if (b_data_set)
334  val = data->at(y * nx + x);
335  ImGui::BeginTooltip();
336  ImGui::Text("XY: %i, %i", x, y);
337  ImGui::Text("Angle: %.2f", arg(val));
338  ImGui::Text("Magnitude: %.2f", abs(val));
339  ImGui::Text("Zoom: %.2f", zoom);
340  ImGui::EndTooltip();
341 }
342 
343 // b_redraw is the standard timer based update, b_trigger_ext can trigger a full redraw of the image
344 template <typename T>
345 void ImGuiImageWindow<T>::render_window(bool b_redraw, int fr_count, bool b_trigger_ext)
346 {
347  ImGui::SetNextWindowSize(ImVec2{256, 256}, ImGuiCond_FirstUseEver);
348  bool t_open = ImGui::Begin(title.c_str(), pb_open, ImGuiWindowFlags_NoScrollbar);
349  if (t_open)
350  {
351  bool fr_switch = detect_frame_switch(fr_count);
352  b_trigger_update = b_trigger_update || b_trigger_ext || fr_switch;
353  if (b_trigger_update)
354  {
355  render_image();
356  }
357 
358  if (this->has(GIM_Flags::SaveImButton))
359  {
360  ImGui::SameLine();
361  if (ImGui::Button("Save Image as..."))
362  {
363  saveFileDialog.Open();
364  }
365  saveFileDialog.Display();
366  if (saveFileDialog.HasSelected())
367  {
368  std::string img_file = saveFileDialog.GetSelected().string();
369  saveFileDialog.ClearSelected();
370  save_image(&img_file, sdl_srf);
371  }
372  }
373 
374  if (this->has(GIM_Flags::SaveDataButton))
375  {
376  ImGui::SameLine();
377  if (ImGui::Button("Save Data as..."))
378  {
379  saveDataDialog.Open();
380  }
381  saveDataDialog.Display();
382  if (saveDataDialog.HasSelected())
383  {
384  std::string com_file = saveDataDialog.GetSelected().string();
385  saveDataDialog.ClearSelected();
386  save_numpy(&com_file, nx, ny, data);
387  }
388  }
389 
390  if (this->has(GIM_Flags::FftButton))
391  {
392  ImGui::SameLine();
393  bool fft_button_press = ImGui::Button("Compute FFT");
394  if (fft_button_press)
395  {
396  *fft_window->pb_open = true;
397  }
398  if (fft_button_press || (*fft_window->pb_open && b_trigger_update))
399  {
400  if (b_data_set)
401  {
402  compute_fft();
403  fft_window->set_data(nx, ny, &data_fft_f);
404  fft_window->b_trigger_update = true;
405  }
406  else
407  {
408  std::cout << "FFT was not performed, because no data was found in " + this->title + "!" << std::endl;
409  }
410  }
411  }
412 
413  if (this->has(GIM_Flags::PowerSlider))
414  {
415  ImGui::SameLine();
416  ImGui::SetNextItemWidth(64);
417  if (ImGui::DragFloat("Power", &power, 0.05f, 0.05f, 2.0f, "%.2f"))
418  {
419  this->last_idr = 0;
420  this->last_y = 0;
421  render_image((fr_count == 0) ? nxy : fr_count);
422  b_trigger_update = true;
423  }
424  }
425 
426  if (this->has(GIM_Flags::ColormapSelector))
427  {
428  ImGui::SameLine();
429  ImGui::SetNextItemWidth(-1);
430  if (ImGui::Combo("Colormap", &data_cmap, cmaps, IM_ARRAYSIZE(cmaps)))
431  {
432  this->last_idr = 0;
433  this->last_y = 0;
434  render_image((fr_count == 0) ? nxy : fr_count);
435  b_trigger_update = true;
436  }
437  }
438 
439  if (b_redraw && auto_render && fr_count > 0)
440  render_image(fr_count);
441 
442  ImGui::SetNextWindowBgAlpha(0.0f);
443  ImGui::BeginChildFrame(ImGui::GetID("ImageFrame"), ImVec2(0.0f, 0.0f), ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
444  ImVec2 vAvail = ImGui::GetContentRegionAvail();
445  float scale = (std::min)(vAvail.x / sdl_srf->w, vAvail.y / sdl_srf->h);
446  float tex_h = sdl_srf->h * scale;
447  float tex_w = sdl_srf->w * scale;
448  float tex_h_z = tex_h * zoom;
449  float tex_w_z = tex_w * zoom;
450  if (b_redraw || b_trigger_update)
451  {
452  glBindTexture(GL_TEXTURE_2D, (*tex_id));
453  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, sdl_srf->w, sdl_srf->h, 0,
454  GL_BGRA, GL_UNSIGNED_BYTE, sdl_srf->pixels);
455  }
456  ImVec2 pos = ImGui::GetCursorScreenPos();
457  ImGui::Image((ImTextureID)(*tex_id), ImVec2(tex_w_z, tex_h_z), uv_min, uv_max, tint_col, border_col);
458  if (ImGui::IsItemHovered())
459  {
460 
461  // Get Mouse Inputs
462  float dz = (float)io.MouseWheel;
463  ImVec2 xy = ImGui::GetMousePos();
464 
465  // Compute relative cursor positions
466  float rel_x = xy.x - pos.x - ImGui::GetScrollX();
467  float rel_y = xy.y - pos.y - ImGui::GetScrollY();
468 
469  // Adjust Scroll positions
470  // Capture Start position of scroll or drag/pan
471  if ((std::abs(dz) > 0.0f) || ImGui::IsMouseClicked(ImGuiMouseButton_Left))
472  {
473  start_x = rel_x;
474  start_y = rel_y;
475  start_xs = ImGui::GetScrollX();
476  start_ys = ImGui::GetScrollY();
477  }
478 
479  // Panning
480  if (ImGui::IsMouseDown(ImGuiMouseButton_Left))
481  {
482  ImGui::SetScrollX(start_xs - (rel_x - start_x));
483  ImGui::SetScrollY(start_ys - (rel_y - start_y));
484  }
485 
486  // Zooming
487  if (std::abs(dz) > 0.0f)
488  {
489 
490  float zoom2 = zoom + dz * 0.1;
491  zoom2 = (std::max)(1.0f, zoom2);
492 
493  float dx = ((xy.x - pos.x) / tex_w_z) * tex_w * (zoom2 - zoom);
494  float dy = ((xy.y - pos.y) / tex_h_z) * tex_h * (zoom2 - zoom);
495 
496  ImGui::SetScrollX(start_xs + dx);
497  ImGui::SetScrollY(start_ys + dy);
498 
499  zoom = zoom2;
500  }
501 
502  // Value Popup
503  if (ImGui::IsMouseDown(ImGuiMouseButton_Right))
504  {
505  float scale_fct = scale * zoom;
506  int x = (int)std::floor((xy.x - pos.x) / scale_fct);
507  int y = (int)std::floor((xy.y - pos.y) / scale_fct);
508  value_tooltip(x, y, zoom);
509  }
510  if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
511  {
512  zoom = 1.0f;
513  ImGui::SetScrollX(0.0f);
514  ImGui::SetScrollY(0.0f);
515  }
516  }
517  ImGui::EndChildFrame();
518  }
519  ImGui::End();
520  b_trigger_update = false;
521 }
522 
523 template 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