Skip to content

Unit Conversion for Signal and Image Axes #295

@PierreRaybaut

Description

@PierreRaybaut

Summary

Add a unit conversion feature allowing users to convert axis units on signals (X/Y) and images (X/Y/Z) through a dedicated dialog with predefined unit categories and custom conversion support.

Motivation

Currently, DataLab stores axis units as free-form strings (xunit, yunit, zunit) with no semantic meaning. When users need to convert units (e.g., wavelength from nm to µm, time from s to ms), they must:

  1. Manually apply a calibration (a*x + b) via the existing calibration dialog
  2. Separately edit the unit string through the object properties editor

This two-step workflow is error-prone and unintuitive. A dedicated unit conversion feature would streamline this into a single operation.

Scope

In scope

  • New "Convert units..." action in the Processing > Axis transformation submenu for both Signal and Image panels
  • Conversion dialog with:
    • Axis selector (X, Y for signals; X, Y, Z for images)
    • Predefined unit categories with conversion factors (see below)
    • Custom conversion mode (user-defined factor and target unit string)
    • Preview of the conversion factor to be applied
  • Predefined unit categories:
    • Length: nm, µm, mm, cm, m, km, in, ft
    • Time: ns, µs, ms, s, min, h
    • Frequency: Hz, kHz, MHz, GHz, THz
    • Wavelength/Energy (spectroscopy): nm ↔ µm ↔ cm⁻¹ ↔ eV ↔ THz
    • Angle: rad, deg, mrad
  • Automatic data scaling: Multiply/divide axis data by the appropriate conversion factor
  • Automatic unit string update: Replace the unit string on the target axis
  • (Optional) Enhance existing calibration dialog: Add "source unit" / "target unit" fields to XYCalibrateParam / XYZCalibrateParam so that calibration also updates the unit string

Out of scope

  • Integration with external unit libraries (pint, astropy.units)
  • Automatic unit propagation across all processing functions
  • Dimensional analysis or unit compatibility checks on operations
  • Unit-aware arithmetic (e.g., warning when adding signals with different units)

Proposed Implementation

Architecture

The implementation follows the standard DataLab/Sigima pattern:

  1. Sigima layer (sigima.proc): Computation function + parameter class
  2. DataLab layer: Processor registration + menu action

Sigima: Unit conversion engine

New module: sigima/proc/common/units.py (or extend existing calibration modules)

# Unit registry: category → {unit_name: factor_to_base_unit}
UNIT_CATEGORIES = {
    "Length": {"nm": 1e-9, "µm": 1e-6, "mm": 1e-3, "cm": 1e-2, "m": 1.0, "km": 1e3, "in": 0.0254, "ft": 0.3048},
    "Time": {"ns": 1e-9, "µs": 1e-6, "ms": 1e-3, "s": 1.0, "min": 60.0, "h": 3600.0},
    "Frequency": {"Hz": 1.0, "kHz": 1e3, "MHz": 1e6, "GHz": 1e9, "THz": 1e12},
    "Angle": {"rad": 1.0, "deg": 0.017453292519943, "mrad": 1e-3},
    # Spectroscopy: non-linear conversions handled separately
}

Parameter class: ConvertUnitsParam

  • axis: Choice (X / Y / Z)
  • category: Choice (Length / Time / Frequency / Angle / Spectroscopy / Custom)
  • source_unit: Auto-detected from current object or user-selected
  • target_unit: User-selected from category
  • custom_factor: Float (only for Custom mode)
  • custom_unit: String (only for Custom mode)

Computation function: convert_units(src, p) → dst

  • Computes conversion factor from source → target
  • Scales axis data accordingly
  • Updates unit string on the result object
  • Handles spectroscopy non-linear conversions (e.g., nm → cm⁻¹ requires 1e7 / x)

DataLab: Registration

  • Register as 1_to_1 in both SignalProcessor and ImageProcessor
  • Add to Processing > Axis transformation submenu
  • Icon: existing or new unit-related icon

UX Flow

  1. User selects one or more signal/image objects
  2. Processing > Axis transformation > Convert units...
  3. Dialog opens:
    • Axis: X / Y (or X / Y / Z for images)
    • Current unit is auto-filled from the object's xunit/yunit/zunit
    • Category dropdown → filters available source/target units
    • If current unit matches a known unit → auto-selects category and source
    • If not → defaults to "Custom" mode
    • Target unit dropdown (filtered by category)
    • Preview: shows the conversion factor (e.g., "×1000" or "÷1e-6")
  4. User confirms → conversion applied to all selected objects (1-to-1 pattern)

Acceptance Criteria

  • "Convert units..." action available in Processing > Axis transformation for both Signal and Image panels
  • Predefined conversions work correctly for all listed unit categories (linear)
  • Spectroscopy conversions (non-linear: nm ↔ cm⁻¹ ↔ eV) work correctly
  • Custom conversion mode allows arbitrary factor + unit string
  • Unit string is automatically updated on the converted axis
  • Current unit is auto-detected and pre-filled in the dialog
  • Works with multi-object selection (applied independently to each, 1-to-1 pattern)
  • Uncertainties (dx/dy) are scaled appropriately when present
  • Unit tests cover all predefined categories and edge cases
  • UI strings are internationalized (_() wrapped)
  • French translations provided

Technical Notes

  • For linear conversions (most categories), the factor is simply source_factor / target_factor
  • For spectroscopy (wavelength ↔ wavenumber ↔ energy ↔ frequency), conversions are non-linear and require transforming the data values (e.g., wavenumber_cm⁻¹ = 1e7 / wavelength_nm). This also reverses the X-axis ordering.
  • The existing TIME_UNIT_FACTORS in sigima/objects/signal/constants.py and FreqUnits in sigima/objects/signal/creation.py could be consolidated into the new unit registry.
  • Consider reusing the same unit registry for the FFT/IFFT unit propagation (currently hardcoded "s"→"Hz")

Related Existing Features

  • Linear calibration (Processing > Axis transformation > Linear calibration...)
  • Polynomial calibration (Image panel)
  • FFT/IFFT automatic unit swapping ("s" ↔ "Hz")
  • Transpose (swaps X/Y units)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions