From 3293a740172fefb86c77a7b3046303d8fe4eeb69 Mon Sep 17 00:00:00 2001
From: Ivan Usov <ivan.usov@psi.ch>
Date: Thu, 16 Feb 2023 17:55:26 +0100
Subject: [PATCH] Add logarithmic color mappers

For #51
---
 pyzebra/app/panel_hdf_param_study.py |  8 +--
 pyzebra/app/panel_hdf_viewer.py      | 87 ++++++++++++++++++----------
 pyzebra/app/panel_plot_data.py       | 40 ++++++++++---
 3 files changed, 95 insertions(+), 40 deletions(-)

diff --git a/pyzebra/app/panel_hdf_param_study.py b/pyzebra/app/panel_hdf_param_study.py
index 3dc7f68..94bb7b6 100644
--- a/pyzebra/app/panel_hdf_param_study.py
+++ b/pyzebra/app/panel_hdf_param_study.py
@@ -367,16 +367,16 @@ def create():
     )
     proj_auto_checkbox.on_click(proj_auto_checkbox_callback)
 
-    def proj_display_max_spinner_callback(_attr, _old_value, new_value):
-        color_mapper_proj.high = new_value
+    def proj_display_max_spinner_callback(_attr, _old, new):
+        color_mapper_proj.high = new
 
     proj_display_max_spinner = Spinner(
         value=1, disabled=bool(proj_auto_checkbox.active), mode="int", width=100
     )
     proj_display_max_spinner.on_change("value", proj_display_max_spinner_callback)
 
-    def proj_display_min_spinner_callback(_attr, _old_value, new_value):
-        color_mapper_proj.low = new_value
+    def proj_display_min_spinner_callback(_attr, _old, new):
+        color_mapper_proj.low = new
 
     proj_display_min_spinner = Spinner(
         value=0, disabled=bool(proj_auto_checkbox.active), mode="int", width=100
diff --git a/pyzebra/app/panel_hdf_viewer.py b/pyzebra/app/panel_hdf_viewer.py
index 53283a4..8b2b7d8 100644
--- a/pyzebra/app/panel_hdf_viewer.py
+++ b/pyzebra/app/panel_hdf_viewer.py
@@ -18,9 +18,11 @@ from bokeh.models import (
     HoverTool,
     LinearAxis,
     LinearColorMapper,
+    LogColorMapper,
     MultiSelect,
     NumberFormatter,
     Panel,
+    RadioGroup,
     Range1d,
     Select,
     Slider,
@@ -483,8 +485,9 @@ def create():
         )
     )
 
-    color_mapper = LinearColorMapper()
-    plot.image(source=image_source, color_mapper=color_mapper)
+    lin_color_mapper = LinearColorMapper(low=0, high=1)
+    log_color_mapper = LogColorMapper(low=0, high=1)
+    plot_image = plot.image(source=image_source, color_mapper=lin_color_mapper)
     plot.image(source=image_source, image="h", global_alpha=0)
     plot.image(source=image_source, image="k", global_alpha=0)
     plot.image(source=image_source, image="l", global_alpha=0)
@@ -573,7 +576,8 @@ def create():
     # shared frame ranges
     frame_range = Range1d(0, 1, bounds=(0, 1))
     scanning_motor_range = Range1d(0, 1, bounds=(0, 1))
-    color_mapper_proj = LinearColorMapper()
+    lin_color_mapper_proj = LinearColorMapper(low=0, high=1)
+    log_color_mapper_proj = LogColorMapper(low=0, high=1)
 
     det_x_range = Range1d(0, IMAGE_W, bounds=(0, IMAGE_W))
     gamma_range = Range1d(0, 1, bounds=(0, 1))
@@ -600,7 +604,7 @@ def create():
         dict(image=[np.zeros((1, 1), dtype="float32")], x=[0], y=[0], dw=[IMAGE_W], dh=[1])
     )
 
-    proj_x_plot.image(source=proj_x_image_source, color_mapper=color_mapper_proj)
+    proj_x_image = proj_x_plot.image(source=proj_x_image_source, color_mapper=lin_color_mapper_proj)
 
     det_y_range = Range1d(0, IMAGE_H, bounds=(0, IMAGE_H))
     nu_range = Range1d(0, 1, bounds=(0, 1))
@@ -629,7 +633,7 @@ def create():
         dict(image=[np.zeros((1, 1), dtype="float32")], x=[0], y=[0], dw=[IMAGE_H], dh=[1])
     )
 
-    proj_y_plot.image(source=proj_y_image_source, color_mapper=color_mapper_proj)
+    proj_y_image = proj_y_plot.image(source=proj_y_image_source, color_mapper=lin_color_mapper_proj)
 
     # ROI slice plot
     roi_avg_plot = figure(plot_height=150, plot_width=IMAGE_PLOT_W, tools="", toolbar_location=None)
