Commit f6ea0bec authored by Julia Garriga Ferrer's avatar Julia Garriga Ferrer
Browse files

Merge branch 'magnification'

parents cd7fc36a ef969c11
Pipeline #48611 passed with stage
in 2 minutes and 6 seconds
......@@ -26,7 +26,7 @@
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "26/05/2021"
__date__ = "07/06/2021"
import copy
import glob
......@@ -46,14 +46,14 @@ from silx.io import utils
from silx.io import fabioh5
from silx.io.url import DataUrl
from darfix.core.dimension import AcquisitionDims, Dimension
from darfix.core.dimension import AcquisitionDims, Dimension, POSITIONER_METADATA
from darfix.core.imageOperations import img2img_mean, chunk_image
from darfix.core.imageOperations import background_subtraction, background_subtraction_2D
from darfix.core.imageOperations import hot_pixel_removal_3D, hot_pixel_removal_2D
from darfix.core.imageOperations import threshold_removal
from darfix.core.imageOperations import Method
from darfix.core.imageRegistration import shift_detection, apply_shift
from darfix.core.mapping import fit_data, compute_moments
from darfix.core.mapping import fit_data, compute_moments, compute_rsm, compute_magnification
from darfix.core.roi import apply_2D_ROI, apply_3D_ROI
from darfix.core.utils import wrapTo2pi
from darfix.decomposition.ipca import IPCA
......@@ -100,7 +100,7 @@ class Dataset():
"""
def __init__(self, _dir, data=None, first_filename=None, filenames=None,
dims=None, in_memory=True, copy_files=False):
dims=None, transformation=None, in_memory=True, copy_files=False):
self._data = None
self._frames_intensity = []
......@@ -109,6 +109,7 @@ class Dataset():
self.moments_dims = {}
self.operations_state = numpy.zeros(len(Operation))
self._dir = _dir
self._transformation = transformation
if copy_files:
self._dir += "/treated"
if not os.path.isdir(self._dir):
......@@ -164,6 +165,14 @@ class Dataset():
if self.running_data is not None:
self.running_data.stop_operation(operation)
@property
def transformation(self):
return self._transformation
@transformation.setter
def transformation(self, value):
self._transformation = value
@property
def dir(self):
return self._dir
......@@ -396,6 +405,10 @@ class Dataset():
_logger.info("Dimension {} of size {} has been added for reshaping"
.format(dimension.name, dimension.size))
def get_metadata_values(self, kind, key):
return numpy.unique([data.get_value(kind=kind, name=key)[0] for data
in self.get_data().metadata])
def __compute_changing_value(self, values, changing_value=1):
"""
Recursive method used to calculate how fast is a dimension. The speed of a dimension is the number of
......@@ -582,7 +595,8 @@ class Dataset():
new_data = Data(urls.reshape(self.data.urls.shape), self.data.metadata,
self._in_memory)
return Dataset(_dir=_dir, data=new_data, dims=self.__dims, in_memory=self._in_memory)
return Dataset(_dir=_dir, data=new_data, dims=self.__dims, transformation=self.transformation,
in_memory=self._in_memory)
def apply_hot_pixel_removal(self, kernel=3, indices=None, _dir=None):
"""
......@@ -627,7 +641,8 @@ class Dataset():
new_data = Data(urls.reshape(self.data.urls.shape), self.data.metadata,
self._in_memory)
return Dataset(_dir=_dir, data=new_data, dims=self.__dims, in_memory=self._in_memory)
return Dataset(_dir=_dir, data=new_data, dims=self.__dims, transformation=self.transformation,
in_memory=self._in_memory)
def apply_threshold_removal(self, bottom=None, top=None, indices=None, _dir=None):
"""
......@@ -672,7 +687,8 @@ class Dataset():
new_data = Data(urls.reshape(self.data.urls.shape), self.data.metadata,
self._in_memory)
return Dataset(_dir=_dir, data=new_data, dims=self.__dims, in_memory=self._in_memory)
return Dataset(_dir=_dir, data=new_data, dims=self.__dims, transformation=self.transformation,
in_memory=self._in_memory)
def apply_roi(self, origin=None, size=None, center=None, indices=None, roi_dir=None):
"""
......@@ -710,12 +726,16 @@ class Dataset():
if urls is None:
return
new_data = Data(urls, self.running_data.metadata, self._in_memory)
transformation = ([apply_2D_ROI(axis, origin, size, center) for axis in self.transformation]
if self.transformation else None)
if indices is None:
shape = list(self.data.shape)[:-2]
shape.append(new_data.shape[-2])
shape.append(new_data.shape[-1])
new_data = new_data.reshape(shape)
return Dataset(_dir=roi_dir, data=new_data, dims=self.__dims, in_memory=self._in_memory)
return Dataset(_dir=roi_dir, data=new_data, dims=self.__dims, transformation=transformation,
in_memory=self._in_memory)
def find_shift(self, dimension=None, h_max=0.5, h_step=0.01, indices=None):
"""
......@@ -823,7 +843,8 @@ class Dataset():
self._lock.release()
data = Data(new_urls.reshape(self.data.urls.shape), self.data.metadata, in_memory=self._in_memory)
return Dataset(_dir=_dir, data=data, dims=self.__dims, in_memory=self._in_memory)
return Dataset(_dir=_dir, data=data, dims=self.__dims, transformation=self.transformation,
in_memory=self._in_memory)
def find_and_apply_shift(self, dimension=None, h_max=0.5, h_step=0.01, shift_approach="fft",
indices=None, callback=None):
......@@ -1256,7 +1277,22 @@ class Dataset():
else:
new_urls = numpy.array(urls)
data = Data(new_urls.reshape(self.data.urls.shape), self.data.metadata, in_memory=True) # to modify
return Dataset(_dir=_dir, data=data, dims=self.__dims, in_memory=True)
return Dataset(_dir=_dir, data=data, dims=self.__dims, transformation=self.transformation, in_memory=True)
def compute_transformation(self, d, rotate=False):
H, W = self.get_data(0).shape
if self.dims.ndim == 1:
ffz = self.get_metadata_values(POSITIONER_METADATA, "ffz")[0]
mainx = -self.get_metadata_values(POSITIONER_METADATA, "mainx")[0]
self.transformation = compute_rsm(H, W, d, ffz, mainx)
else:
obx = self.get_metadata_values(POSITIONER_METADATA, "obx")[0]
obpitch = self.get_metadata_values(POSITIONER_METADATA, "obpitch")
obpitch = obpitch[len(obpitch) // 2]
mainx = -self.get_metadata_values(POSITIONER_METADATA, "mainx")[0]
self.transformation = compute_magnification(H, W, d, obx, obpitch, mainx)
def __deepcopy__(self, memo):
"""
......
......@@ -26,7 +26,7 @@
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "22/02/2020"
__date__ = "04/06/2021"
import numpy
try:
......@@ -138,13 +138,35 @@ def compute_moments(values, data):
stack = [values[i] * data[i] for i in range(len(data))]
zsum = numpy.sum(data, axis=0)
com = numpy.sum(stack, axis=0) / zsum
com = medfilt2d(com)
com = medfilt2d(com.astype(numpy.float64))
com[numpy.isnan(com)] = numpy.min(com[~numpy.isnan(com)])
repeat_values = numpy.repeat(values, com.shape[0] * com.shape[1]).reshape(len(values), com.shape[0], com.shape[1])
std = numpy.sqrt(numpy.sum((data[i] * ((repeat_values[i] - com)**2) for i in range(len(data))), axis=0)) / zsum
std = medfilt2d(std)
std = medfilt2d(std.astype(numpy.float64))
std[numpy.isnan(std)] = numpy.min(std[~numpy.isnan(std)])
skews = skew(stack, axis=0)
kurt = kurtosis(stack, axis=0)
return com, std, skews, kurt
def compute_rsm(H, W, d, ffz, mainx):
pix_arr = numpy.meshgrid(numpy.arange(H), numpy.arange(W))
pix_arr[0] = (pix_arr[0] - W / 2) * d
pix_arr[1] = (H / 2 - pix_arr[1]) * d
pix_arr[0] = numpy.arctan2((ffz - W / 2) * pix_arr[0], mainx)
pix_arr[1] = numpy.arctan2(pix_arr[1], numpy.sqrt(ffz * ffz + mainx * mainx))
return pix_arr
def compute_magnification(H, W, d, obx, obpitch, mainx):
pix_arr = numpy.meshgrid(numpy.arange(H), numpy.arange(W))
d1 = obx / numpy.cos(obpitch)
d2 = mainx / numpy.cos(obpitch) - d1
M = d2 / d1
d /= M
pix_arr[0] = (pix_arr[0] - W / 2) * d
pix_arr[1] = (H / 2 - pix_arr[1]) * d
return pix_arr
......@@ -26,7 +26,7 @@
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "22/02/2021"
__date__ = "07/06/2021"
from matplotlib.colors import hsv_to_rgb
import numpy
......@@ -111,22 +111,32 @@ class GrainPlotWidget(qt.QMainWindow):
self.indices = indices
self.bg_indices = bg_indices
self.bg_dataset = bg_dataset
for i in range(len(Method)):
self._methodCB.model().item(i).setEnabled(False)
scale = 100
self.ori_dist, self.hsv_key = self.dataset.compute_mosaicity_colorkey()
xdim = self.dataset.dims.get(1)
ydim = self.dataset.dims.get(0)
xscale = (xdim.unique_values[-1] - xdim.unique_values[0]) / (xdim.size - 1)
yscale = (ydim.unique_values[-1] - ydim.unique_values[0]) / (ydim.size - 1)
self._contoursPlot.addImage(hsv_to_rgb(self.hsv_key), xlabel=xdim.name,
ylabel=ydim.name, scale=(xscale / scale, yscale / scale))
self._contoursPlot.getColorBarWidget().hide()
self._methodCB.model().item(4).setEnabled(True)
self._methodCB.setCurrentIndex(4)
self._curvesColormap = Colormap(name='temperature',
vmin=numpy.min(self.ori_dist),
vmax=numpy.max(self.ori_dist))
self._computeContoursB.clicked.connect(self._computeContours)
if self.dataset.transformation:
px = self.dataset.transformation[0][0][0]
py = self.dataset.transformation[1][0][0]
xscale = (self.dataset.transformation[0][-1][-1] - px) / len(self.dataset.transformation[0][0])
yscale = (self.dataset.transformation[1][-1][-1] - py) / len(self.dataset.transformation[1][0])
self.origin = (px, py)
self.scale = (xscale, yscale)
if self.dataset.dims.ndim > 1:
self.ori_dist, self.hsv_key = self.dataset.compute_mosaicity_colorkey()
xdim = self.dataset.dims.get(1)
ydim = self.dataset.dims.get(0)
xscale = (xdim.unique_values[-1] - xdim.unique_values[0]) / (xdim.size - 1)
yscale = (ydim.unique_values[-1] - ydim.unique_values[0]) / (ydim.size - 1)
self._contoursPlot.addImage(hsv_to_rgb(self.hsv_key), xlabel=xdim.name,
ylabel=ydim.name, scale=(xscale / scale, yscale / scale))
self._contoursPlot.getColorBarWidget().hide()
self._curvesColormap = Colormap(name='temperature',
vmin=numpy.min(self.ori_dist),
vmax=numpy.max(self.ori_dist))
self._computeContoursB.clicked.connect(self._computeContours)
self._methodCB.model().item(4).setEnabled(True)
self._methodCB.setCurrentIndex(4)
self._thread = OperationThread(self, self.dataset.apply_moments)
self._thread.setArgs(self.indices)
self._thread.finished.connect(self._updateData)
......@@ -140,7 +150,6 @@ class GrainPlotWidget(qt.QMainWindow):
self._plots[-1].setGraphTitle(dim.name)
self._plots[-1].setDefaultColormap(Colormap(name='viridis'))
self._plotWidget.layout().addWidget(self._plots[-1])
self._updatePlot(self._methodCB.currentText())
def _updateData(self):
"""
......@@ -149,8 +158,11 @@ class GrainPlotWidget(qt.QMainWindow):
self._thread.finished.disconnect(self._updateData)
if self._thread.data is not None:
self._moments = self._thread.data
for i in range(len(Method)):
self._updatePlot(self._methodCB.currentText())
rg = len(Method) if self.dataset.dims.ndim > 1 else 4
for i in range(rg):
self._methodCB.model().item(i).setEnabled(True)
self._methodCB.setCurrentIndex(0)
else:
print("\nComputation aborted")
......@@ -226,8 +238,12 @@ class GrainPlotWidget(qt.QMainWindow):
plot.addImage(darfix.config.FWHM_VAL * self._moments[i][1])
elif method == Method.COM:
self._plotWidget.show()
for i, plot in enumerate(self._plots):
plot.addImage(self._moments[i][0])
if self.dataset.transformation is not None:
for i, plot in enumerate(self._plots):
plot.addImage(self._moments[i][0], origin=self.origin, scale=self.scale, xlabel='µm', ylabel='µm')
else:
for i, plot in enumerate(self._plots):
plot.addImage(self._moments[i][0], xlabel='pixels', ylabel='pixels')
elif method == Method.SKEWNESS:
self._plotWidget.show()
for i, plot in enumerate(self._plots):
......@@ -238,7 +254,13 @@ class GrainPlotWidget(qt.QMainWindow):
plot.addImage(self._moments[i][3])
elif method == Method.MOSAICITY:
self._plotWidget.hide()
self._mosaicityPlot.addImage(hsv_to_rgb(self._computeMosaicity()))
if self.dataset.transformation:
self._mosaicityPlot.addImage(hsv_to_rgb(self._computeMosaicity()),
origin=self.origin, scale=self.scale,
xlabel='µm', ylabel='µm')
else:
self._mosaicityPlot.addImage(hsv_to_rgb(self._computeMosaicity()))
self._mosaicityPlot.show()
def _opticolor(self, img, minc, maxc):
......
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "04/06/2021"
from silx.gui import qt
class MagnificationWidget(qt.QMainWindow):
"""
Widget to apply magnification transformation to the data axes.
"""
sigComputed = qt.Signal()
def __init__(self, parent=None):
qt.QWidget.__init__(self, parent)
widget = qt.QWidget()
layout = qt.QVBoxLayout()
self._checkbox2x = qt.QCheckBox("2x magnification")
self._checkbox10x = qt.QCheckBox("10x magnification")
self._checkboxManual = qt.QCheckBox("Manual magnification:")
self._manualLE = qt.QLineEdit(parent=self)
self._manualLE.setEnabled(False)
validator = qt.QDoubleValidator()
validator.setBottom(0)
self._manualLE.setValidator(validator)
self._okButton = qt.QPushButton("Ok")
self._okButton.setEnabled(False)
self._okButton.pressed.connect(self._saveMagnification)
layout.addWidget(self._checkbox2x)
layout.addWidget(self._checkbox10x)
layout.addWidget(self._checkboxManual)
layout.addWidget(self._manualLE)
layout.addWidget(self._okButton)
# self._okButton.pressed.connect(self._saveMagnification)
self._checkbox2x.stateChanged.connect(self._check2x)
self._checkbox10x.stateChanged.connect(self._check10x)
self._checkboxManual.stateChanged.connect(self._checkManual)
self._checkbox2x.setChecked(True)
widget.setLayout(layout)
self.setCentralWidget(widget)
def setDataset(self, dataset, indices=None, bg_indices=None, bg_dataset=None):
"""
Dataset setter.
:param Dataset dataset: dataset
"""
self.dataset = dataset
self.indices = indices
self.bg_indices = bg_indices
self.bg_dataset = bg_dataset
if not self.dataset.dims:
msg = qt.QMessageBox()
msg.setIcon(qt.QMessageBox.Warning)
msg.setText("This widget has to be used before selecting any region of \
interest and after selecting the dimensions")
msg.exec_()
else:
self._okButton.setEnabled(True)
def getDataset(self):
return self.dataset, self.indices, self.bg_indices, self.bg_dataset
def _checkManual(self, checked):
if checked:
self._checkbox2x.setChecked(False)
self._checkbox10x.setChecked(False)
self._manualLE.setEnabled(True)
else:
self._manualLE.setEnabled(False)
def _check2x(self, checked):
if checked:
self._checkbox10x.setChecked(False)
self._checkboxManual.setChecked(False)
self._manualLE.setEnabled(False)
def _check10x(self, checked):
if checked:
self._checkbox2x.setChecked(False)
self._checkboxManual.setChecked(False)
self._manualLE.setEnabled(False)
def _saveMagnification(self):
if self._checkbox2x.isChecked():
d = 3.75
elif self._checkbox10x.isChecked():
d = 0.75
else:
magnification = self._manualLE.text()
if magnification == "":
msg = qt.QMessageBox()
msg.setIcon(qt.QMessageBox.Warning)
msg.setText("Magnification value has to be entered when choosing manual")
msg.exec_()
return
self.dataset.compute_transformation(d)
self.sigComputed.emit()
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "04/06/2021"
from silx.gui import qt
from silx.utils.enum import Enum as _Enum
class PixelSize(_Enum):
"""
Different pixel sizes
"""
Basler = 0.051
PcoEdge_2x = 0.00375
PcoEdge_10x = 0.00075
class RSMWidget(qt.QMainWindow):
"""
Widget to compute Reciprocal Space Map
"""
sigComputed = qt.Signal()
def __init__(self, parent=None):
qt.QWidget.__init__(self, parent)
self._rotate = False
self._moments = None
widget = qt.QWidget()
layout = qt.QGridLayout()
pixelSizeLabel = qt.QLabel("Pixel size: ")
self._pixelSizeCB = qt.QComboBox()
self._pixelSizeCB.addItems(PixelSize.names())
self._rotateCB = qt.QCheckBox("Rotate RSM", self)
self._okButton = qt.QPushButton("Ok")
self._okButton.setEnabled(False)
self._okButton.pressed.connect(self._saveRSM)
layout.addWidget(pixelSizeLabel, 0, 0)
layout.addWidget(self._pixelSizeCB, 0, 1)
layout.addWidget(self._rotateCB, 0, 2)
layout.addWidget(self._okButton, 1, 0, 1, 2)
widget.setLayout(layout)
self.setCentralWidget(widget)
def setDataset(self, dataset, indices=None, bg_indices=None, bg_dataset=None):
"""
Dataset setter.
:param Dataset dataset: dataset
"""
self.dataset = dataset
self.indices = indices
self.bg_indices = bg_indices
self.bg_dataset = bg_dataset
self._okButton.setEnabled(True)
def getDataset(self):
return self.dataset, self.indices, self.bg_indices, self.bg_dataset
def _saveRSM(self):
self.dataset.compute_transformation(PixelSize[self._pixelSizeCB.currentText()].value,
self._rotateCB.isChecked())
self.sigComputed.emit()
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""Example showing the widget :mod:`~darfix.gui.dimensionsWidget.DimensionWidget`.
"""
__authors__ = ["J. Garriga"]
__license__ = "MIT"
__date__ = "01/06/2021"
import signal
import sys
from silx.gui import qt
from darfix.test.utils import createRandomDataset
from darfix.gui.magnificationWidget import MagnificationWidget
def exec_():
qapp = qt.QApplication([])
# add connection with ctrl + c signal
qt.QLocale.setDefault(qt.QLocale.c())
signal.signal(signal.SIGINT, sigintHandler)
sys.excepthook = qt.exceptionHandler
timer = qt.QTimer()
timer.start(500)
# Application have to wake up Python interpreter, else SIGINT is not
# catch
timer.timeout.connect(lambda: None)
w = MagnificationWidget()
dataset = createRandomDataset(dims=(100, 100), nb_data_files=10, header=True)
w.setDataset(dataset)
w.show()
qapp.exec_()
def sigintHandler(*args):
"""Handler for the SIGINT signal."""
qt.QApplication.quit()