As I was finishing my last post, I noticed a problem.

Introduction

I found that shrinking the 16-bit, 32-bit, 64-bit images into 8-bit data, that FLTK requires and most of our monitors are only capable of displaying, most of the information was lost and not displayed.

An example of the problem explained above. This is a 16-bit image of the Leo Triplet.

The optimal fix is the cut out the area of the bit space that contains most of the information. We can let the user choose this range from looking at a histogram of the image. Of course, as MaximDL probably does, we can also calculate the maximum value from the histogram and grab a part of the space around the max; I won't implement this.

When it comes to modifying the code from the last post, we need to keep the double array first produced, create a histogram and histogram window that lets the user select the range, and have the main window react accordingly.

The Code

After looking through the documentation and source code, I found that following the example of the Fl_File_Chooser class was my best option. On GitHub I added two commits, (commit 1, commit 2), that adapted the Fl_FITS_Image.H, Main_Window, and created a Fl_Levels_Chooser class which contains the window information.
The new window will use a Fl_Chart , which is the widget that contains charts in the FLTK library, Fl_Button , is a typical button, Fl_Return_Button , a typical button that captures the return key, and Fl_Hor_Nice_Slider, two horizontal sliders, one for the upper point of the range and another for the lower point.

// include/Fl_Levels_Chooser.H
#ifndef Fl_Levels_Chooser_H
#define Fl_Levels_Chooser_h

#include <FL/Fl_Window.H>
#include <FL/Fl_Chart.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Hor_Nice_Slider.H>

class FL_EXPORT Fl_Levels_Chooser {
private:
  Fl_Hor_Nice_Slider* highlevel;
  Fl_Hor_Nice_Slider* lowlevel;
  Fl_Return_Button* setButton;
  Fl_Button* cancelButton;
  Fl_Chart* histogramChart;
  Fl_Window* window;

We will need to store the low and high end points of the range in two doubles and an integer that will contain whether there was a failure, so, in the same class there are these lines of code,

  double _high, _low;
  int _status = 0;

The Fl_File_Chooser class uses a void pointer to integrate different class, this is unclear to me but I think it's best to follow the lead of the creator of the FLTK library.

  void *data_;

When creating a histogram, we will add up the number of points that display a certain value for all values. This is ok for 8-bit integers that have 255 different possible values but for 16-bit with $65,535$, 32-bit with $4,294,967,295$, and 64-bit with $9,223,372,036,854,775,807$ with different possible numbers and, We can't store that many values in memory. Fortunately, we can set a number of equally spaced partitions of these spaces where we can truncate these values into one number for each partition; I chose 255. All we have to do is find the closest number that is also a multiple of $\frac{\text{bit space size}}{255}$ then add or find that number in an std::map class and set it to 1 if it's added, if it is found then add one to the existing value. There is more details in the source code of the Fl_Levels_Chooser class. Meanwhile we add the following to the class

  double roundclose(double num, double multiple)

We need to create callback functions for the two sliders and two buttons. Add the following to the class

  static void OnHighLevelChanged(Fl_Widget* w, void* d);
  static void OnLowLevelChanged(Fl_Widget* w, void* d);
  
  inline void cb_setButton_i(Fl_Return_Button*, void*);
  static void cb_setButton(Fl_Return_Button*, void*);
  
  inline void cb_cancelButton_i(Fl_Button*, void*);
  static void cb_cancelButton(Fl_Button*, void*);

Our constructor will take in the image as a pointer to the double array, the width of the image, the height of the image, and whether the image is rgb or not.

public:
  Fl_Levels_Chooser(double* image, int width, int height, int d);
  ~Fl_Levels_Chooser();

Let's add functions that will let us access both doubles and the integer.

  int Status() { return _status; }
  
  double GetHighLevel() { return _high; }
  void SetHighLevel(double high) { _high = high; }
  
  double GetLowLevel() { return _low; }
  void SetLowLevel(double low) { _low = low; }

We need a function that we can later use to execute a static function added to it using the FLTK callback feature.

  void callback(void (*cb)(Fl_Levels_Chooser *, void *), void *d = 0);

A function to show the window widget and two function to show and get the _data pointer we have.

  void show();
  
  void * user_data() const;
  void user_data(void* d);

In the constructor code of the source code, I initialized all the widgets so that they would be in an optimal position. I also imported the image, parse it into the histogram, and displayed the histogram.
You can click on the link above to view the source code for the Fl_Levels_Chooser class.

I'll show some of the highlights from the code in the Fl_Levels_Chooser class. In the constructor to build the histogram we first built an std::map<double, uint64_t> object and add to it.

// max_ele     -> maximum element in image
// min_ele     -> minimum element in image
// nviz_points -> 255 - number of partitis/points in the histogram
// npixel      -> number of pixels in image
// /\/\    Some Code    /\/\
  // Size per point
  double ps = (max_ele - min_ele) / nviz_points;
  
  // Create histogram Data
  std::map<double, uint64_t> dic; // dic -> Dictionary in C#
  for (int i = 0; i < npixel; i++)
  {
    double cur = roundclose(image[i], ps);
    if (!dic.count(cur))
      dic.insert(std::pair<double, uint64_t>(cur, 1));
    else
      dic.at(cur)++;
  }

Then add each element of the map type to the chart using a for each iteration. We can take a look at the reference for more info.

  // Set Chart bounds
  histogramChart->bound(min_ele, max_ele);
  
  // Add data to histogramChart
  for (auto& item: dic)
  {
    histogramChart->add(item.second, 0, Fl_RED);
  }
  histogramChart->redraw();

The rest of the changes add features, like making the high slider always be higher then the low side slider, and adapt the code for this change by passing around pointers to the double array and callback functions.
FULL SOURCE CODE

Result

What we get after compiling the code is the added feature to change the levels of the image and turn the above image showing almost nothing into an image that clearly shows the Leo Triplets!

Part 1