@@ -638,17 +642,41 @@ def create():
     roi_avg_plot.line(source=roi_avg_plot_line_source, line_color="steelblue")
 
     def colormap_select_callback(_attr, _old, new):
-        color_mapper.palette = new
-        color_mapper_proj.palette = new
+        lin_color_mapper.palette = new
+        log_color_mapper.palette = new
+        lin_color_mapper_proj.palette = new
+        log_color_mapper_proj.palette = new
 
     colormap_select = Select(
         title="Colormap:",
         options=[("Greys256", "greys"), ("Plasma256", "plasma"), ("Cividis256", "cividis")],
-        width=210,
+        width=100,
     )
     colormap_select.on_change("value", colormap_select_callback)
     colormap_select.value = "Plasma256"
 
+    def colormap_scale_rg_callback(selection):
+        if selection == 0:  # Linear
+            plot_image.glyph.color_mapper = lin_color_mapper
+            proj_x_image.glyph.color_mapper = lin_color_mapper_proj
+            proj_y_image.glyph.color_mapper = lin_color_mapper_proj
+
+        else:  # Logarithmic
+            if (
+                display_min_spinner.value > 0
+                and display_max_spinner.value > 0
+                and proj_display_min_spinner.value > 0
+                and proj_display_max_spinner.value > 0
+            ):
+                plot_image.glyph.color_mapper = log_color_mapper
+                proj_x_image.glyph.color_mapper = log_color_mapper_proj
+                proj_y_image.glyph.color_mapper = log_color_mapper_proj
+            else:
+                colormap_scale_rg.active = 0
+
+    colormap_scale_rg = RadioGroup(labels=["Linear", "Logarithmic"], active=0, width=100)
+    colormap_scale_rg.on_click(colormap_scale_rg_callback)
+
     def main_auto_checkbox_callback(state):
         if state:
             display_min_spinner.disabled = True
@@ -664,20 +692,21 @@ def create():
     )
     main_auto_checkbox.on_click(main_auto_checkbox_callback)
 
-    def display_max_spinner_callback(_attr, _old_value, new_value):
-        color_mapper.high = new_value
+    def display_max_spinner_callback(_attr, _old, new):
+        lin_color_mapper.high = new
+        log_color_mapper.high = new
+        # TODO: without this _update_image() log color mapper display is delayed
+        _update_image()
 
-    display_max_spinner = Spinner(
-        value=1, disabled=bool(main_auto_checkbox.active), mode="int", width=100
-    )
+    display_max_spinner = Spinner(value=1, disabled=bool(main_auto_checkbox.active), width=100)
     display_max_spinner.on_change("value", display_max_spinner_callback)
 
-    def display_min_spinner_callback(_attr, _old_value, new_value):
-        color_mapper.low = new_value
+    def display_min_spinner_callback(_attr, _old, new):
+        lin_color_mapper.low = new
+        log_color_mapper.low = new
+        _update_image()
 
-    display_min_spinner = Spinner(
-        value=0, disabled=bool(main_auto_checkbox.active), mode="int", width=100
-    )
+    display_min_spinner = Spinner(value=0, disabled=bool(main_auto_checkbox.active), width=100)
     display_min_spinner.on_change("value", display_min_spinner_callback)
 
     def proj_auto_checkbox_callback(state):
@@ -695,20 +724,20 @@ def create():
     )
     proj_auto_checkbox.on_click(proj_auto_checkbox_callback)
 
-    def proj_display_max_spinner_callback(_attr, _old_value, new_value):
-        color_mapper_proj.high = new_value
+    def proj_display_max_spinner_callback(_attr, _old, new):
+        lin_color_mapper_proj.high = new
+        log_color_mapper_proj.high = new
+        _update_proj_plots()
 
-    proj_display_max_spinner = Spinner(
-        value=1, disabled=bool(proj_auto_checkbox.active), mode="int", width=100
-    )
+    proj_display_max_spinner = Spinner(value=1, disabled=bool(proj_auto_checkbox.active), width=100)
     proj_display_max_spinner.on_change("value", proj_display_max_spinner_callback)
 
-    def proj_display_min_spinner_callback(_attr, _old_value, new_value):
-        color_mapper_proj.low = new_value
+    def proj_display_min_spinner_callback(_attr, _old, new):
+        lin_color_mapper_proj.low = new
+        log_color_mapper_proj.low = new
+        _update_proj_plots()
 
