Skip to content

Commit

Permalink
Merge pull request #3856 from vallsv/image-compare-autoscale
Browse files Browse the repository at this point in the history
CompareImages: Fix the A-B visualization mode
  • Loading branch information
t20100 authored Jun 15, 2023
2 parents 86c8e65 + 57cb803 commit 73df45e
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 30 deletions.
47 changes: 20 additions & 27 deletions src/silx/gui/plot/CompareImages.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import logging
import numpy
import math
from typing import Optional

import silx.image.bilinear
from silx.gui import qt
Expand All @@ -42,6 +41,7 @@
from silx.utils.deprecation import deprecated_warning
from silx.utils.weakref import WeakMethodProxy
from silx.gui.plot.items import Scatter
from silx.math.colormap import normalize

from .tools.compare.core import sift
from .tools.compare.core import VisualizationMode
Expand Down Expand Up @@ -298,7 +298,7 @@ def setVisualizationMode(self, mode):
if self.__visualizationMode == mode:
return
self.__visualizationMode = mode
mode = self.getVisualizationMode()
self.__item.setVizualisationMode(mode)
self.__vline.setVisible(mode == VisualizationMode.VERTICAL_LINE)
self.__hline.setVisible(mode == VisualizationMode.HORIZONTAL_LINE)
self.__updateData()
Expand Down Expand Up @@ -588,10 +588,10 @@ def __updateData(self):

mode = self.getVisualizationMode()
if mode == VisualizationMode.COMPOSITE_RED_BLUE_GRAY_NEG:
data1 = self.__composeImage(data1, data2, mode)
data1 = self.__composeRgbImage(data1, data2, mode)
data2 = None
elif mode == VisualizationMode.COMPOSITE_RED_BLUE_GRAY:
data1 = self.__composeImage(data1, data2, mode)
data1 = self.__composeRgbImage(data1, data2, mode)
data2 = None
elif mode == VisualizationMode.COMPOSITE_A_MINUS_B:
data1 = self.__composeAMinusBImage(data1, data2)
Expand Down Expand Up @@ -670,7 +670,7 @@ def __rescaleImage(self, image, shape):
data[:, :, c] = self.__rescaleArray(image[:, :, c], shape)
return data

def __composeImage(self, data1, data2, mode):
def __composeRgbImage(self, data1, data2, mode):
"""Returns an RBG image containing composition of data1 and data2 in 2
different channels
Expand Down Expand Up @@ -708,28 +708,31 @@ def __composeImage(self, data1, data2, mode):

shape = intensity1.shape
result = numpy.empty((shape[0], shape[1], 3), dtype=numpy.uint8)
a = (intensity1.astype(numpy.float32) - vmin) * (1.0 / (vmax - vmin)) * 255.0
a[a < 0] = 0
a[a > 255] = 255
b = (intensity2.astype(numpy.float32) - vmin) * (1.0 / (vmax - vmin)) * 255.0
b[b < 0] = 0
b[b > 255] = 255
a, _, _ = normalize(intensity1,
norm=sealed.getNormalization(),
autoscale=sealed.getAutoscaleMode(),
vmin=sealed.getVMin(),
vmax=sealed.getVMax(),
gamma=sealed.getGammaNormalizationParameter())
b, _, _ = normalize(intensity2,
norm=sealed.getNormalization(),
autoscale=sealed.getAutoscaleMode(),
vmin=sealed.getVMin(),
vmax=sealed.getVMax(),
gamma=sealed.getGammaNormalizationParameter())
if mode == VisualizationMode.COMPOSITE_RED_BLUE_GRAY:
result[:, :, 0] = a
result[:, :, 1] = (a + b) / 2
result[:, :, 1] = a // 2 + b // 2
result[:, :, 2] = b
elif mode == VisualizationMode.COMPOSITE_RED_BLUE_GRAY_NEG:
result[:, :, 0] = 255 - b
result[:, :, 1] = 255 - (a + b) / 2
result[:, :, 1] = 255 - (a // 2 + b // 2)
result[:, :, 2] = 255 - a
return result

def __composeAMinusBImage(self, data1, data2):
"""Returns an intensity image containing the composition of `A-B`.
The result is returned as an image array of float normalized into the
colormap range.
A data image of a size of 0 is considered as missing. This does not
interrupt the processing.
Expand All @@ -740,24 +743,14 @@ def __composeAMinusBImage(self, data1, data2):
if data1.size != 0 and data2.size != 0:
assert(data1.shape[0:2] == data2.shape[0:2])

