Skip to content

Keep the LUT range in data units with the Z-axis log scale (color bar, contrast panel) - #86

Merged
PierreRaybaut merged 2 commits into
developfrom
fix/63-zaxis-log-data-units
Oct 29, 2026
Merged

PierreRaybaut merged 2 commits into
developfrom
fix/63-zaxis-log-data-units

Conversation

@PierreRaybaut

Copy link
Copy Markdown
Contributor

Closes #63.

Problem

With the Z-axis logarithmic scale (ZAxisLogTool), image items expressed their LUT range (min/max) in base-10 logarithms, whereas the cursor values, the contrast histogram and the image data stay in data units. As a result:

  • the color bar showed the logarithms of the image values (e.g. 0 to 4 instead of 1 to 10000);
  • the contrast adjustment panel mixed a histogram of the data with a range of logarithms: "Full range", "Eliminate %", "Min." and "Max." applied data values as log bounds, which showed the image with a single color;
  • cross sections and exported images with the LUT applied (apply_lut=True) applied the log coefficients to the data (saturated values);
  • set_data(lut_range=...), keep_lut_range, serialization, ImageAxesParam (z min/max) and downstream applications (e.g. DataLab's Z scale bounds) handled a range whose unit depended on the scale;
  • clip(1) turned every value lower than 1 into 0: data between 0 and 1 was shown with a single color;
  • ZAxisLogTool was enabled for items which do not draw the log data (RGB images, 2D histograms, quadrilateral grids, image filters).

Changes

The LUT range now always uses data units: only the color mapping is logarithmic, as with matplotlib's LogNorm.

Image items (BaseImageItem)

  • set_lut_range stores the range in data units and computes the LUT coefficients from the logarithms of the bounds. In log mode, bounds lower than or equal to zero are replaced by the smallest positive value of the data.
  • get_lut_range_full returns the full range of the positive values in log mode; set_data uses it when no lut_range is given, and an explicit lut_range is in data units.
  • Values lower than or equal to zero are drawn with the lowest color of the colormap: they are replaced by the smallest positive value of the data (instead of 1). NaN values are kept.
  • Enabling the log scale sets the full range of the positive values; disabling it restores the previous LUT range (updated by set_data as it would have been without the log scale). Enabling it twice no longer overwrites the saved range.
  • New helpers _get_display_data (shared by draw_image of RawImageItem, ImageItem, XYImageItem and TrImageItem) and _to_lut_space (cross sections).
  • export_roi(apply_lut=True) and cross sections with apply_lut=True use the log data.
  • The anti-aliasing kernel created by set_interpolation follows the data type of the data drawn.
  • New public method supports_zaxis_log(): False for RGBImageItem, QuadGridItem, Histogram2DItem and ImageFilterItem, for which set_zaxis_log_state has no effect.

Color bar

  • BasePlot.update_colormap_axis switches the colormap axis to QwtLogScaleEngine (bounds in data units: decade ticks with minor ticks at 2, 4, 6, 8) and back to a linear scale engine, only when needed.
  • New LogScaleColorMap (plotpy.mathutils.colormap) wraps the item colormap so that the colors of the color bar match the image (a log scale engine alone would keep a linear colormap along a logarithmic axis).

Tool

  • ZAxisLogTool only handles items supporting the log scale, and calls notify_colormap_changed(): the contrast panel and SIG_LUT_CHANGED listeners get the new range.

Tests

  • New plotpy/tests/unit/test_image_zaxis_log.py (28 tests including parametrizations over ImageItem, XYImageItem, TrImageItem, MaskedImageItem, MaskedXYImageItem):
    • toggling and LUT range in data units;
    • cross sections and export_roi with the log LUT;
    • non-positive values, data between 0 and 1;
    • full range / outliers elimination;
    • set_data with and without lut_range;
    • anti-aliasing kernel dtype;
    • color bar scale engine, bounds and colormap, when switching between log and linear items;
    • LogScaleColorMap colors (with inverted colormaps);
    • unsupported items; contrast panel range after toggling.
  • test_image_log_set_data.py updated to data units; test_zaxislog.py checks the color bar scale.
  • Full PlotPy suite locally: 423 passed, 16 skipped.
  • Downstream, with this branch: DataLab image tools / contrast / ROI editor tests (62 passed), SigimaX tests (354 passed).

Notes

  • API change: in log mode, get_lut_range, set_lut_range, get_lut_range_full and set_data(lut_range=...) now use data units instead of base-10 logarithms (documented in the 2.12.0 release notes). Since the previous behavior was internally inconsistent, this is handled as a bug fix.
  • Thanks to @ovallcorba for the clear report, and for the feat/logscale_colorbar branch which fixed the color bar labels: this PR takes the same log scale engine approach, and extends it to the colors of the color bar and to the LUT range, so that every consumer of the range sees data units.
  • Not included (possible follow-ups): logarithmic X axis for the contrast histogram, persistence of the Z-axis log state, log scale for QuadGridItem, and log colors for ImageFilterItem when it uses the colormap of a source image in log mode.

With the Z-axis logarithmic scale, the LUT range of image items was
expressed in base-10 logarithms: the color bar showed logarithms of the
image values, the contrast adjustment panel mixed a histogram of the data
with a range of logarithms (so that "Full range" or "Eliminate %" showed
the image with a single color), and cross sections or exported images with
the LUT applied mixed data with log coefficients.

The LUT range now always uses data units: only the color mapping is
logarithmic (like matplotlib's LogNorm).

- `set_lut_range` stores data units and computes the LUT coefficients
  from the logarithms of the bounds; `get_lut_range_full` and
  `set_data` follow the same convention
- Values lower than or equal to zero are replaced by the smallest
  positive value of the data instead of 1 (`clip(1)`), so that data
  lower than 1 is no longer shown with a single color
- Enabling the log scale sets the full range of the positive values,
  disabling it restores the previous LUT range; enabling it twice no
  longer overwrites the saved range
- The color bar uses a logarithmic scale engine, with bounds in data
  units, and the new `LogScaleColorMap` so that its colors match the
  image
- Cross sections and `export_roi` with `apply_lut=True` use the log
  data; the anti-aliasing kernel follows the data type of the data drawn
- New `BaseImageItem.supports_zaxis_log`: RGB images, 2D histograms,
  quadrilateral grids and image filters, which do not draw the log data,
  are ignored by `ZAxisLogTool` and `set_zaxis_log_state`
- `ZAxisLogTool` notifies the LUT change, so that the contrast panel and
  other listeners get the new range

Closes #63

Assisted-by: Claude Opus 5.5
@PierreRaybaut
PierreRaybaut merged commit b62c67d into develop Oct 29, 2026
4 checks passed
@PierreRaybaut
PierreRaybaut deleted the fix/63-zaxis-log-data-units branch October 29, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant