riCOM_cpp
This repository contains the C++ implementation of the riCOM (Real Time Centre Of Mass) algorithm for 4D Scanning electron microscopy.
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