-    proj_display_min_spinner = Spinner(
-        value=0, disabled=bool(proj_auto_checkbox.active), mode="int", width=100
-    )
+    proj_display_min_spinner = Spinner(value=0, disabled=bool(proj_auto_checkbox.active), width=100)
     proj_display_min_spinner.on_change("value", proj_display_min_spinner_callback)
 
     events_data = dict(
@@ -881,7 +910,7 @@ def create():
 
     layout_image = column(gridplot([[proj_v, None], [plot, proj_h]], merge_tools=False))
     colormap_layout = column(
-        colormap_select,
+        row(colormap_select, column(Spacer(height=15), colormap_scale_rg)),
         main_auto_checkbox,
         row(display_min_spinner, display_max_spinner),
         proj_auto_checkbox,
diff --git a/pyzebra/app/panel_plot_data.py b/pyzebra/app/panel_plot_data.py
index fe0df21..f7c60f4 100644
--- a/pyzebra/app/panel_plot_data.py
+++ b/pyzebra/app/panel_plot_data.py
@@ -13,8 +13,10 @@ from bokeh.models import (
     Div,
     FileInput,
     LinearColorMapper,
+    LogColorMapper,
     NumericInput,
     Panel,
+    RadioGroup,
     Select,
     Spacer,
     Spinner,
@@ -254,11 +256,15 @@ def create():
     )
     plot.toolbar.logo = None
 
-    color_mapper = LinearColorMapper(nan_color=(0, 0, 0, 0), low=0, high=1)
+    lin_color_mapper = LinearColorMapper(nan_color=(0, 0, 0, 0), low=0, high=1)
+    log_color_mapper = LogColorMapper(nan_color=(0, 0, 0, 0), low=0, high=1)
     image_source = ColumnDataSource(dict(image=[np.zeros((1, 1))], x=[0], y=[0], dw=[1], dh=[1]))
-    plot.image(source=image_source, color_mapper=color_mapper)
+    plot_image = plot.image(source=image_source, color_mapper=lin_color_mapper)
 
-    plot.add_layout(ColorBar(color_mapper=color_mapper, width=15), "right")
+    lin_color_bar = ColorBar(color_mapper=lin_color_mapper, width=15)
+    log_color_bar = ColorBar(color_mapper=log_color_mapper, width=15, visible=False)
+    plot.add_layout(lin_color_bar, "right")
+    plot.add_layout(log_color_bar, "right")
 
     scatter_source = ColumnDataSource(dict(x=[], y=[]))
     plot.scatter(source=scatter_source, size=4, fill_color="green", line_color="green")
@@ -298,7 +304,8 @@ def create():
     redef_ub_ti = TextInput(width=490, disabled=True)
 
     def colormap_select_callback(_attr, _old, new):
-        color_mapper.palette = new
+        lin_color_mapper.palette = new
+        log_color_mapper.palette = new
 
     colormap_select = Select(
         title="Colormap:",
@@ -309,17 +316,36 @@ def create():
     colormap_select.value = "Plasma256"
 
     def display_min_ni_callback(_attr, _old, new):
-        color_mapper.low = new
+        lin_color_mapper.low = new
+        log_color_mapper.low = new
 
     display_min_ni = NumericInput(title="Intensity min:", value=0, mode="float", width=70)
     display_min_ni.on_change("value", display_min_ni_callback)
 
     def display_max_ni_callback(_attr, _old, new):
-        color_mapper.high = new
+        lin_color_mapper.high = new
+        log_color_mapper.high = new
 
     display_max_ni = NumericInput(title="max:", value=1, mode="float", width=70)
     display_max_ni.on_change("value", display_max_ni_callback)
 
+    def colormap_scale_rg_callback(selection):
+        if selection == 0:  # Linear
+            plot_image.glyph.color_mapper = lin_color_mapper
+            lin_color_bar.visible = True
+            log_color_bar.visible = False
+
+        else:  # Logarithmic
+            if display_min_ni.value > 0 and display_max_ni.value > 0:
+                plot_image.glyph.color_mapper = log_color_mapper
+                lin_color_bar.visible = False
+                log_color_bar.visible = True
+            else:
+                colormap_scale_rg.active = 0
+
+    colormap_scale_rg = RadioGroup(labels=["Linear", "Logarithmic"], active=0, width=100)
+    colormap_scale_rg.on_click(colormap_scale_rg_callback)
+
     xrange_min_ni = NumericInput(title="x range min:", value=0, mode="float", width=70)
     xrange_max_ni = NumericInput(title="max:", value=1, mode="float", width=70)
     xrange_step_ni = NumericInput(title="x mesh:", value=0.01, mode="float", width=70)
@@ -355,7 +381,7 @@ def create():
                 hkl_div,
                 row(hkl_normal, hkl_cut, hkl_delta),
                 row(hkl_in_plane_x, hkl_in_plane_y),
-                colormap_select,
+                row(colormap_select, column(Spacer(height=15), colormap_scale_rg)),
                 row(display_min_ni, display_max_ni),
                 row(column(Spacer(height=19), auto_range_cb)),
                 row(xrange_min_ni, xrange_max_ni),
-- 
GitLab