Dioptas/dioptas/controller/integration/ImageController.py

1091 lines
49 KiB
Python
Executable File

# -*- coding: utf-8 -*-
# Dioptas - GUI program for fast processing of 2D X-ray diffraction data
# Principal author: Clemens Prescher (clemens.prescher@gmail.com)
# Copyright (C) 2014-2019 GSECARS, University of Chicago, USA
# Copyright (C) 2015-2018 Institute for Geology and Mineralogy, University of Cologne, Germany
# Copyright (C) 2019-2020 DESY, Hamburg, Germany
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import numpy as np
from PIL import Image
from qtpy import QtWidgets, QtCore
import pyqtgraph as pg
from xypattern import Pattern
from ...widgets.UtilityWidgets import open_file_dialog, open_files_dialog, save_file_dialog
# imports for type hinting in PyCharm -- DO NOT DELETE
from ...widgets.integration import IntegrationWidget
from ...model.DioptasModel import DioptasModel
from ...model.util.HelperModule import get_partial_index, get_partial_value
from .EpicsController import EpicsController
class ImageController(object):
"""
The ImageController manages the Image actions in the Integration Window. It connects the file actions, as
well as interaction with the image_view.
"""
def __init__(self, widget: IntegrationWidget, dioptas_model: DioptasModel):
"""
:param widget: Reference to IntegrationWidget
:param dioptas_model: Reference to DioptasModel object
"""
self.widget = widget
self.model = dioptas_model
self.epics_controller = EpicsController(self.widget, self.model)
self.img_docked = True
self.view_mode = 'normal' # modes available: normal, alternative
self.roi_active = False
self.vertical_splitter_alternative_state = None
self.vertical_splitter_normal_state = None
self.horizontal_splitter_alternative_state = None
self.horizontal_splitter_normal_state = None
self.create_signals()
self.create_mouse_behavior()
def plot_img(self, auto_scale=None):
"""
Plots the current image loaded in self.img_data.
:param auto_scale:
Determines if intensities should be auto-scaled. If value is None it will use the parameter saved in the
Object (self._auto_scale)
"""
if auto_scale is None:
auto_scale = self.widget.img_autoscale_btn.isChecked()
if self.widget.integration_image_widget.show_background_subtracted_img_btn.isChecked():
self.widget.img_widget.plot_image(self.model.img_model.img_data, False)
else:
self.widget.img_widget.plot_image(self.model.img_model.raw_img_data, False)
if auto_scale:
self.widget.img_widget.auto_level()
def plot_cake(self, auto_scale=None):
"""
Plots the cake saved in the calibration data
:param auto_scale:
Determines if the intensity should be auto-scaled. If value is None it will use the parameter saved in the
object (self._auto_scale)
"""
if auto_scale is None:
auto_scale = self.widget.img_autoscale_btn.isChecked()
shift_amount = self.widget.cake_shift_azimuth_sl.value()
self.widget.cake_widget.plot_image(np.roll(self.model.cake_data, shift_amount, axis=0))
self.plot_cake_integral()
self.update_cake_axes_range()
if auto_scale:
self.widget.cake_widget.auto_level()
def plot_cake_integral(self, tth=None):
if not self.widget.cake_widget.cake_integral_plot.isVisible():
return
if tth is None:
tth = self.model.clicked_tth
x, y = self.model.calibration_model.cake_integral(
tth,
self.widget.integration_control_widget.integration_options_widget.cake_integral_width_sb.value()
)
shift_amount = self.widget.cake_shift_azimuth_sl.value()
self.widget.cake_widget.plot_cake_integral(x, np.roll(y, shift_amount))
def _update_cake_integral(self):
self.plot_cake_integral()
def save_cake_integral(self):
img_filename, _ = os.path.splitext(os.path.basename(self.model.img_model.filename))
filename = save_file_dialog(
self.widget, "Save Cake Integral Data.",
os.path.join(self.model.working_directories['pattern'],
img_filename + '.xy'))
if filename != '':
integral_pattern = Pattern(*self.widget.cake_widget.cake_integral_item.getData())
integral_pattern.save(filename)
def plot_mask(self):
"""
Plots the mask data.
"""
if self.model.use_mask and self.widget.img_mode == 'Image':
self.widget.img_widget.activate_mask()
self.widget.img_widget.plot_mask(self.model.mask_model.get_img())
else:
self.widget.img_widget.deactivate_mask()
def update_mask_transparency(self):
"""
Changes the colormap of the mask according to the transparency option selection in the GUI. Resulting Mask will
be either transparent or solid.
"""
self.model.transparent_mask = self.widget.mask_transparent_cb.isChecked()
if self.model.transparent_mask:
self.widget.img_widget.set_mask_color([255, 0, 0, 100])
else:
self.widget.img_widget.set_mask_color([255, 0, 0, 255])
def create_signals(self):
self.model.configuration_selected.connect(self.update_gui_from_configuration)
self.model.img_changed.connect(self.update_img_control_widget)
self.model.img_changed.connect(self.plot_img)
self.model.img_changed.connect(self.plot_mask)
"""
Creates all the connections of the GUI elements.
"""
self.widget.img_step_file_widget.next_btn.clicked.connect(self.load_next_img)
self.widget.img_step_file_widget.previous_btn.clicked.connect(self.load_previous_img)
self.widget.load_img_btn.clicked.connect(self.load_file)
self.widget.img_filename_txt.editingFinished.connect(self.filename_txt_changed)
self.widget.img_directory_txt.editingFinished.connect(self.directory_txt_changed)
self.widget.img_directory_btn.clicked.connect(self.img_directory_btn_click)
self.widget.img_step_series_widget.next_btn.clicked.connect(self.load_next_series_img)
self.widget.img_step_series_widget.previous_btn.clicked.connect(self.load_prev_series_img)
self.widget.img_step_series_widget.pos_txt.editingFinished.connect(self.load_series_img)
self.widget.file_info_btn.clicked.connect(self.show_file_info)
self.widget.img_step_file_widget.browse_by_name_rb.clicked.connect(self.set_iteration_mode_number)
self.widget.img_step_file_widget.browse_by_time_rb.clicked.connect(self.set_iteration_mode_time)
self.widget.image_control_widget.sources_cb.currentTextChanged.connect(self.select_source)
###
# Image widget image specific controls
self.widget.img_roi_btn.clicked.connect(self.click_roi_btn)
self.widget.img_mask_btn.clicked.connect(self.change_mask_mode)
self.widget.mask_transparent_cb.clicked.connect(self.update_mask_transparency)
###
# Image Widget cake specific controls
self.widget.img_phases_btn.clicked.connect(self.toggle_show_phases)
self.widget.cake_shift_azimuth_sl.valueChanged.connect(self.cake_shift_changed)
self.widget.integration_image_widget.cake_view.img_view_box.sigRangeChanged.connect(self.update_cake_axes_range)
self.widget.pattern_q_btn.clicked.connect(self.set_cake_axis_to_q)
self.widget.pattern_tth_btn.clicked.connect(self.set_cake_axis_to_2th)
self.widget.integration_control_widget.integration_options_widget.cake_integral_width_sb.valueChanged. \
connect(self._update_cake_integral)
self.widget.integration_control_widget.integration_options_widget.cake_integral_width_sb.editingFinished. \
connect(self._update_cake_integral)
self.widget.integration_control_widget.integration_options_widget.cake_save_integral_btn.clicked. \
connect(self.save_cake_integral)
###
# General Image Widget controls
self.widget.img_dock_btn.clicked.connect(self.img_dock_btn_clicked)
self.widget.img_autoscale_btn.clicked.connect(self.img_autoscale_btn_clicked)
self.widget.img_mode_btn.clicked.connect(self.change_view_mode)
self.widget.integration_image_widget.show_background_subtracted_img_btn.clicked.connect(
self.show_background_subtracted_img_btn_clicked)
self.widget.qa_save_img_btn.clicked.connect(self.save_img)
self.widget.load_calibration_btn.clicked.connect(self.load_calibration)
self.widget.set_wavelnegth_btn.clicked.connect(self.set_wavelength)
# signals
self.widget.change_view_btn.clicked.connect(self.change_view_btn_clicked)
self.widget.autoprocess_cb.toggled.connect(self.auto_process_cb_click)
def activate(self):
if self.widget.img_mode == 'Image':
if not self.model.img_changed.has_listener(self.plot_img):
self.model.img_changed.connect(self.plot_img)
if not self.model.img_changed.has_listener(self.plot_mask):
self.model.img_changed.connect(self.plot_mask)
elif self.widget.img_mode == 'Cake':
if not self.model.cake_changed.has_listener(self.plot_cake):
self.model.cake_changed.connect(self.plot_cake)
self.widget.calibration_lbl.setText(
self.model.calibration_model.calibration_name
)
self.widget.wavelength_lbl.setText(
"{:.4f}".format(self.model.calibration_model.wavelength * 1e10) + " A"
)
def update_image(self):
if self.widget.img_mode == 'Image':
self.plot_mask()
self.plot_img()
elif self.widget.img_mode == 'Cake':
self.plot_cake()
def deactivate(self):
if self.model.img_changed.has_listener(self.plot_img):
self.model.img_changed.disconnect(self.plot_img)
if self.plot_mask in self.model.img_changed.listeners:
self.model.img_changed.disconnect(self.plot_mask)
if self.plot_cake in self.model.cake_changed.listeners:
self.model.cake_changed.disconnect(self.plot_cake)
def create_mouse_behavior(self):
"""
Creates the signal connections of mouse interactions
"""
self.widget.img_widget.mouse_left_clicked.connect(self.img_mouse_click)
self.widget.img_widget.mouse_moved.connect(self.show_img_mouse_position)
self.widget.cake_widget.mouse_left_clicked.connect(self.img_mouse_click)
self.widget.cake_widget.mouse_moved.connect(self.show_img_mouse_position)
self.model.clicked_tth_changed.connect(self.set_cake_line_position)
self.model.clicked_tth_changed.connect(self.set_image_line_position)
def load_file(self, *args, **kwargs):
filename = kwargs.get('filename', None)
if filename is None:
filenames = open_files_dialog(self.widget, "Load image data file(s)",
self.model.working_directories['image'])
else:
filenames = [filename]
if filenames is not None and len(filenames) != 0:
self.model.working_directories['image'] = os.path.dirname(str(filenames[0]))
if len(filenames) == 1:
self.model.img_model.load(str(filenames[0]))
else:
if self.widget.img_batch_mode_add_rb.isChecked():
self.model.img_model.blockSignals(True)
self.model.img_model.load(str(filenames[0]))
for ind in range(1, len(filenames)):
self.model.img_model.add(filenames[ind])
self.model.img_model.blockSignals(False)
self.model.img_model.img_changed.emit()
if self.widget.img_batch_mode_average_rb.isChecked():
self.model.img_model.blockSignals(True)
self.model.img_model.load(str(filenames[0]))
for ind in range(1, len(filenames)):
self.model.img_model.add(filenames[ind])
self.model.img_model._img_data = self.model.img_model._img_data / len(filenames)
self.model.img_model._calculate_img_data()
self.model.img_model.blockSignals(False)
self.model.img_model.img_changed.emit()
elif self.widget.img_batch_mode_integrate_rb.isChecked():
self._load_multiple_files(filenames)
elif self.widget.img_batch_mode_image_save_rb.isChecked():
self._save_multiple_image_files(filenames)
def _load_multiple_files(self, filenames):
if not self.model.calibration_model.is_calibrated:
self.widget.show_error_msg("Can not integrate multiple images without calibration.")
return
working_directory = self._get_pattern_working_directory()
if working_directory == '':
return # abort file processing if no directory was selected
progress_dialog = self.widget.get_progress_dialog("Integrating multiple files.", "Abort Integration",
len(filenames))
self._set_up_batch_processing()
for ind in range(len(filenames)):
filename = str(filenames[ind])
base_filename = os.path.basename(filename)
progress_dialog.setValue(ind)
progress_dialog.setLabelText("Integrating: " + base_filename)
self.model.img_model.blockSignals(True)
self.model.img_model.load(filename)
self.model.img_model.blockSignals(False)
x, y = self.integrate_pattern()
self._save_pattern(base_filename, working_directory, x, y)
QtWidgets.QApplication.processEvents()
if progress_dialog.wasCanceled():
break
progress_dialog.close()
self._tear_down_batch_processing()
def _get_pattern_working_directory(self):
if self.widget.pattern_autocreate_cb.isChecked():
working_directory = self.model.working_directories['pattern']
else:
# if there is no working directory selected A file dialog opens up to choose a directory...
working_directory = str(QtWidgets.QFileDialog.getExistingDirectory(
self.widget, "Please choose the output directory for the integrated Patterns.",
self.model.working_directories['pattern']))
return working_directory
def _set_up_batch_processing(self):
self.model.blockSignals(True)
def _tear_down_batch_processing(self):
self.model.blockSignals(False)
self.model.img_changed.emit()
self.model.pattern_changed.emit()
def _save_multiple_image_files(self, filenames):
working_directory = str(QtWidgets.QFileDialog.getExistingDirectory(
self.widget, "Please choose the output directory for the Images.",
self.model.working_directories['image']))
if working_directory == '':
return
self._set_up_batch_processing()
progress_dialog = self.widget.get_progress_dialog("Saving multiple image files.", "Abort",
len(filenames))
QtWidgets.QApplication.processEvents()
self.model.current_configuration.auto_integrate_pattern = False
for ind, filename in enumerate(filenames):
base_filename = os.path.basename(filename)
progress_dialog.setValue(ind)
progress_dialog.setLabelText("Saving: " + base_filename)
self.model.img_model.load(str(filename))
self.save_img(os.path.join(working_directory, 'batch_' + base_filename))
QtWidgets.QApplication.processEvents()
if progress_dialog.wasCanceled():
break
self.model.current_configuration.auto_integrate_pattern = True
progress_dialog.close()
self._tear_down_batch_processing()
def _save_pattern(self, base_filename, working_directory, x, y):
file_endings = self._get_pattern_file_endings()
for file_ending in file_endings:
filename = os.path.join(working_directory, os.path.splitext(base_filename)[0] + file_ending)
self.model.pattern_model.set_pattern(x, y, filename, unit=self.get_integration_unit())
if file_ending == '.xy':
self.model.pattern_model.save_pattern(filename, header=self._create_pattern_header())
else:
self.model.pattern_model.save_pattern(filename)
# save the background subtracted filename
if self.model.pattern.auto_bkg is not None:
directory = os.path.join(working_directory, 'bkg_subtracted')
if not os.path.exists(directory):
os.mkdir(directory)
filename = os.path.join(directory, self.model.pattern.name + file_ending)
if file_ending == '.xy':
self.model.pattern_model.save_pattern(filename, header=self._create_pattern_header(),
subtract_background=True)
else:
self.model.pattern_model.save_pattern(filename, subtract_background=True)
def _create_pattern_header(self):
header = self.model.calibration_model.create_file_header()
header = header.replace('\r\n', '\n')
header += '\n#\n# ' + self.model.pattern_model.unit + '\t I'
return header
def _get_pattern_file_endings(self):
res = []
if self.widget.pattern_header_xy_cb.isChecked():
res.append('.xy')
if self.widget.pattern_header_chi_cb.isChecked():
res.append('.chi')
if self.widget.pattern_header_dat_cb.isChecked():
res.append('.dat')
return res
def show_file_info(self):
self.widget.file_info_widget.raise_widget()
def get_integration_unit(self):
if self.widget.pattern_tth_btn.isChecked():
return '2th_deg'
elif self.widget.pattern_q_btn.isChecked():
return 'q_A^-1'
elif self.widget.pattern_d_btn.isChecked():
return 'd_A'
def integrate_pattern(self):
if self.widget.img_mask_btn.isChecked():
mask = self.model.mask_model.get_mask()
else:
mask = None
if self.widget.img_roi_btn.isChecked():
roi_mask = self.widget.img_widget.roi.getRoiMask(self.model.img_data.shape)
else:
roi_mask = None
if roi_mask is None and mask is None:
mask = None
elif roi_mask is None and mask is not None:
mask = mask
elif roi_mask is not None and mask is None:
mask = roi_mask
elif roi_mask is not None and mask is not None:
mask = np.logical_or(mask, roi_mask)
if self.widget.pattern_tth_btn.isChecked():
integration_unit = '2th_deg'
elif self.widget.pattern_q_btn.isChecked():
integration_unit = 'q_A^-1'
elif self.widget.pattern_d_btn.isChecked():
integration_unit = 'd_A'
else:
# in case something weird happened
print('No correct integration unit selected')
return
if not self.widget.automatic_binning_cb.isChecked():
num_points = int(str(self.widget.bin_count_txt.text()))
else:
num_points = None
return self.model.calibration_model.integrate_1d(mask=mask, unit=integration_unit, num_points=num_points)
def change_mask_mode(self):
self.model.use_mask = self.widget.integration_image_widget.mask_btn.isChecked()
self.widget.mask_transparent_cb.setVisible(self.model.use_mask)
self.plot_mask()
self.model.img_model.img_changed.emit()
def update_mask_mode(self):
self.widget.integration_image_widget.mask_btn.setChecked(bool(self.model.use_mask))
self.widget.mask_transparent_cb.setVisible(bool(self.model.use_mask))
self.widget.mask_transparent_cb.setChecked(bool(self.model.transparent_mask))
def update_img_mode(self):
self.widget.img_mode_btn.click()
def load_series_img(self):
pos = int(str(self.widget.img_step_series_widget.pos_txt.text()))
self.model.img_model.load_series_img(pos)
def load_prev_series_img(self):
step = int(str(self.widget.img_step_series_widget.step_txt.text()))
pos = int(str(self.widget.img_step_series_widget.pos_txt.text()))
self.model.img_model.load_series_img(pos - step)
def load_next_series_img(self):
step = int(str(self.widget.img_step_series_widget.step_txt.text()))
pos = int(str(self.widget.img_step_series_widget.pos_txt.text()))
self.model.img_model.load_series_img(pos + step)
def load_next_img(self):
step = int(str(self.widget.img_step_file_widget.step_txt.text()))
self.model.img_model.load_next_file(step=step)
def load_previous_img(self):
step = int(str(self.widget.img_step_file_widget.step_txt.text()))
self.model.img_model.load_previous_file(step=step)
def filename_txt_changed(self):
current_filename = os.path.basename(self.model.img_model.filename)
current_directory = str(self.widget.img_directory_txt.text())
new_filename = str(self.widget.img_filename_txt.text())
if os.path.exists(os.path.join(current_directory, new_filename)):
try:
self.load_file(filename=os.path.join(current_directory, new_filename).replace('\\', '/'))
except TypeError:
self.widget.img_filename_txt.setText(current_filename)
else:
self.widget.img_filename_txt.setText(current_filename)
def directory_txt_changed(self):
new_directory = str(self.widget.img_directory_txt.text())
if os.path.exists(new_directory) and new_directory != self.model.working_directories['image']:
if self.model.img_model.autoprocess:
self._files_now = dict([(f, None) for f in os.listdir(self.model.working_directories['image'])])
self.model.working_directories['image'] = os.path.abspath(new_directory)
old_filename = str(self.widget.img_filename_txt.text())
self.widget.img_filename_txt.setText(old_filename + '*')
else:
self.widget.img_directory_txt.setText(self.model.working_directories['image'])
def img_directory_btn_click(self):
directory = str(QtWidgets.QFileDialog.getExistingDirectory(
self.widget,
"Please choose the image working directory.",
self.model.working_directories['image']))
if directory != '':
if self.model.img_model.autoprocess:
self._files_now = dict([(f, None) for f in os.listdir(self.model.working_directories['image'])])
self.model.working_directories['image'] = directory
self.widget.img_directory_txt.setText(directory)
def update_img_control_widget(self):
self.widget.img_step_series_widget.setVisible(int(self.model.img_model.series_max > 1))
self.widget.img_step_series_widget.pos_validator.setTop(self.model.img_model.series_max)
self.widget.img_step_series_widget.pos_txt.setText(str(self.model.img_model.series_pos))
self.widget.file_info_btn.setVisible(self.model.img_model.file_info != "")
self.widget.move_btn.setVisible(len(self.model.img_model.motors_info) > 0)
self.widget.img_filename_txt.setText(os.path.basename(self.model.img_model.filename))
self.widget.img_directory_txt.setText(os.path.dirname(self.model.img_model.filename))
self.widget.file_info_widget.text_lbl.setText(self.model.img_model.file_info)
self.widget.image_control_widget.sources_widget.setVisible(not (self.model.img_model.sources is None))
if self.model.img_model.sources is not None:
sources_cb = self.widget.image_control_widget.sources_cb
sources_cb.blockSignals(True)
# remove all previous items:
for _ in range(sources_cb.count()):
sources_cb.removeItem(0)
sources_cb.addItems(self.model.img_model.sources)
sources_cb.setCurrentText(self.model.img_model.selected_source)
sources_cb.blockSignals(False)
self.widget.cbn_plot_btn.setText('Plot')
self.widget.oiadac_plot_btn.setText('Plot')
# update the window due to some errors on mac when using macports
self._get_master_parent().update()
def _get_master_parent(self):
master_widget_parent = self.widget
while master_widget_parent.parent():
master_widget_parent = master_widget_parent.parent()
return master_widget_parent
def click_roi_btn(self):
if self.model.current_configuration.roi is None:
self.model.current_configuration.roi = self.widget.img_widget.roi.getRoiLimits()
else:
self.model.current_configuration.roi = None
self.update_roi_in_gui()
def update_roi_in_gui(self):
roi = self.model.mask_model.roi
if roi is None:
self.widget.img_widget.deactivate_roi()
self.widget.img_roi_btn.setChecked(False)
if self.roi_active:
self.widget.img_widget.roi.sigRegionChangeFinished.disconnect(self.update_roi_in_model)
self.roi_active = False
return
if not self.model.current_configuration.auto_integrate_cake:
self.widget.img_roi_btn.setChecked(True)
self.widget.img_widget.activate_roi()
self.widget.img_widget.update_roi_shade_limits(self.model.img_data.shape)
pos = QtCore.QPoint(int(roi[2]), int(roi[0]))
size = QtCore.QPoint(int(roi[3] - roi[2]), int(roi[1] - roi[0]))
self.widget.img_widget.roi.setRoiLimits(pos, size)
if not self.roi_active:
self.widget.img_widget.roi.sigRegionChangeFinished.connect(self.update_roi_in_model)
self.roi_active = True
def update_roi_in_model(self):
self.model.current_configuration.roi = self.widget.img_widget.roi.getRoiLimits()
def change_view_mode(self):
if str(self.widget.img_mode_btn.text()) == 'Cake':
self.activate_cake_mode()
elif str(self.widget.img_mode_btn.text()) == 'Image':
self.activate_image_mode()
def toggle_show_phases(self):
if str(self.widget.img_phases_btn.text()) == 'Show Phases':
self.widget.integration_image_widget.cake_view.show_all_visible_cake_phases(
self.widget.phase_widget.phase_show_cbs)
self.widget.img_phases_btn.setText('Hide Phases')
self.model.enabled_phases_in_cake.emit()
elif str(self.widget.img_phases_btn.text()) == 'Hide Phases':
self.widget.integration_image_widget.cake_view.hide_all_cake_phases()
self.widget.img_phases_btn.setText('Show Phases')
def cake_shift_changed(self):
self.plot_cake()
self._update_cake_mouse_click_pos()
self.update_cake_azimuth_axis()
self.plot_cake_integral(None)
def activate_cake_mode(self):
if self.model.calibration_model.cake_geometry is None:
self.widget.show_error_msg("Can not switch to the cake mode without calibration.")
return
if not self.model.current_configuration.auto_integrate_cake:
self.model.current_configuration.auto_integrate_cake = True
self.model.current_configuration.integrate_image_2d()
self.set_cake_line_position(self.model.clicked_tth)
self._update_cake_mouse_click_pos()
self.widget.img_mode_btn.setText('Image')
self.widget.img_mode = str("Cake")
self.model.img_changed.disconnect(self.plot_img)
self.model.img_changed.disconnect(self.plot_mask)
self.model.cake_changed.connect(self.plot_cake)
self.plot_cake()
self.widget.cake_shift_azimuth_sl.setVisible(True)
self.widget.cake_shift_azimuth_sl.setMinimum(int(-len(self.model.cake_azi) / 2))
self.widget.cake_shift_azimuth_sl.setMaximum(int(len(self.model.cake_azi) / 2))
self.widget.cake_shift_azimuth_sl.setSingleStep(1)
self.widget.img_phases_btn.setVisible(True)
self.widget.integration_image_widget.img_pg_layout.hide()
self.widget.integration_image_widget.cake_pg_layout.show()
def activate_image_mode(self):
if self.model.current_configuration.auto_integrate_cake:
self.model.current_configuration.auto_integrate_cake = False
self.widget.cake_shift_azimuth_sl.setVisible(False)
self.widget.img_phases_btn.setVisible(False)
self._update_image_line_pos()
self._update_image_mouse_click_pos()
self.widget.img_mode_btn.setText('Cake')
self.widget.img_mode = str("Image")
self.model.img_changed.connect(self.plot_img)
self.model.img_changed.connect(self.plot_mask)
self.model.cake_changed.disconnect(self.plot_cake)
self.plot_img()
self.plot_mask()
self.widget.integration_image_widget.img_pg_layout.show()
self.widget.integration_image_widget.cake_pg_layout.hide()
def img_autoscale_btn_clicked(self):
if self.widget.img_autoscale_btn.isChecked():
self.widget.img_widget.auto_level()
def img_dock_btn_clicked(self):
self.img_docked = not self.img_docked
self.widget.dock_img(self.img_docked)
def show_background_subtracted_img_btn_clicked(self):
if self.widget.img_mode_btn.text() == 'Cake':
self.plot_img()
else:
self.widget.integration_image_widget.show_background_subtracted_img_btn.setChecked(False)
def _update_cake_mouse_click_pos(self):
if not self.model.calibration_model.is_calibrated:
return
tth = self.model.clicked_tth
azi = self.model.clicked_azi
cake_tth = self.model.cake_tth
x_pos = get_partial_index(cake_tth, tth)
if x_pos is None:
return
x_pos = x_pos + 0.5
shift_amount = self.widget.cake_shift_azimuth_sl.value()
y_pos = (get_partial_index(self.model.cake_azi, azi) + 0.5 + shift_amount) % len(self.model.cake_azi)
self.widget.cake_widget.set_mouse_click_position(x_pos, y_pos)
def _update_image_line_pos(self):
if not self.model.calibration_model.is_calibrated:
return
self.set_image_line_position(self.model.clicked_tth)
def _update_image_mouse_click_pos(self):
if not self.model.calibration_model.is_calibrated:
return
tth = np.deg2rad(self.model.clicked_tth)
azi = np.deg2rad(self.model.clicked_azi)
new_pos = self.model.calibration_model.get_pixel_ind(tth, azi)
if len(new_pos) == 0:
self.widget.img_widget.mouse_click_item.hide()
else:
x_ind, y_ind = new_pos
self.widget.img_widget.set_mouse_click_position(y_ind + 0.5, x_ind + 0.5)
self.widget.img_widget.mouse_click_item.show()
def update_cake_axes_range(self):
if self.model.current_configuration.auto_integrate_cake:
self.update_cake_azimuth_axis()
self.update_cake_x_axis()
def update_cake_azimuth_axis(self):
img_view_rect = self.widget.integration_image_widget.cake_view.img_view_rect()
img_bounding_rect = self.widget.integration_image_widget.cake_view.img_bounding_rect()
shift_amount = self.widget.cake_shift_azimuth_sl.value()
cake_azi = self.model.cake_azi - shift_amount * np.mean(np.diff(self.model.cake_azi))
if img_bounding_rect.height() == 0:
return
height = img_view_rect.height()
bottom = img_view_rect.top()
v_scale = (cake_azi[-1] - cake_azi[0]) / img_bounding_rect.height()
v_shift = np.min(cake_azi[0])
min_azi = v_scale * bottom + v_shift
max_azi = v_scale * (bottom + height) + v_shift
self.widget.integration_image_widget.cake_view.left_axis_cake.setRange(min_azi, max_azi)
def update_cake_x_axis(self):
if self.model.cake_tth is None:
return
img_view_rect = self.widget.integration_image_widget.cake_view.img_view_rect()
img_bounding_rect = self.widget.integration_image_widget.cake_view.img_bounding_rect()
cake_tth = self.model.cake_tth
if img_bounding_rect.width() == 0:
return
width = img_view_rect.width()
left = img_view_rect.left()
h_scale = (np.max(cake_tth) - np.min(cake_tth)) / img_bounding_rect.width()
h_shift = np.min(cake_tth)
min_tth = h_scale * left + h_shift
max_tth = h_scale * (left + width) + h_shift
if self.model.current_configuration.integration_unit == '2th_deg':
self.widget.integration_image_widget.cake_view.bottom_axis_cake.setRange(min_tth, max_tth)
elif self.model.current_configuration.integration_unit == 'q_A^-1':
self.widget.integration_image_widget.cake_view.bottom_axis_cake.setRange(
self.convert_x_value(min_tth, '2th_deg', 'q_A^-1'),
self.convert_x_value(max_tth, '2th_deg', 'q_A^-1'))
def set_cake_axis_to_2th(self):
self.widget.integration_image_widget.cake_view.bottom_axis_cake.setLabel(u'', u'°')
self.update_cake_x_axis()
def set_cake_axis_to_q(self):
self.widget.integration_image_widget.cake_view.bottom_axis_cake.setLabel('Q', 'A<sup>-1</sup>')
self.update_cake_x_axis()
def show_img_mouse_position(self, x, y):
if self.widget.img_mode == 'Cake':
img_data = self.widget.cake_widget.img_data
else:
img_data = self.widget.img_widget.img_data
img_shape = img_data.shape
if 0 < x < img_shape[1] - 1 and 0 < y < img_shape[0] - 1:
self.update_mouse_position_labels(x, y, img_data[int(np.floor(y)), int(np.floor(x))])
else:
self.update_mouse_position_labels(x, y, None)
if 0.5 < x < img_shape[1] - 0.5 and 0.5 < y < img_shape[0] - 0.5:
if self.model.calibration_model.is_calibrated:
x_temp = x
x = np.array([y])
y = np.array([x_temp])
if self.widget.img_mode == 'Cake':
tth = get_partial_value(self.model.cake_tth, y - 0.5)
shift_amount = self.widget.cake_shift_azimuth_sl.value()
cake_azi = self.model.cake_azi - shift_amount * np.mean(np.diff(self.model.cake_azi))
azi = get_partial_value(cake_azi, x - 0.5)
q_value = self.convert_x_value(tth, '2th_deg', 'q_A^-1')
else:
tth = self.model.calibration_model.get_two_theta_img(x, y)
tth = tth / np.pi * 180.0
q_value = self.convert_x_value(tth, '2th_deg', 'q_A^-1')
azi = self.model.calibration_model.get_azi_img(x, y) / np.pi * 180
d = self.convert_x_value(tth, '2th_deg', 'd_A')
tth_str = u"2θ:%9.3f " % tth
self.widget.mouse_tth_lbl.setText(tth_str)
self.widget.mouse_d_lbl.setText('d:%9.3f ' % d)
self.widget.mouse_q_lbl.setText('Q:%9.3f ' % q_value)
self.widget.mouse_azi_lbl.setText('X:%9.3f ' % azi)
self.widget.img_widget_mouse_tth_lbl.setText(tth_str)
self.widget.img_widget_mouse_d_lbl.setText('d:%9.3f ' % d)
self.widget.img_widget_mouse_q_lbl.setText('Q:%9.3f ' % q_value)
self.widget.img_widget_mouse_azi_lbl.setText('X:%9.3f ' % azi)
else:
self.widget.mouse_tth_lbl.setText(u'2θ: -')
self.widget.mouse_d_lbl.setText('d: -')
self.widget.mouse_q_lbl.setText('Q: -')
self.widget.mouse_azi_lbl.setText('X: -')
self.widget.img_widget_mouse_tth_lbl.setText(u'2θ: -')
self.widget.img_widget_mouse_d_lbl.setText('d: -')
self.widget.img_widget_mouse_q_lbl.setText('Q: -')
self.widget.img_widget_mouse_azi_lbl.setText('X: -')
def img_mouse_click(self, x, y):
if self.widget.img_mode == 'Cake':
img_data = self.widget.cake_widget.img_data
else:
img_data = self.widget.img_widget.img_data
if 0 < x < img_data.shape[1] - 1 and 0 < y < img_data.shape[0] - 1:
intensity = img_data[int(np.floor(y)), int(np.floor(x))]
else:
intensity = None
self.update_mouse_click_position_labels(x, y, intensity)
if self.model.calibration_model.is_calibrated:
x, y = y, x # the indices are reversed for the img_array
if self.widget.img_mode == 'Cake': # cake mode
# get clicked tth and azimuth
cake_shape = self.model.cake_data.shape
if x < 0 or y < 0 or x > cake_shape[0] - 1 or y > cake_shape[1] - 1:
return
tth = get_partial_value(self.model.cake_tth, y - 0.5)
shift_amount = self.widget.cake_shift_azimuth_sl.value()
azi = get_partial_value(np.roll(self.model.cake_azi, shift_amount), x - 0.5)
self.widget.cake_widget.activate_vertical_line()
elif self.widget.img_mode == 'Image': # image mode
img_shape = self.model.img_data.shape
if x < 0 or y < 0 or x > img_shape[0] - 1 or y > img_shape[1] - 1:
return
x = np.array([x])
y = np.array([y])
tth = np.rad2deg(self.model.calibration_model.get_two_theta_img(x, y))
azi = np.rad2deg(self.model.calibration_model.get_azi_img(x, y))
else: # in the case of whatever
tth = 0
azi = 0
self.clicked_tth = tth # in degree
self.clicked_azi = azi # in degree
if self.widget.img_mode == 'Cake':
self.plot_cake_integral()
self.model.clicked_tth_changed.emit(tth)
self.model.clicked_azi_changed.emit(azi)
self.widget.click_tth_lbl.setText(self.widget.mouse_tth_lbl.text())
self.widget.click_d_lbl.setText(self.widget.mouse_d_lbl.text())
self.widget.click_q_lbl.setText(self.widget.mouse_q_lbl.text())
self.widget.click_azi_lbl.setText(self.widget.mouse_azi_lbl.text())
self.widget.img_widget_click_tth_lbl.setText(self.widget.mouse_tth_lbl.text())
self.widget.img_widget_click_d_lbl.setText(self.widget.mouse_d_lbl.text())
self.widget.img_widget_click_q_lbl.setText(self.widget.mouse_q_lbl.text())
self.widget.img_widget_click_azi_lbl.setText(self.widget.mouse_azi_lbl.text())
def update_mouse_position_labels(self, x, y, intensity):
x_pos_string = 'X: %4d' % x
y_pos_string = 'Y: %4d' % y
try:
int_string = 'I: %5d' % intensity
except (ValueError, TypeError, OverflowError):
int_string = 'I:'
self.widget.mouse_x_lbl.setText(x_pos_string)
self.widget.mouse_y_lbl.setText(y_pos_string)
self.widget.mouse_int_lbl.setText(int_string)
def update_mouse_click_position_labels(self, x, y, intensity):
self.update_mouse_position_labels(x, y, intensity)
self.widget.click_x_lbl.setText(self.widget.mouse_x_lbl.text())
self.widget.click_y_lbl.setText(self.widget.mouse_y_lbl.text())
self.widget.click_int_lbl.setText(self.widget.mouse_int_lbl.text())
def set_cake_line_position(self, tth):
if self.model.cake_tth is None:
return
pos = get_partial_index(self.model.cake_tth, tth)
if pos is None:
self.widget.cake_widget.plot_cake_integral(np.array([]), np.array([]))
self.widget.cake_widget.deactivate_vertical_line()
return
self.widget.cake_widget.vertical_line.setValue(pos + 0.5)
self.widget.cake_widget.activate_vertical_line()
self.plot_cake_integral(tth)
def set_image_line_position(self, tth):
if not self.model.calibration_model.is_calibrated:
self.widget.img_widget.deactivate_circle_scatter()
return
self.widget.img_widget.activate_circle_scatter()
self.widget.img_widget.set_circle_line(self.model.calibration_model.get_two_theta_array(), np.deg2rad(tth))
def set_iteration_mode_number(self):
self.model.img_model.set_file_iteration_mode('number')
def set_iteration_mode_time(self):
self.model.img_model.set_file_iteration_mode('time')
def select_source(self, source):
self.model.img_model.select_source(source)
def convert_x_value(self, value, previous_unit, new_unit):
wavelength = self.model.calibration_model.wavelength
if previous_unit == '2th_deg':
tth = value
elif previous_unit == 'q_A^-1':
tth = np.arcsin(
value * 1e10 * wavelength / (4 * np.pi)) * 360 / np.pi
elif previous_unit == 'd_A':
tth = 2 * np.arcsin(wavelength / (2 * value * 1e-10)) * 180 / np.pi
else:
tth = 0
if new_unit == '2th_deg':
res = tth
elif new_unit == 'q_A^-1':
res = 4 * np.pi * \
np.sin(tth / 360 * np.pi) / \
wavelength / 1e10
elif new_unit == 'd_A':
res = wavelength / (2 * np.sin(tth / 360 * np.pi)) * 1e10
else:
res = 0
return res
def load_calibration(self):
filename = open_file_dialog(
self.widget, "Load calibration...",
self.model.working_directories['calibration'],
'*.poni')
if filename != '':
self.model.working_directories['calibration'] = os.path.dirname(filename)
self.model.calibration_model.load(filename)
self.widget.calibration_lbl.setText(
self.model.calibration_model.calibration_name)
self.widget.wavelength_lbl.setText('{:.4f}'.format(self.model.calibration_model.wavelength * 1e10) + ' A')
self.model.img_model.img_changed.emit()
def set_wavelength(self):
wavelength, ok = QtWidgets.QInputDialog.getText(self.widget, 'Set Wavelength', 'Wavelength in Angstroms:')
if ok:
self.model.calibration_model.pattern_geometry.wavelength = float(wavelength) * 1e-10
self.widget.wavelength_lbl.setText('{:.4f}'.format(self.model.calibration_model.wavelength * 1e10) + ' A')
self.model.img_model.img_changed.emit()
def auto_process_cb_click(self):
self.model.img_model.autoprocess = self.widget.autoprocess_cb.isChecked()
def save_img(self, filename=None):
if not filename:
img_filename = os.path.splitext(os.path.basename(self.model.img_model.filename))[0]
filename = save_file_dialog(self.widget, "Save Image.",
os.path.join(self.model.working_directories['image'],
img_filename + '.png'),
('Image (*.png);;Data (*.tiff);;Text (*.txt)'))
if filename != '':
if filename.endswith('.png'):
if self.widget.img_mode == 'Cake':
self.widget.cake_widget.deactivate_vertical_line()
self.widget.cake_widget.deactivate_mouse_click_item()
QtWidgets.QApplication.processEvents()
self.widget.cake_widget.save_img(filename)
self.widget.cake_widget.activate_vertical_line()
self.widget.cake_widget.activate_mouse_click_item()
elif self.widget.img_mode == 'Image':
self.widget.img_widget.deactivate_circle_scatter()
self.widget.img_widget.deactivate_roi()
QtWidgets.QApplication.processEvents()
self.widget.img_widget.save_img(filename)
self.widget.img_widget.activate_circle_scatter()
if self.roi_active:
self.widget.img_widget.activate_roi()
elif filename.endswith('.tiff') or filename.endswith('.tif'):
if self.widget.img_mode == 'Image':
im_array = np.int32(self.model.img_data)
elif self.widget.img_mode == 'Cake':
im_array = np.int32(self.model.cake_data)
im_array = np.flipud(im_array)
im = Image.fromarray(im_array)
im.save(filename)
elif filename.endswith('.txt') or filename.endswith('.csv'):
if self.widget.img_mode == 'Image':
return
elif self.widget.img_mode == 'Cake': # saving cake data as a text file for export.
with open(filename, 'w') as out_file: # this is done in an odd and slow way because the headers
# should be floats and the data itself int.
cake_tth = np.insert(self.model.cake_tth, 0, 0)
np.savetxt(out_file, cake_tth[None], fmt='%6.3f')
for azi, row in zip(self.model.cake_azi, self.model.cake_data):
row_str = " ".join(["{:6.0f}".format(el) for el in row])
out_file.write("{:6.2f}".format(azi) + row_str + '\n')
def update_gui_from_configuration(self):
self.widget.img_mask_btn.setChecked(int(self.model.use_mask))
self.widget.mask_transparent_cb.setChecked(bool(self.model.transparent_mask))
self.widget.autoprocess_cb.setChecked(bool(self.model.img_model.autoprocess))
self.widget.calibration_lbl.setText(self.model.calibration_model.calibration_name)
self.update_img_control_widget()
self.update_mask_mode()
self.update_roi_in_gui()
if self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Image':
self.activate_cake_mode()
elif not self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Cake':
self.activate_image_mode()
elif self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Cake':
self.set_cake_line_position(self.model.clicked_tth)
self._update_cake_mouse_click_pos()
elif not self.model.current_configuration.auto_integrate_cake and self.widget.img_mode == 'Image':
self._update_image_line_pos()
self._update_image_mouse_click_pos()
def change_view_btn_clicked(self):
if self.view_mode == 'alternative':
self.change_view_to_normal()
elif self.view_mode == 'normal':
self.change_view_to_alternative()
def change_view_to_normal(self):
if self.view_mode == 'normal':
return
self.vertical_splitter_alternative_state = self.widget.vertical_splitter.saveState()
self.horizontal_splitter_alternative_state = self.widget.horizontal_splitter.saveState()
self.widget.vertical_splitter.addWidget(self.widget.integration_pattern_widget)
self.widget.integration_control_widget.setOrientation(QtCore.Qt.Horizontal)
if self.vertical_splitter_normal_state:
self.widget.vertical_splitter.restoreState(self.vertical_splitter_normal_state)
if self.horizontal_splitter_normal_state:
self.widget.horizontal_splitter.restoreState(self.horizontal_splitter_normal_state)
self.widget.img_widget.set_orientation("horizontal")
self.view_mode = 'normal'
def change_view_to_alternative(self):
if self.view_mode == 'alternative':
return
self.vertical_splitter_normal_state = self.widget.vertical_splitter.saveState()
self.horizontal_splitter_normal_state = self.widget.horizontal_splitter.saveState()
self.widget.vertical_splitter_left.insertWidget(0, self.widget.integration_pattern_widget)
self.widget.integration_control_widget.setOrientation(QtCore.Qt.Vertical)
if self.vertical_splitter_alternative_state:
self.widget.vertical_splitter.restoreState(self.vertical_splitter_alternative_state)
if self.horizontal_splitter_alternative_state:
self.widget.horizontal_splitter.restoreState(self.horizontal_splitter_alternative_state)
self.widget.img_widget.set_orientation("vertical")
self.view_mode = 'alternative'