sealed = self.__getSealedColormap()
vmin, vmax = sealed.getVRange()

data1 = self.__asIntensityImage(data1)
data2 = self.__asIntensityImage(data2)
if data1.size == 0:
result = data2
elif data2.size == 0:
result = data1
else:
a = (data1.astype(numpy.float32) - vmin) * (1.0 / (vmax - vmin))
a[a < 0] = 0
a[a > 1] = 1
b = (data2.astype(numpy.float32) - vmin) * (1.0 / (vmax - vmin))
b[b < 0] = 0
b[b > 1] = 1
r = a - b
result = vmin + (r - r.min()) * ((vmax - vmin) / (r.max() - r.min()))
result = data1.astype(numpy.float32) - data2.astype(numpy.float32)
return result

def __asIntensityImage(self, image: numpy.ndarray):
Expand Down
60 changes: 57 additions & 3 deletions src/silx/gui/plot/tools/compare/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import numpy
import enum
import contextlib
from typing import NamedTuple

from silx.gui.plot.items.image import ImageBase
Expand Down Expand Up @@ -89,6 +90,7 @@ def __init__(self):
ColormapMixIn.__init__(self)
self.__image1 = None
self.__image2 = None
self.__vizualisationMode = VisualizationMode.ONLY_A

def getImageData1(self):
return self.__image1
Expand All @@ -108,6 +110,53 @@ def setImageData2(self, image2):
self.__image2 = image2
self._updated(ItemChangedType.DATA)

def getVizualisationMode(self) -> VisualizationMode:
return self.__vizualisationMode

@contextlib.contextmanager
def _updateColormapRange(self, previousMode, mode):
"""COMPOSITE_A_MINUS_B don't have the same data range than others.
If the colormap is using a fixed range, it is updated in order to set
a similar range with the new data.
"""
normalize_colormap = (previousMode == VisualizationMode.COMPOSITE_A_MINUS_B
or mode == VisualizationMode.COMPOSITE_A_MINUS_B)
if normalize_colormap:
data = self._getConcatenatedData(copy=False)
if data is None or data.size == 0:
normalize_colormap = False
else:
std1 = numpy.nanstd(data)
mean1 = numpy.nanmean(data)
yield

def transfer(v, std1, mean1, std2, mean2):
"""Transfer a value from a data range to another using statistics"""
if v is None:
return None
rv = (v - mean1) / std1
return rv * std2 + mean2

if normalize_colormap:
data = self._getConcatenatedData(copy=False)
if data is not None and data.size != 0:
std2 = numpy.nanstd(data)
mean2 = numpy.nanmean(data)
c = self.getColormap()
if c is not None:
vmin, vmax = c.getVRange()
vmin = transfer(vmin, std1, mean1, std2, mean2)
vmax = transfer(vmax, std1, mean1, std2, mean2)
c.setVRange(vmin, vmax)

def setVizualisationMode(self, mode: VisualizationMode):
if self.__vizualisationMode == mode:
return None
with self._updateColormapRange(self.__vizualisationMode, mode):
self.__vizualisationMode = mode
self._updated(ItemChangedType.DATA)

def _getConcatenatedData(self, copy=True):
if self.__image1 is None and self.__image2 is None:
return None
Expand All @@ -116,9 +165,14 @@ def _getConcatenatedData(self, copy=True):
if self.__image2 is None:
return numpy.array(self.__image1, copy=copy)

d1 = self.__image1[numpy.isfinite(self.__image1)]
d2 = self.__image2[numpy.isfinite(self.__image2)]
return numpy.concatenate((d1, d2))
if self.__vizualisationMode == VisualizationMode.COMPOSITE_A_MINUS_B:
# In this case the histogram have to be special
if self.__image1.shape == self.__image2.shape:
return self.__image1.astype(numpy.float32) - self.__image2.astype(numpy.float32)
else:
d1 = self.__image1[numpy.isfinite(self.__image1)]
d2 = self.__image2[numpy.isfinite(self.__image2)]
return numpy.concatenate((d1, d2))

def _updated(self, event=None, checkVisibility=True):
# Synchronizes colormapped data if changed
Expand Down

0 comments on commit 73df45e

Please sign in to comment.