#! python
# -*- coding: utf-8 -*-
"""Main window of visual simulator.
Authors:
* Victor Hugo de Mello Pessoa <victor.pessoa@usp.br>
* Daniel Cosmo Pizetta <daniel.pizetta@usp.br>
Since:
2017/08/01
Todo:
Replaning this module, divide and make it more simple.
"""
import imp
import json
import logging
import os
from glob import glob
import h5py
import numpy as np
import pyqtgraph as pg
from pyqtgraph import opengl as gl
from pyqtgraph import parametertree as pt
from pyqtgraph.Qt import QtCore, QtGui
import mrsprint
from bloch.simulator import evolve
from mrsprint.data import DataEditor
from mrsprint.gui.mw_gradient import Ui_Gradient
from mrsprint.gui.mw_mrsprint import Ui_MainWindow
from mrsprint.settings import Settings
from mrsprint.simulator import create_positions, frequency_shift
from mrsprint.simulator.plot import Plot, plot_item
from mrsprint.subject.sample import Nucleus, Sample, SampleElement
from mrsprint.system.magnet import Magnet
_logger = logging.getLogger(__name__)
def _partialItemToPath(func, *args, **keywords):
"""Return path = QListWidgetItem.toolTip()."""
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + (fargs[0].toolTip(),) + fargs[1:]), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
def _color_settings_2d(rgb, value, opacity, colored):
"""Return background color to color table item.
Args:
rgb (list(float)): List of minimum values of each color in rgb ([r, g, b] from 0. to 1.).
value (float): Value of data (from 0. to 1.).
opacity (float): Value of opacity (from 0. to 1.).
colored (bool): If colored or grayscale.
Returns:
QtGui.QBrush: Color.
"""
# Grayscale
color = QtGui.QBrush(QtGui.QColor(int(255 * (0.6 * value)),
int(255 * (0.6 * value)),
int(255 * (0.6 * value)),
int(255 * .5 * opacity)), QtCore.Qt.SolidPattern)
if colored:
color = QtGui.QBrush(QtGui.QColor(int(255 * (rgb[0] + 0.6 * value)),
int(255 * (rgb[1] + 0.6 * value)),
int(255 * (rgb[2] + 0.6 * value)),
int(255 * .5 * opacity)), QtCore.Qt.SolidPattern)
return color
def _color_settings_3d(rgb, value, opacity, colored):
"""Return color to color 3D view item.
Args:
rgb (list(float)): List of minimum values of each color in rgb ([r, g, b] from 0. to 1.).
value (float): Value of data (from 0. to 1.).
opacity (float): Value of opacity(from 0. to 1.).
colored (bool): If colored or grayscale.
Returns:
tuple(float): Color in (r, g, b, a)
"""
# Grayscale
color = (1. + 0.6 * value,
1. + 0.6 * value,
1. + 0.6 * value,
0.5 * opacity)
if colored:
color = (rgb[0] + 0.6 * value,
rgb[1] + 0.6 * value,
rgb[2] + 0.6 * value,
0.5 * opacity)
return color
def _get_element_3d_view(position, element_size, size):
"""Return a 3D faced box item at position x, y, z.
Args:
position (list(int)): List of the indexes of the item.
element_size (list(float)): List of the dimensions of the item.
size (list(float)): List of the dimensions of the whole sample.
Returns:
gl.GLMeshItem: 3D item.
"""
x, y, z = position
dx, dy, dz = element_size
lx, ly, lz = size
# translated position
x, y, z = x * dx - lx / 2, y * dy - ly / 2, z * dz - lz / 2
# Vertex
v = np.array([[x, y, z],
[x + dx, y, z],
[x, y + dy, z],
[x + dx, y + dy, z],
[x, y, z + dz],
[x + dx, y, z + dz],
[x, y + dy, z + dz],
[x + dx, y + dy, z + dz]])
# Faces
f = np.array([[0, 1, 2], [1, 2, 3], [0, 1, 4],
[1, 4, 5], [0, 2, 4], [2, 4, 6],
[1, 3, 5], [3, 5, 7], [2, 3, 6],
[3, 6, 7], [4, 5, 6], [5, 6, 7]])
data = gl.MeshData(vertexes=v, faces=f)
item = gl.GLMeshItem(meshdata=data)
item.setGLOptions(opts="translucent")
return item
[docs]class MainWindow(QtGui.QMainWindow):
"""Main window.
About load methods:
This method should exist for each type of context that could be edited
in the 2D editor and 3D view. Ex. loadSystemParameters. It should be
responsible for loading values from parameter tree, connect signals -
to respective own actions, and set data.To keep it isolated, it should
set data, and when data is changed, data triggers the GUI update.
And the reverse mode.
"""
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
# Useful definitions
red = [0.4, 0.2, 0.2]
green = [0.2, 0.4, 0.2]
blue = [0.2, 0.2, 0.4]
self.colors = [red, green, blue]
self.tables2DEditor = [self.ui.tableWidget2DEditorXY,
self.ui.tableWidget2DEditorXZ,
self.ui.tableWidget2DEditorYZ]
# All the same method
self.updateMethods2DEditor = [self.updateDataFromTable2DEditor,
self.updateDataFromTable2DEditor,
self.updateDataFromTable2DEditor]
# The order here is inverted because Z index is in X and so on
self.sliders2DEditor = [self.ui.horizontalSlider2DEditorZIndex,
self.ui.horizontalSlider2DEditorYIndex,
self.ui.horizontalSlider2DEditorXIndex]
# The order here is inverted because Z index is in X and so on
self.spinBoxes2DEditor = [self.ui.spinBox2DEditorZIndex,
self.ui.spinBox2DEditorYIndex,
self.ui.spinBox2DEditorXIndex]
# File related
self.currentExtension = '.spl'
self.currentContext = 'Sample'
self.fileChanged = False
self.filepath = {"Sample": "./examples/subject/linear_uniform_3x1x1.spl",
"System": "./examples/system/no_inhomogeneity.stm",
"Sequence": "./examples/sequence/grad_phase_enc_1d.py",
"Simulator": "./examples/simulator",
"Processing": "./examples/processing",
"Protocol": "./examples/protocol",
"Examples": "./examples",
"Settings": "mrsprint.hd5",
"Config": "mrsprint.json"}
self._readConfigFile()
# Parameter tree
self.parameterTree = pt.ParameterTree(self.ui.dockWidgetParameters)
self.ui.dockWidgetParameters.setWidget(self.parameterTree)
# Data
self.dataEditor = DataEditor()
self.dataEditor.registerObserver(self)
# Settings
# Todo: remove from here, write a method
self.settings = Settings(self, QtCore.Qt.Dialog)
self.settings.setWindowModality(QtCore.Qt.WindowModal)
self.settings.resize(450, 600)
self.settings.sample_config_group.sigTreeStateChanged.connect(self.sampleSettingsChanges)
self.settings.sample_element_config_group.sigTreeStateChanged.connect(self.sampleSettingsChanges)
self.settings.magnet_config_group.sigTreeStateChanged.connect(self.systemSettingsChanges)
self.settings.simulator_group.sigTreeStateChanged.connect(self.simulatorSettingsChanges)
self.settings.rf_group.sigTreeStateChanged.connect(self.simulatorSettingsChanges)
self.settings.gradient_group.sigTreeStateChanged.connect(self.simulatorSettingsChanges)
self.ui.actionConfigurationPreferences.triggered.connect(lambda: self.settings.show())
self.ui.actionAbout.triggered.connect(self.about)
# File - context sensitive
self.ui.actionFileNew.triggered.connect(self.fileNew)
self.ui.actionFileOpen.triggered.connect(self.fileOpen)
self.ui.actionFileSave.triggered.connect(self.fileSave)
self.ui.actionFileSaveAs.triggered.connect(self.fileSaveAs)
self.ui.actionFileClose.triggered.connect(self.fileClose)
self.ui.actionQuit.triggered.connect(self.quit)
self.ui.listWidgetExplorer.itemDoubleClicked.connect(_partialItemToPath(self.open))
# Views
self.ui.actionViewsParameters.toggled.connect(self.ui.dockWidgetParameters.setVisible)
self.ui.actionViewsExplorer.toggled.connect(self.ui.dockWidgetExplorer.setVisible)
self.ui.actionViewsExplorer.setChecked(True)
self.ui.actionViewsParameters.setChecked(True)
# Change tab (focus) when tabified
self.ui.action2DEditorXY.triggered.connect(self.ui.dockWidget2DEditorXY.raise_)
self.ui.action2DEditorXZ.triggered.connect(self.ui.dockWidget2DEditorXZ.raise_)
self.ui.action2DEditorYZ.triggered.connect(self.ui.dockWidget2DEditorYZ.raise_)
self.ui.action2DEditorXYZ.triggered.connect(self.tabify2DEditor)
# Background, color and gradient
self.ui.action2DEditorGradient.triggered.connect(self.gradient2DEditor)
self.ui.action2DEditorColor.triggered.connect(self.updateTableFromData2DEditor)
self.ui.tableWidget2DEditorXZ.itemChanged.connect(self.updateDataFromTable2DEditor)
self.ui.tableWidget2DEditorYZ.itemChanged.connect(self.updateDataFromTable2DEditor)
self.ui.tableWidget2DEditorXY.itemChanged.connect(self.updateDataFromTable2DEditor)
self.ui.tableWidget2DEditorXY.itemSelectionChanged.connect(self.enableGradient2DEditor)
self.ui.tableWidget2DEditorXZ.itemSelectionChanged.connect(self.enableGradient2DEditor)
self.ui.tableWidget2DEditorYZ.itemSelectionChanged.connect(self.enableGradient2DEditor)
self.ui.tableWidget2DEditorXY.itemSelectionChanged.connect(self.clearSelection2DEditor)
self.ui.tableWidget2DEditorXZ.itemSelectionChanged.connect(self.clearSelection2DEditor)
self.ui.tableWidget2DEditorYZ.itemSelectionChanged.connect(self.clearSelection2DEditor)
self.ui.horizontalSlider2DEditorXIndex.valueChanged.connect(self.updateTableFromData2DEditor)
self.ui.horizontalSlider2DEditorYIndex.valueChanged.connect(self.updateTableFromData2DEditor)
self.ui.horizontalSlider2DEditorZIndex.valueChanged.connect(self.updateTableFromData2DEditor)
# Context
self.ui.actionContextSample.triggered.connect(self.updateContext)
self.ui.actionContextSystem.triggered.connect(self.updateContext)
self.ui.actionContextSequence.triggered.connect(self.updateContext)
self.ui.actionContextSimulator.triggered.connect(self.updateContext)
self.ui.actionContextProcessing.triggered.connect(self.updateContext)
# Parameters
self.sample = None
self.sampleElement = None
self.nucleus = None
self.magnet = None
self.sequenceExample = None
self.plot = None
# Sample
self.ui.actionSampleElementT1.triggered.connect(lambda: self.dataEditor.setCurrentValuesIndex(0))
self.ui.actionSampleElementT2.triggered.connect(lambda: self.dataEditor.setCurrentValuesIndex(1))
self.ui.actionSampleElementRho.triggered.connect(lambda: self.dataEditor.setCurrentValuesIndex(2))
# Field Inhomogeneity
self.ui.actionFieldInhomogeneityB0X.triggered.connect(lambda: self.dataEditor.setCurrentValuesIndex(0))
self.ui.actionFieldInhomogeneityB0Y.triggered.connect(lambda: self.dataEditor.setCurrentValuesIndex(1))
self.ui.actionFieldInhomogeneityB0Z.triggered.connect(lambda: self.dataEditor.setCurrentValuesIndex(2))
# Simulator
self.ui.actionSimulatorCalculate.triggered.connect(self.simulate)
self.ui.actionSimulatorRun.triggered.connect(lambda: self.plot.run())
self.ui.actionSimulatorPause.triggered.connect(lambda: self.plot.pause())
self.ui.actionSimulatorStop.triggered.connect(lambda: self.plot.pause())
# Main dockWidget
self.dockWidgetMain = QtGui.QDockWidget()
self.dockWidgetMain.setFeatures(QtGui.QDockWidget.DockWidgetFloatable)
self.setCentralWidget(self.dockWidgetMain)
# Side dockWidget
self.dockWidgetSide = QtGui.QDockWidget()
self.dockWidgetSide.setFeatures(QtGui.QDockWidget.DockWidgetFloatable)
self.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.dockWidgetSide)
# Graphics View
self.graphicsKSpace = None
self.graphicsSequence = None
self.graphicsMagnetization = None
self.graphicsIFT = None
# 3D View
self.view3DCameraLayout = QtGui.QWidget(self.dockWidgetMain)
self.view3D = None
self.spinView3D = None
self.create3DView()
# Camera
self.ui.action3DViewCameraXYZ.triggered.connect(
lambda: self.view3D.setCameraPosition(elevation=30, azimuth=45))
self.ui.action3DViewCameraXY.triggered.connect(
lambda: self.view3D.setCameraPosition(elevation=270, azimuth=270))
self.ui.action3DViewCameraYZ.triggered.connect(
lambda: self.view3D.setCameraPosition(elevation=180, azimuth=0))
self.ui.action3DViewCameraXZ.triggered.connect(
lambda: self.view3D.setCameraPosition(elevation=180, azimuth=270))
# Axis
self.ui.action3DViewXYZAxis.triggered.connect(self.showAxis3DView)
# Background and color
self.ui.action3DViewInvertBackground.triggered.connect(self.invertBackground3DView)
self.ui.action3DViewColor.triggered.connect(self.updateViewFromData3DView)
# Action groups
self.sample_action_group = QtGui.QActionGroup(self)
self.sample_action_group.addAction(self.ui.actionSampleElementRho)
self.sample_action_group.addAction(self.ui.actionSampleElementT1)
self.sample_action_group.addAction(self.ui.actionSampleElementT2)
self.sample_action_group.setExclusive(True)
self.system_action_group = QtGui.QActionGroup(self)
self.system_action_group.addAction(self.ui.actionFieldInhomogeneityB0X)
self.system_action_group.addAction(self.ui.actionFieldInhomogeneityB0Y)
self.system_action_group.addAction(self.ui.actionFieldInhomogeneityB0Z)
self.system_action_group.setExclusive(True)
self.simulator_action_group = QtGui.QActionGroup(self)
self.simulator_action_group.addAction(self.ui.actionSimulatorRun)
self.simulator_action_group.addAction(self.ui.actionSimulatorPlay)
self.simulator_action_group.addAction(self.ui.actionSimulatorPause)
self.simulator_action_group.addAction(self.ui.actionSimulatorStop)
self.simulator_action_group.setExclusive(True)
self.context_action_group = QtGui.QActionGroup(self)
self.context_action_group.addAction(self.ui.actionContextSample)
self.context_action_group.addAction(self.ui.actionContextSystem)
self.context_action_group.addAction(self.ui.actionContextSequence)
self.context_action_group.addAction(self.ui.actionContextSimulator)
self.context_action_group.addAction(self.ui.actionContextProcessing)
self.context_action_group.setExclusive(True)
self.plane_action_group = QtGui.QActionGroup(self)
self.plane_action_group.addAction(self.ui.action2DEditorXY)
self.plane_action_group.addAction(self.ui.action2DEditorXZ)
self.plane_action_group.addAction(self.ui.action2DEditorYZ)
self.plane_action_group.setExclusive(True)
self.plane_action_group.setEnabled(False)
# Todo: create a group for simulator and sequence
self.ui.spinBox2DEditorXIndex.valueChanged.connect(self.translateGrid3DView)
self.ui.spinBox2DEditorYIndex.valueChanged.connect(self.translateGrid3DView)
self.ui.spinBox2DEditorZIndex.valueChanged.connect(self.translateGrid3DView)
self.ui.action2DEditorGradient.setEnabled(False)
self.ui.action2DEditorXY.setChecked(True)
self.ui.action2DEditorXYZ.triggered.connect(self.plane_action_group.setDisabled)
self.ui.actionSampleElementAll.triggered.connect(self.sample_action_group.setDisabled)
self.ui.actionFieldInhomogeneityAll.triggered.connect(self.system_action_group.setDisabled)
# Keep this order in toolbars
self.insertToolBar(self.ui.toolBar2DEditor, self.ui.toolBarFile)
self.insertToolBar(self.ui.toolBarSampleElement, self.ui.toolBarContext)
self.ui.actionContextSample.setChecked(True)
self.loadSampleParameters()
self.loadSystemParameters()
self.loadSequenceParameters()
self.updateExplorer()
self.updateObserverData()
self.updateObserverIndex()
self.updateContext()
def _readConfigFile(self):
"""Read and load configuration file mrpsrint.json."""
path = self.filepath["Config"]
if os.path.exists(path):
try:
with open(path, 'r') as f:
config = json.load(f)
self.filepath = config["filepath"]
except Exception as err: # analysis:ignore
_logger.warning("Configuration file cannot be loaded: %s", path)
else:
_logger.warning("Configuration file loaded from: %s", path)
else:
_logger.warning("Configuration file not found: %s", path)
def _writeConfigFile(self):
"""Write configuration file mrsprint.json"""
path = self.filepath["Config"]
with open(path, 'w') as f:
str_json = json.dumps({"Current": self.filepath}, sort_keys=True, indent=4)
f.write(str_json)
_logger.info("Configuration file saved: %s", path)
[docs] def updateContext(self):
"""If context is changed, update interface."""
# Clear parameters
self.parameterTree.clear()
# Check context
spl = self.ui.actionContextSample.isChecked()
stm = self.ui.actionContextSystem.isChecked()
sqn = self.ui.actionContextSequence.isChecked()
sml = self.ui.actionContextSimulator.isChecked()
prc = self.ui.actionContextProcessing.isChecked()
# Widgets
self.dockWidgetSide.setVisible(sml)
self.ui.dockWidget2DEditorXY.setVisible(spl or stm)
self.ui.dockWidget2DEditorXZ.setVisible(spl or stm)
self.ui.dockWidget2DEditorYZ.setVisible(spl or stm)
# Menus
self.ui.menuSampleElement.setEnabled(spl)
self.ui.menuSystem.setEnabled(stm)
self.ui.menuSimulator.setEnabled(sml)
# Context toolbars
self.ui.toolBarSampleElement.setVisible(spl)
self.ui.toolBarFieldInhomogeneity.setVisible(stm)
self.ui.toolBarSimulator.setVisible(sml)
# General toolbars
self.ui.toolBar2DEditor.setEnabled(spl or stm)
self.ui.toolBar3DViewCamera.setEnabled(spl or stm)
self.ui.toolBar3DView.setEnabled(spl or stm)
self.ui.toolBarFile.setEnabled(spl or stm or sqn)
# Actions
self.ui.actionSampleElementT1.setChecked(spl)
self.ui.actionFieldInhomogeneityB0X.setChecked(stm)
# File actions
self.ui.actionFileOpen.setEnabled(spl or stm or sqn)
self.ui.actionFileNew.setEnabled(spl or stm)
self.ui.actionFileSave.setEnabled(spl or stm)
self.ui.actionFileSaveAs.setEnabled(spl or stm)
self.ui.actionFileClose.setEnabled(spl or stm)
if spl:
self.currentExtension = '.spl'
self.currentContext = 'Sample'
self.loadSampleParameters()
elif stm:
self.currentExtension = '.stm'
self.currentContext = 'System'
self.loadSystemParameters()
elif sqn:
self.currentExtension = '.py'
self.currentContext = 'Sequence'
self.loadSequenceParameters()
elif sml:
self.currentExtension = '.sml'
self.currentContext = 'Simulator'
self.loadSimulatorParameters()
elif prc:
self.currentExtension = '.prc'
self.currentContext = 'Processing'
self.loadProcessingParameters()
self.updateExplorer()
[docs] def updateObserverDataSize(self):
"""Reread data and apply updates, maybe need some rebuild."""
self.resizeTable2DEditor()
self.create3DView()
self.ui.horizontalSlider2DEditorXIndex.blockSignals(True)
self.ui.horizontalSlider2DEditorXIndex.setValue(0)
self.ui.horizontalSlider2DEditorXIndex.blockSignals(False)
self.ui.horizontalSlider2DEditorYIndex.blockSignals(True)
self.ui.horizontalSlider2DEditorYIndex.setValue(0)
self.ui.horizontalSlider2DEditorYIndex.blockSignals(False)
self.ui.horizontalSlider2DEditorZIndex.blockSignals(True)
self.ui.horizontalSlider2DEditorZIndex.setValue(0)
self.ui.horizontalSlider2DEditorZIndex.blockSignals(False)
return 0
[docs] def updateObserverData(self):
"""Reread data and apply updates."""
self.updateTableFromData2DEditor()
self.updateViewFromData3DView()
return 0
[docs] def updateObserverIndex(self):
"""Reread indexes and apply updates, does not need recreated anything."""
self.updateTableFromData2DEditor()
self.updateViewFromData3DView()
return 0
[docs] def setSettingsPath(self):
self.filepath["Settings"] = self.settings.path.text()
[docs] def loadSettings(self):
"""Load all the config parameters needed by the software."""
self.settings.open(self.filepath["Settings"])
[docs] def loadSampleParameters(self):
"""Load sample context and its parameters."""
# Clear parameters
self.parameterTree.clear()
# Sample element
self.sampleElement = SampleElement(self.settings.sample_element_config_group)
self.sampleElement.t1.sigValueChanged.connect(
lambda: self.updateSelectedCells(self.sampleElement.t1.value(), 0))
self.sampleElement.t2.sigValueChanged.connect(
lambda: self.updateSelectedCells(self.sampleElement.t2.value(), 1))
self.sampleElement.rho.sigValueChanged.connect(
lambda: self.updateSelectedCells(self.sampleElement.rho.value(), 2))
# Add sample element parameters
self.parameterTree.addParameters(self.sampleElement)
# Sample
self.sample = Sample(self.settings.sample_config_group)
self.sample.Nx.sigValueChanged.connect(
lambda: self.dataEditor.setNX(self.sample.Nx.value()))
self.sample.Ny.sigValueChanged.connect(
lambda: self.dataEditor.setNY(self.sample.Ny.value()))
self.sample.Nz.sigValueChanged.connect(
lambda: self.dataEditor.setNZ(self.sample.Nz.value()))
self.sample.SizeX.sigValueChanged.connect(
lambda: self.dataEditor.setSizeX(self.sample.SizeX.value()))
self.sample.SizeY.sigValueChanged.connect(
lambda: self.dataEditor.setSizeY(self.sample.SizeY.value()))
self.sample.SizeZ.sigValueChanged.connect(
lambda: self.dataEditor.setSizeZ(self.sample.SizeZ.value()))
# Add sample parameters
self.parameterTree.addParameters(self.sample)
# Getting data
max_t1 = self.settings.sample_element_config_group.maxT1.value()
def_t1 = self.settings.sample_element_config_group.t1.value()
min_t1 = self.settings.sample_element_config_group.minT1.value()
max_t2 = self.settings.sample_element_config_group.maxT2.value()
def_t2 = self.settings.sample_element_config_group.t2.value()
min_t2 = self.settings.sample_element_config_group.minT2.value()
max_rho = self.settings.sample_element_config_group.maxRho.value()
def_rho = self.settings.sample_element_config_group.rho.value()
min_rho = 0.
default_shape = [self.sample.Nx.value(), self.sample.Ny.value(), self.sample.Nz.value()]
default_size = [self.sample.SizeX.value(), self.sample.SizeY.value(), self.sample.SizeZ.value()]
# Setting data
self.dataEditor.setCurrentValuesIndex(0)
self.dataEditor.setDimension(default_size, default_shape)
self.dataEditor.currentOpacityIndex = 2
self.dataEditor.setValues([min_t1, min_t2, min_rho], [def_t1, def_t2, def_rho], [max_t1, max_t2, max_rho])
self.dataEditor.data = np.full(self.dataEditor.shape + [3], self.dataEditor.defaultValues)
if self.filepath["Sample"]:
self.openSample(self.filepath["Sample"])
self.create3DView()
[docs] def loadSystemParameters(self):
"""Load system context and its parameters."""
self.settings.blockSignals(True)
# Nucleus
self.nucleus = Nucleus()
self.nucleus.nucleus.sigValueChanged.connect(self.nucleus.updateGamma)
self.parameterTree.addParameters(self.nucleus)
# Magnet
self.magnet = Magnet(self.settings.magnet_config_group)
default_shape = [self.magnet.resolution.value() for i in range(3)]
default_size = [self.settings.magnet_config_group.SizeX.value(),
self.settings.magnet_config_group.SizeY.value(),
self.settings.magnet_config_group.SizeZ.value()]
self.magnet.resolution.sigValueChanged.connect(
lambda: self.dataEditor.setDimension(
default_size, [self.magnet.resolution.value() for i in range(3)]))
self.nucleus.gamma.sigValueChanged.connect(
lambda: self.magnet.carrierFrequency.setValue(
self.magnet.magneticStrength.value() * self.nucleus.gamma.value()))
self.magnet.magneticStrength.sigValueChanged.connect(
lambda: self.magnet.carrierFrequency.setValue(
self.magnet.magneticStrength.value() * self.nucleus.gamma.value()))
# Add magnet parameters
self.parameterTree.addParameters(self.magnet)
# Getting data
max_b0x = self.settings.magnet_config_group.limitB0x.value()
def_b0x = self.settings.magnet_config_group.B0x.value()
min_b0x = - max_b0x
max_b0y = self.settings.magnet_config_group.limitB0y.value()
def_b0y = self.settings.magnet_config_group.B0z.value()
min_b0y = - max_b0y
max_b0z = self.settings.magnet_config_group.limitB0z.value()
def_b0z = self.settings.magnet_config_group.B0z.value()
min_b0z = - max_b0z
# Setting data
self.dataEditor.setCurrentValuesIndex(0)
self.dataEditor.setDimension(default_size, default_shape)
self.dataEditor.currentOpacityIndex = -1
self.dataEditor.setValues([min_b0x, min_b0y, min_b0z], [def_b0x, def_b0y, def_b0z], [max_b0x, max_b0y, max_b0z])
self.dataEditor.data = np.full(self.dataEditor.shape + [3], self.dataEditor.defaultValues)
if self.filepath["System"]:
self.openSystem(self.filepath["System"])
self.settings.blockSignals(False)
self.create3DView()
[docs] def loadSequenceParameters(self):
"""Load sequence context and its parameters."""
self.dockWidgetMain.setWidget(self.graphicsSequence)
if self.sequenceExample is not None:
self.parameterTree.addParameters(self.sequenceExample)
if self.filepath["Sequence"]:
self.openSequence(self.filepath["Sequence"])
[docs] def loadSimulatorParameters(self):
"""Load simulator context and its parameters."""
self.spinView3D = gl.GLViewWidget()
self.graphicsMagnetization = pg.GraphicsWindow()
self.dockWidgetMain.setWidget(self.spinView3D)
self.dockWidgetSide.setWidget(self.graphicsMagnetization)
simulator_ready = True
missing_contexts = []
for context in ["Sample", "System", "Sequence"]:
if not self.filepath[context]:
simulator_ready = False
missing_contexts.append(context)
self.ui.actionSimulatorCalculate.setEnabled(simulator_ready)
self.simulator_action_group.setDisabled(simulator_ready)
if not simulator_ready:
title = "Simulator context"
message = "There is no saved file from " + missing_contexts[0]
for i in range(1, len(missing_contexts) - 1):
message += ", " + missing_contexts[i]
if len(missing_contexts) > 1:
message += " and " + missing_contexts[len(missing_contexts) - 1]
message += " contexts.\nThese files are needed to start a simulation."
else:
message += " context.\nThis file is needed to start a simulation."
message_box = QtGui.QMessageBox(QtGui.QMessageBox.Warning, title, message)
message_box.exec()
[docs] def loadProcessingParameters(self):
"""Load processing parameters and connect signals."""
self.graphicsIFT = pg.GraphicsWindow()
self.dockWidgetMain.setWidget(self.graphicsIFT)
if self.plot is None:
# Message error
title = "Processing context"
message = "There is no simulation of experiment by now.\nPlease select the 'Simulator context' and calculate a new simulation."
message_box = QtGui.QMessageBox(QtGui.QMessageBox.Warning, title, message)
message_box.exec()
else:
# Complex ifft self.setCentralWidget(self.dockWidgetMain)
# gamma Grad t self.setCentralWidget(self.dockWidgetMain)
# rad/(G*s) * self.setCentralWidget(self.dockWidgetMain)
# k = 1/z
# z = 1/k
# k = gamma gr self.setCentralWidget(self.dockWidgetMain)
# k = 26752.21 self.setCentralWidget(self.dockWidgetMain)
read = self.sequenceExample.read
G_x = max(self.sequenceExample.gr[0])
n = len(read)
dt = self.sequenceExample.dt
freq = np.arange(0, n) * 2 * np.pi / (26752.219 * G_x * dt * n)
# freq = np.fft.fftfreq(n, dt)*2*np.pi/(26752.219*G_x)
data_ifft_abs = np.absolute(np.fft.ifft(self.plot.reduced_mxy[read[0]:read[-1] + 1]))
self.graphicsIFT.addPlot(y=data_ifft_abs, x=freq)
[docs] def sampleSettingsChanges(self):
"""Reload sample parameters."""
if self.ui.actionContextSample.isChecked():
self.loadSampleParameters()
[docs] def systemSettingsChanges(self):
"""Reload system parameters."""
if self.ui.actionContextSystem.isChecked():
self.loadSystemParameters()
[docs] def simulatorSettingsChanges(self):
"""Reload simulator parameters."""
if self.filepath["Sequence"]:
imp.load_source("sequence", self.filepath["Sequence"])
import sequence
self.sequenceExample = sequence.SequenceExample(self.settings, self.nucleus)
if self.ui.actionContextSequence.isChecked():
self.loadSequenceParameters()
# ------------------------------------------------------------------------
# 2D Editor
# ------------------------------------------------------------------------
[docs] def setIndexLimits2DEditor(self, nx, ny, nz):
"""Set index limits for 2D editor.
Args:
nx (int): Shape of data in the x dimension.
ny (int): Shape of data in the y dimension.
nz (int): Shape of data in the z dimension.
"""
# X index
self.ui.spinBox2DEditorXIndex.setMaximum(nx - 1)
self.ui.spinBox2DEditorXIndex.setMinimum(0)
self.ui.horizontalSlider2DEditorXIndex.setMaximum(ny - 1)
self.ui.horizontalSlider2DEditorXIndex.setMinimum(0)
# Y index
self.ui.spinBox2DEditorYIndex.setMaximum(ny - 1)
self.ui.spinBox2DEditorYIndex.setMinimum(0)
self.ui.horizontalSlider2DEditorYIndex.setMaximum(ny - 1)
self.ui.horizontalSlider2DEditorYIndex.setMinimum(0)
# Z index
self.ui.spinBox2DEditorZIndex.setMaximum(nz)
self.ui.spinBox2DEditorZIndex.setMinimum(0)
self.ui.horizontalSlider2DEditorZIndex.setMaximum(nz - 1)
self.ui.horizontalSlider2DEditorZIndex.setMinimum(0)
[docs] def resizeTable2DEditor(self):
"""Change the size of the tableWidgets whenever a row/column is added/removed."""
nx, ny, nz = self.dataEditor.shape
_logger.debug("Shape: %s", (nx, ny, nz))
# In the order tables are listed
rows_size = [ny, nz, nz]
cols_size = [nx, nx, ny]
page_size = [nz, ny, nx]
row_label = ["Y", "Z", "Z"]
column_label = ["X", "X", "Y"]
# Run over 3 tables
for index in range(3):
row_size_old = self.tables2DEditor[index].rowCount()
column_size_old = self.tables2DEditor[index].columnCount()
# _logger.debug("Table index: %s Row count old:%s Column count old: %s ", index, row_size_old, column_size_old)
self.tables2DEditor[index].setColumnCount(cols_size[index])
self.tables2DEditor[index].setRowCount(rows_size[index])
self.sliders2DEditor[index].setMinimum(0)
self.spinBoxes2DEditor[index].setMinimum(0)
self.sliders2DEditor[index].setMaximum(page_size[index] - 1)
self.spinBoxes2DEditor[index].setMaximum(page_size[index] - 1)
# Set row labels
row_labels = [row_label[index] + str(i) for i in range(rows_size[index])]
self.tables2DEditor[index].setVerticalHeaderLabels(row_labels)
# Set column labels
column_labels = [column_label[index] + str(i) for i in range(cols_size[index])]
self.tables2DEditor[index].setHorizontalHeaderLabels(column_labels)
self.tables2DEditor[index].blockSignals(True)
# Process rows
if rows_size[index] > row_size_old:
for i in range(row_size_old, rows_size[index]):
for j in range(self.tables2DEditor[index].columnCount()):
item = QtGui.QTableWidgetItem()
self.tables2DEditor[index].setItem(i, j, item)
elif rows_size[index] < row_size_old:
for i in range(row_size_old, rows_size[index]):
for j in range(self.tables2DEditor[index].columnCount()):
self.tables2DEditor[index].takeItem(i, j)
# Process columns
if cols_size[index] > column_size_old:
for i in range(self.tables2DEditor[index].rowCount()):
for j in range(column_size_old, cols_size[index]):
item = QtGui.QTableWidgetItem()
self.tables2DEditor[index].setItem(i, j, item)
elif cols_size[index] < column_size_old:
for i in range(self.tables2DEditor[index].rowCount()):
for j in range(column_size_old, cols_size[index]):
self.tables2DEditor[index].takeItem(i, j)
# row_size_new = self.tables2DEditor[index].rowCount()
# column_size_new = self.tables2DEditor[index].columnCount()
# _logger.debug("Table index: %s Row count new:%s Column count new: %s >", index, row_size_new, column_size_new)
self.tables2DEditor[index].blockSignals(False)
self.updateTableFromData2DEditor()
[docs] def clearSelection2DEditor(self, all_tables=False):
"""Clear selection of all 2D editor tables.
Args:
all_tables (bool): Informs if all tables are shown or not. Default is False.
"""
if not self.ui.tableWidget2DEditorXY.hasFocus() or all_tables:
self.ui.tableWidget2DEditorXY.clearSelection()
if not self.ui.tableWidget2DEditorXZ.hasFocus() or all_tables:
self.ui.tableWidget2DEditorXZ.clearSelection()
if not self.ui.tableWidget2DEditorYZ.hasFocus() or all_tables:
self.ui.tableWidget2DEditorYZ.clearSelection()
[docs] def updateTableFromData2DEditor(self):
"""Update items of all 2D editor using 3d-array data and set color.
This method is called when the data shape is changed or when
the plane index is changed. Also, when the data index is changed.
"""
# Get plane indexes - visual information only
x_index = self.ui.horizontalSlider2DEditorXIndex.value()
y_index = self.ui.horizontalSlider2DEditorYIndex.value()
z_index = self.ui.horizontalSlider2DEditorZIndex.value()
# Get data shape
nx, ny, nz = self.dataEditor.shape
_logger.debug("Shape: %s, Plane indexes: %s ", (nx, ny, nz), (x_index, y_index, z_index))
# Prevent multiple signals
self.ui.tableWidget2DEditorXY.blockSignals(True)
self.ui.tableWidget2DEditorXZ.blockSignals(True)
self.ui.tableWidget2DEditorYZ.blockSignals(True)
min_value = self.dataEditor.minimumValues[self.dataEditor.currentValuesIndex]
max_value = self.dataEditor.maximumValues[self.dataEditor.currentValuesIndex]
max_opacity = 1
if self.dataEditor.currentOpacityIndex != -1:
max_opacity = self.dataEditor.maximumValues[self.dataEditor.currentOpacityIndex]
rgb = self.colors[self.dataEditor.currentValuesIndex]
for i in range(nx):
for j in range(ny):
# Changes on plane XY
xy_value = self.dataEditor.getDataItem(i, j, z_index)
try:
xy_norm_value = (max_value - xy_value) / (max_value - min_value)
except Warning:
# RuntimeWarning: invalid value encountered in long_scalars
pass
opacity = 1.
editor_color = self.ui.action2DEditorColor.isChecked()
if self.dataEditor.currentOpacityIndex != -1:
opacity = self.dataEditor.getDataItem(
i, j, z_index, self.dataEditor.currentOpacityIndex) / max_opacity
item_xy = self.ui.tableWidget2DEditorXY.item(j, i)
if item_xy:
item_xy.setText(str(xy_value))
item_xy.setBackground(_color_settings_2d(rgb, xy_norm_value, opacity, editor_color))
for k in range(nz):
# Changes on plane XZ
xz_value = self.dataEditor.getDataItem(i, y_index, k)
xz_norm_value = (max_value - xz_value) / (max_value - min_value)
opacity = 1.
if self.dataEditor.currentOpacityIndex != -1:
opacity = self.dataEditor.getDataItem(
i, y_index, k, self.dataEditor.currentOpacityIndex) / max_opacity
item_xz = self.ui.tableWidget2DEditorXZ.item(k, i)
if item_xz:
item_xz.setText(str(xz_value))
item_xz.setBackground(_color_settings_2d(rgb, xz_norm_value, opacity, editor_color))
# Changes on plane YZ
yz_value = self.dataEditor.getDataItem(x_index, j, k)
yz_norm_value = (max_value - yz_value) / (max_value - min_value)
opacity = 1.
if self.dataEditor.currentOpacityIndex != -1:
opacity = self.dataEditor.getDataItem(
x_index, j, k, self.dataEditor.currentOpacityIndex) / max_opacity
item_yz = self.ui.tableWidget2DEditorYZ.item(k, j)
if item_yz:
item_yz.setText(str(yz_value))
item_yz.setBackground(_color_settings_2d(rgb, yz_norm_value, opacity, editor_color))
# Enable signals again
self.ui.tableWidget2DEditorXY.blockSignals(False)
self.ui.tableWidget2DEditorXZ.blockSignals(False)
self.ui.tableWidget2DEditorYZ.blockSignals(False)
[docs] def updateSelectedCells(self, value, index):
"""Update selected cells from parameter tree.
Args:
value (float): Value to be updated in the table.
index (int): Index of selected type of data.
"""
items, methods, _ = self.itemsMethodsTable2DEditor()
for item in items:
self.dataEditor.setDataItem(eval(methods["x"]),
eval(methods["y"]),
eval(methods["z"]),
value,
index)
self.updateObserverData()
[docs] def enableGradient2DEditor(self):
"""Enable the gradient editor for 2D editor."""
if (self.ui.tableWidget2DEditorXY.selectedItems() and
self.ui.tableWidget2DEditorXY.hasFocus()) or \
(self.ui.tableWidget2DEditorXZ.selectedItems() and
self.ui.tableWidget2DEditorXZ.hasFocus()) or \
(self.ui.tableWidget2DEditorYZ.selectedItems() and
self.ui.tableWidget2DEditorYZ.hasFocus()):
self.ui.action2DEditorGradient.setEnabled(True)
else:
self.ui.action2DEditorGradient.setEnabled(False)
[docs] def itemsMethodsTable2DEditor(self):
"""Return current selected item, methods to access their indexes and the table.
Returns:
list (QtGui.QTableWidgetItem()), dict (string of methods), QtGui.QTableWidget():
The current selected items, the methods attached to the selected table and the selected table
Todo:
This could be even better if getting these values, return just a new matrix
with proper indexes and values to set data.
"""
table = None
item_methods = {}
items = []
if self.ui.tableWidget2DEditorXY.selectedItems():
table = self.ui.tableWidget2DEditorXY
items = table.selectedItems()
item_methods = {"z": "self.ui.horizontalSlider2DEditorZIndex.value()",
"y": "item.row()",
"x": "item.column()"}
elif self.ui.tableWidget2DEditorXZ.selectedItems():
table = self.ui.tableWidget2DEditorXZ
items = table.selectedItems()
item_methods = {"z": "item.row()",
"y": "self.ui.horizontalSlider2DEditorYIndex.value()",
"x": "item.column()"}
elif self.ui.tableWidget2DEditorYZ.selectedItems():
table = self.ui.tableWidget2DEditorYZ
items = table.selectedItems()
item_methods = {"z": "item.row()",
"y": "item.column()",
"x": "self.ui.horizontalSlider2DEditorXIndex.value()"}
return items, item_methods, table
[docs] def gradient2DEditor(self):
"""Run the gradient editor and set values to 2D editor tables.
Todo:
Remove eval after changing itemMethodsTable2dEditor.
"""
dialog = QtGui.QMainWindow()
dialog.ui = Ui_Gradient()
dialog.ui.setupUi(dialog)
# Setting dialog_ui values
cur_index = self.dataEditor.currentValuesIndex
max_value = self.dataEditor.maximumValues[cur_index]
min_value = self.dataEditor.minimumValues[cur_index]
dialog.ui.doubleSpinBoxStart.setMaximum(max_value)
dialog.ui.doubleSpinBoxStart.setMinimum(min_value)
dialog.ui.doubleSpinBoxEnd.setMaximum(max_value)
dialog.ui.doubleSpinBoxEnd.setMinimum(min_value)
dialog.show()
if dialog.show():
items, item_methods, table = self.itemsMethodsTable2DEditor()
table.blockSignals(True)
self.clearSelection2DEditor(all_tables=True)
# this must be item.row or item.column
method_index = ""
# If gradient is applied to rows
if dialog.ui.radioButtonRow.isChecked():
method_index = "item.row()"
# If gradient in applied to columns
elif dialog.ui.radioButtonColumn.isChecked():
method_index = "item.column()"
# Use of eval it is not so good, but it works nicelly
max_position = max([eval(method_index) for item in items])
min_position = min([eval(method_index) for item in items])
gradient = np.linspace(dialog.ui.doubleSpinBoxStart.value(),
dialog.ui.doubleSpinBoxEnd.value(),
max_position - min_position + 1)
for item in items:
self.dataEditor.setDataItem(eval(item_methods["x"]),
eval(item_methods["y"]),
eval(item_methods["z"]),
gradient[eval(method_index) - min_position])
table.blockSignals(False)
self.dataEditor.updateObserversData()
[docs] def updateDataFromTable2DEditor(self, item_changed):
"""Update data from table in 2D editor."""
value = float(item_changed.text())
_logger.debug("Value: %s", value)
items, item_methods, _ = self.itemsMethodsTable2DEditor()
for item in items:
self.dataEditor.setDataItem(eval(item_methods["x"]),
eval(item_methods["y"]),
eval(item_methods["z"]),
value)
self.updateObserverData()
[docs] def tabify2DEditor(self, value):
"""Tabify 2D editor depending on value.
Args:
value (bool): Informs if the tables in 2DEditor should be tabified or not.
"""
_logger.debug("Tabify: %s", value)
if value:
self.removeDockWidget(self.ui.dockWidget2DEditorXY)
self.removeDockWidget(self.ui.dockWidget2DEditorXZ)
self.removeDockWidget(self.ui.dockWidget2DEditorYZ)
self.ui.dockWidget2DEditorXY.setVisible(True)
self.ui.dockWidget2DEditorXZ.setVisible(True)
self.ui.dockWidget2DEditorYZ.setVisible(True)
self.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.ui.dockWidget2DEditorXY)
self.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.ui.dockWidget2DEditorXZ)
self.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.ui.dockWidget2DEditorYZ)
else:
self.tabifyDockWidget(self.ui.dockWidget2DEditorXY, self.ui.dockWidget2DEditorXZ)
self.tabifyDockWidget(self.ui.dockWidget2DEditorXZ, self.ui.dockWidget2DEditorYZ)
if self.ui.action2DEditorYZ.isChecked():
self.ui.dockWidget2DEditorYZ.raise_()
elif self.ui.action2DEditorXZ.isChecked():
self.ui.dockWidget2DEditorXZ.raise_()
else:
self.ui.dockWidget2DEditorXY.raise_()
[docs] def invertBackground2DEditor(self, invert=False):
"""Invert the background color of 2D editor."""
raise NotImplementedError("Method invert background 2D editor is not implemented.")
# ------------------------------------------------------------------------
# 3D View
# ------------------------------------------------------------------------
[docs] def create3DView(self):
"""Create a 3D view and the first 3D object.
It must be called when starts and when the shape or size is changed.
"""
lx, ly, lz = self.dataEditor.size
dx, dy, dz = self.dataEditor.elementSize
nx, ny, nz = self.dataEditor.shape
_logger.debug("Shape: %s, Size: %s", (nx, ny, nz), (lx, ly, lz))
# Camera configuration
self.view3D = gl.GLViewWidget()
camera_dist = 3 * max(lx, ly, lz)
self.view3D.setCameraPosition(distance=camera_dist)
self.dockWidgetMain.setWidget(self.view3D)
# Grid configuration
self.view3DGridX = gl.GLGridItem(antialias=True, glOptions="translucent")
self.view3D.addItem(self.view3DGridX)
self.view3DGridY = gl.GLGridItem(antialias=True, glOptions="translucent")
self.view3D.addItem(self.view3DGridY)
self.view3DGridZ = gl.GLGridItem(antialias=True, glOptions="translucent")
self.view3D.addItem(self.view3DGridZ)
self.view3DGridX.setVisible(self.ui.action3DViewXGrid.isChecked())
self.view3DGridY.setVisible(self.ui.action3DViewYGrid.isChecked())
self.view3DGridZ.setVisible(self.ui.action3DViewZGrid.isChecked())
self.ui.action3DViewXGrid.triggered.connect(self.view3DGridX.setVisible)
self.ui.action3DViewYGrid.triggered.connect(self.view3DGridY.setVisible)
self.ui.action3DViewZGrid.triggered.connect(self.view3DGridZ.setVisible)
self.translateGrid3DView()
# Axis configuration
self.view3DAxisX = gl.GLLinePlotItem(pos=np.array([[0, 0, 0], [(lx + 4) / 2, 0, 0]]),
color=(255, 0, 0, 1), width=3.)
self.view3DAxisX.setGLOptions(opts="opaque")
self.view3DAxisX.setVisible(self.ui.action3DViewXYZAxis.isChecked())
self.view3D.addItem(self.view3DAxisX)
self.view3DAxisY = gl.GLLinePlotItem(pos=np.array([[0, 0, 0], [0, (ly + 4) / 2, 0]]),
color=(0, 255, 0, 1), width=3.)
self.view3DAxisY.setGLOptions(opts="opaque")
self.view3DAxisY.setVisible(self.ui.action3DViewXYZAxis.isChecked())
self.view3D.addItem(self.view3DAxisY)
self.view3DAxisZ = gl.GLLinePlotItem(pos=np.array([[0, 0, 0], [0, 0, (lz + 4) / 2]]),
color=(0, 0, 255, 1), width=3.)
self.view3DAxisZ.setGLOptions(opts="opaque")
self.view3DAxisZ.setVisible(self.ui.action3DViewXYZAxis.isChecked())
self.view3D.addItem(self.view3DAxisZ)
# Create 3D object
def initialize_3d_item(x, y, z):
"""Create a new 3D item and put it in the view 3D.
Args:
x (int): X index of the item.
y (int): Y index of the item.
z (int): Z index of the item.
Returns:
gl.GLMeshItem(): GL mesh item.
"""
item = _get_element_3d_view([x, y, z], [dx, dy, dz], [lx, ly, lz])
self.view3D.addItem(item)
return item
self.view3DObject = [[[initialize_3d_item(x, y, z)
for z in range(nz)]
for y in range(ny)]
for x in range(nx)]
self.updateViewFromData3DView()
# Background color setting
if self.ui.action3DViewInvertBackground.isChecked():
self.view3D.setBackgroundColor((255, 255, 255))
[docs] def updateViewFromData3DView(self):
"""Color 3D view based on 3D data."""
min_value = self.dataEditor.minimumValues[self.dataEditor.currentValuesIndex]
max_value = self.dataEditor.maximumValues[self.dataEditor.currentValuesIndex]
max_density = 1.
if self.dataEditor.currentOpacityIndex != -1:
max_density = self.dataEditor.maximumValues[self.dataEditor.currentOpacityIndex]
rgb = self.colors[self.dataEditor.currentValuesIndex]
_logger.debug("Value index: %s", self.dataEditor.currentValuesIndex)
# Runs over the view3DObject changing color
for index, item in np.ndenumerate(self.view3DObject):
x, y, z = index
value = (self.dataEditor.getDataItem(x, y, z) - max_value) / (min_value - max_value)
opacity = 1.
if self.dataEditor.currentOpacityIndex != -1:
opacity = self.dataEditor.getDataItem(x, y, z, self.dataEditor.currentOpacityIndex) / max_density
item.setColor(_color_settings_3d(rgb, value, opacity, self.ui.action3DViewColor.isChecked()))
[docs] def translateGrid3DView(self):
"""Translate grids to correct position."""
lx, ly, lz = self.dataEditor.size
dx, dy, dz = self.dataEditor.elementSize
self.view3DGridX.resetTransform()
self.view3DGridY.resetTransform()
self.view3DGridZ.resetTransform()
self.view3DGridX.setSize(z=lx + dx, y=ly + dy, x=lz + dz)
self.view3DGridX.translate(dx=0, dy=0, dz=self.ui.spinBox2DEditorXIndex.value() * dx - lx / 2 + dx / 2)
self.view3DGridX.rotate(90., 0, 1, 0)
self.view3DGridY.setSize(x=lx + dx, y=lz + dz, z=ly + dy)
self.view3DGridY.translate(dx=0, dy=0, dz=self.ui.spinBox2DEditorYIndex.value() * dy - ly / 2 + dy / 2)
self.view3DGridY.rotate(-90., 1, 0, 0)
self.view3DGridZ.setSize(x=lx + dx, y=ly + dy, z=lz + dz)
self.view3DGridZ.translate(dx=0, dy=0, dz=self.ui.spinBox2DEditorZIndex.value() * dz - lz / 2 + dz / 2)
[docs] def showAxis3DView(self, show=True):
"""Show axis in 3D view.
Args:
show (bool): Informs if the axis should be visible. Default is True.
"""
self.view3DAxisX.setVisible(show)
self.view3DAxisY.setVisible(show)
self.view3DAxisZ.setVisible(show)
[docs] def invertBackground3DView(self, invert=False):
"""Invert the background color of 3D view.
Args:
invert (bool): Informs if the background color should be inverted. Default is False.
"""
if invert:
self.view3D.setBackgroundColor((255, 255, 255))
else:
self.view3D.setBackgroundColor((0, 0, 0))
# Graph View ---------------------------------------------------------------
[docs] def plotSequence(self):
"""Use the data from a sequence to create the RF and Gradient plots."""
self.graphicsSequence = pg.GraphicsWindow()
self.dockWidgetMain.setWidget(self.graphicsSequence)
# sequence components
rf = self.sequenceExample.rf
gr_x, gr_y, gr_z = self.sequenceExample.gr
tp = self.sequenceExample.tp
# rf components
rf_am = np.absolute(rf)
rf_pm = np.angle(rf, deg=False)
rf_fm = np.gradient(rf_pm, tp[0] - tp[1])
# rf graphs
rf_am_plot = plot_item(self.graphicsSequence, tp, rf_am, "y", False, "", "", True, "RF (AM)", "G")
rf_fm_plot = plot_item(self.graphicsSequence, tp, rf_fm, "m", False, "", "", True, "RF (FM)", "Hz")
rf_pm_plot = plot_item(self.graphicsSequence, tp, rf_pm, "c", False, "", "", True, "RF (PM)", "rad")
rf_pm_plot.getAxis('left').setTicks([[(np.pi, chr(960)),
(np.pi / 2, chr(960) + '/2'),
(0.0, '0'),
(-np.pi, '-' + chr(960)),
(-np.pi / 2, '-' + chr(960) + '/2')]])
# gradient graphs
gr_x_plot = plot_item(self.graphicsSequence, tp, gr_x, "r", False, "", "", True, "Gr (x)", "G/cm")
gr_y_plot = plot_item(self.graphicsSequence, tp, gr_y, "g", False, "", "", True, "Gr (y)", "G/cm")
gr_z_plot = plot_item(self.graphicsSequence, tp, gr_z, "b", True, "Time", "s", True, "Gr (z)", "G/cm")
# List of sequence components plot
plot = [rf_am_plot, rf_pm_plot, rf_fm_plot, gr_x_plot, gr_y_plot, gr_z_plot]
# Link plots
# Todo: remove range and use iterators (next)
for i in range(len(plot) - 1):
plot[i].getViewBox().setXLink(plot[i + 1].getViewBox())
# List of timeline plots
timelines = [pg.InfiniteLine(pos=0, angle=90, pen="w", movable=True,
bounds=(tp[0], tp[len(tp) - 1] * 0.999)) for i in plot]
def updateTimelines():
"""Function to set the same value for all timelines.
Todo:
Rewrite this function to eliminate or write correct use of 'for'.
"""
# Todo: there is a better way to do this
indexes = [1 for i in range(len(timelines))]
# Todo: remove range, change to iterators
for i in range(len(timelines)):
if timelines[i].value() == timelines[np.mod(i + 1, len(timelines))].value():
indexes[i] = 0
indexes[np.mod(i + 1, len(timelines))] = 0
if 1 in indexes:
value = timelines[indexes.index(1)].value()
for timeline in timelines:
timeline.blockSignals(True)
timeline.setValue(value)
timeline.blockSignals(False)
# Todo: remove range, change to iterators
for i in range(len(plot)):
plot[i].addItem(timelines[i])
timelines[i].sigPositionChanged.connect(updateTimelines)
# Simulation ---------------------------------------------------------------
[docs] def simulate(self):
"""Setup a simulation."""
# opening file
self.ui.actionSimulatorCalculate.setChecked(True)
sample_file = h5py.File(self.filepath["Sample"], 'r')
sample_group = sample_file.require_group("SampleGroup")
# reading sample file
sample_param_dset = sample_group.require_dataset("param", exact=True, shape=(9,), dtype=float)
sample_shape = tuple(sample_group.require_dataset("shape", exact=True, shape=(4,), dtype=int).value)
sample_data_dset = sample_group.require_dataset("data", exact=True, shape=sample_shape, dtype=float)
# creating positions
sample_size = [sample_param_dset.value[i] for i in [0, 1, 2]]
sample_dimension = [int(sample_param_dset.value[3 + i]) for i in [0, 1, 2]]
sample_step = [sample_size[i] / sample_dimension[i] for i in [0, 1, 2]]
sample_offset = [-sample_size[i] / 2 + sample_step[i] / 2 for i in [0, 1, 2]]
sample_position = create_positions(size=sample_size, step=sample_step, offset=sample_offset)
# get this from magnet freq shift or inhomogeneity
freq_shift = frequency_shift(5, 1, 0)
# get this from simulator parameters
mode = 2
number_voxels = sample_dimension[0] * sample_dimension[1] * sample_dimension[2]
magnetization_size = sample_step[2] / 2
rf = self.sequenceExample.rf
gr = self.sequenceExample.gr
dt = self.sequenceExample.dt
tp = self.sequenceExample.tp
t1 = np.zeros(number_voxels)
t2 = np.zeros(number_voxels)
rho = np.zeros(number_voxels)
index = 0
# try not use for
for k in range(sample_dimension[2]):
for j in range(sample_dimension[1]):
for i in range(sample_dimension[0]):
t1[index] = sample_data_dset[i, j, k, 0]
t2[index] = sample_data_dset[i, j, k, 1]
rho[index] = sample_data_dset[i, j, k, 2]
index += 1
# Evolve Bloch equation
mx, my, mz = evolve(rf, gr, dt, t1, t2, freq_shift, sample_position, mode)
# Apply density of nuclei effect
mx = np.transpose(mx, (1, 0, 2))
my = np.transpose(my, (1, 0, 2))
mz = np.transpose(mz, (1, 0, 2))
for i in range(number_voxels):
mx[i] *= rho[i] * magnetization_size
my[i] *= rho[i] * magnetization_size
mz[i] *= rho[i] * magnetization_size
mx = np.transpose(mx, (1, 0, 2))
my = np.transpose(my, (1, 0, 2))
mz = np.transpose(mz, (1, 0, 2))
self.plot = Plot(self.settings, mx, my, mz, gr, tp, freq_shift, sample_position, magnetization_size)
self.plot.plotMagnetization(self.graphicsMagnetization)
self.plot.plotSpin(self.spinView3D)
if self.settings.simulator_group.showSample.value():
nx, ny, nz = sample_dimension
for x in range(nx):
for y in range(ny):
for z in range(nz):
item = _get_element_3d_view([x, y, z], sample_step, sample_size)
item.setColor((1, 1, 1, 0.06))
self.spinView3D.addItem(item)
if self.settings.simulator_group.showMagnet.value():
x = self.settings.magnet_config_group.SizeX.value()
y = self.settings.magnet_config_group.SizeY.value()
z = self.settings.magnet_config_group.SizeZ.value()
magnet = _get_element_3d_view([0, 0, 0], [x, y, z], [x, y, z])
magnet.setColor((1, 1, 1, 0.03))
self.spinView3D.addItem(magnet)
self.simulator_action_group.setEnabled(True)
self.ui.actionSimulatorStop.setChecked(True)
# ------------------------------------------------------------------------
# File -------------------------------------------------------------------
# ------------------------------------------------------------------------
[docs] def open(self, file_path):
"""Open a HDF5 file and applies the changes to the program.
Args:
file_path (str): Path to the file where the data will be opened.
"""
_logger.info("File path to open: %s", file_path)
if self.currentContext == "Sample":
self.openSample(file_path)
elif self.currentContext == "System":
self.openSystem(file_path)
elif self.currentContext == "Sequence":
self.openSequence(file_path)
self.filepath[self.currentContext] = file_path
[docs] def save(self, file_path):
"""Create a HDF5 file to save current data.
Args:
file_path (str): Path to the file where the data will be saved.
"""
_logger.debug("File path to save: %s", file_path)
if self.currentContext == "Sample":
self.saveSample(file_path)
elif self.currentContext == "System":
self.saveSystem(file_path)
self.filepath[self.currentContext] = file_path
[docs] def canClose(self):
"""Check if the user want to save the sample before closing.
Returns:
bool: True if can close.
"""
if self.fileChanged:
close_dialog = QtGui.QMessageBox()
close_dialog.setWindowTitle("Close")
close_dialog.setText("Do you want to save your changes?")
close_dialog.setStandardButtons(QtGui.QMessageBox.Save |
QtGui.QMessageBox.Discard |
QtGui.QMessageBox.Cancel)
close_dialog.setDefaultButton(QtGui.QMessageBox.Save)
answer = close_dialog.exec()
if answer == QtGui.QMessageBox.Save:
self.fileSave()
return True
elif answer == QtGui.QMessageBox.Discard:
_logger.debug("Discarding changes: %s", self.filepath)
return True
else:
return False
else:
return True
[docs] def fileOpen(self):
"""Open a dialog to select a HDF5 file to be opened."""
if not self.canClose():
return
file_dialog = QtGui.QFileDialog(caption="Open ...")
file_dialog.setNameFilter("*" + self.currentExtension)
file_dialog.setAcceptMode(QtGui.QFileDialog.AcceptOpen)
if file_dialog.exec():
file_path = file_dialog.selectedFiles()[0]
if file_path:
self.open(file_path)
[docs] def fileNew(self):
"""Restart the edition from start."""
if self.canClose():
self.filepath[self.currentContext] = ""
self.fileChanged = False
[docs] def fileSave(self):
"""Save the current sample in the last saving file.
If there is no current saving file, it calls fileSaveAs().
"""
if self.filepath[self.currentContext]:
self.save(self.filepath[self.currentContext])
else:
self.fileSaveAs()
[docs] def fileSaveAs(self):
"""Open a dialog to select the current saving file."""
file_dialog = QtGui.QFileDialog(caption="Save as ...")
file_dialog.setNameFilter("*" + self.currentExtension)
file_dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
if file_dialog.exec():
file_path = str(file_dialog.selectedFiles()[0])
if os.path.splitext(file_path)[1] != self.currentExtension:
file_path += self.currentExtension
if file_path:
self.save(file_path)
[docs] def fileClose(self):
"""Close file and create a new one."""
_logger.debug("Closing: %s", self.filepath[self.currentContext])
self.fileNew()
[docs] def quit(self):
"""Close the main window."""
_logger.debug("Quit application")
if self.canClose():
self.close()
# File (Sample) ---------------------------------------------------------------------
[docs] def openSample(self, file_path):
"""Open a HDF5 sample file.
Args:
file_path (str): Path to a file containing a sample.
"""
file_ = h5py.File(file_path, "r")
sample_group = file_.require_group("SampleGroup")
# Reading datasets
param_dset = sample_group.require_dataset("param", exact=True, shape=(9,), dtype=float)
shape_ = tuple(sample_group.require_dataset("shape", exact=True, shape=(4,), dtype=int).value)
data = sample_group.require_dataset("data", exact=True, shape=shape_, dtype=float)
# Updating changes
size = param_dset.value[0:3]
shape = [int(n) for n in param_dset.value[3:6]]
self.dataEditor.setDimension(size, shape)
lx, ly, lz = self.dataEditor.size
nx, ny, nz = self.dataEditor.shape
self.sample.Nx.setValue(nx)
self.sample.Ny.setValue(ny)
self.sample.Nz.setValue(nz)
self.sample.SizeX.setValue(lx)
self.sample.SizeY.setValue(ly)
self.sample.SizeZ.setValue(lz)
self.dataEditor.data = data.value
file_.close()
[docs] def saveSample(self, file_path):
"""Save a HDF5 sample file.
Args:
file_path (str): Path to the file where the sample will be saved.
"""
# File operations
file_ = h5py.File(file_path, "w")
group = file_.create_group("SampleGroup")
# Get parameters from context
parameters = [param.value() for param in self.sample]
# Creating datasets
param = group.create_dataset("param", data=parameters)
shape = group.create_dataset("shape", data=self.dataEditor.data.shape[0:4])
data = group.create_dataset("data", data=self.dataEditor.data[:, :, :, :])
# File operations
file_.flush()
file_.close()
# File (System) ---------------------------------------------------------------------
[docs] def openSystem(self, file_path):
"""Open a HDF5 system file.
Args:
file_path (str): Path to a file containing the system.
"""
# File operations
file_ = h5py.File(file_path, "r")
group = file_.require_group("SystemGroup")
# Reading datasets
resolution = group.require_dataset("shape", exact=True, shape=(), dtype=int).value
shape_ = (resolution, resolution, resolution, 3)
nucleus = group.require_dataset("nucleus", exact=True, shape=(), dtype=object).value
magnet = group.require_dataset("magnet", shape=(), dtype=float).value
data = group.require_dataset("data", exact=True, shape=shape_, dtype=float)
# Updating changes
self.magnet.magneticStrength.setValue(magnet)
self.magnet.resolution.setValue(resolution)
self.nucleus.nucleus.setValue(nucleus)
default_shape = [resolution, resolution, resolution]
default_size = [self.settings.magnet_config_group.SizeX.value(),
self.settings.magnet_config_group.SizeY.value(),
self.settings.magnet_config_group.SizeZ.value()]
# Set data
# Todo: insert magnet size inside the file
self.dataEditor.setDimension(default_size, default_shape)
self.dataEditor.data = data.value
# File operations
file_.close()
[docs] def saveSystem(self, file_path):
"""Save a HDF5 system file.
Args:
file_path (str): Path to the file where the system will be saved.
"""
# File operations
file_ = h5py.File(file_path, "w")
group = file_.create_group("SystemGroup")
# Creating datasets
shape = group.create_dataset("shape", data=self.magnet.resolution.value())
nucleus = group.create_dataset("nucleus", data=self.nucleus.nucleus.value())
magnet = group.create_dataset("magnet", data=self.magnet.magneticStrength.value(), dtype=float)
data = group.create_dataset("data", data=self.dataEditor.data[:, :, :, :])
# File operations
file_.flush()
file_.close()
# File (Sequence) ---------------------------------------------------------------------
[docs] def openSequence(self, file_path):
"""Open a python sequence file.
The file must contain a class SequenceExample, that contains
information about the sequence as RF pulses and the Gradient.
Args:
file_path (str): Path to a file containing the sequence.
"""
imp.load_source("sequence", file_path)
import sequence
self.sequenceExample = sequence.SequenceExample(self.settings, self.nucleus)
for param in self.sequenceExample:
param.sigValueChanged.connect(self.plotSequence)
self.parameterTree.clear()
self.parameterTree.addParameters(self.sequenceExample)
self.plotSequence()
[docs] def updateExplorer(self):
"""Update explorer files for current context."""
try:
context_dir = os.path.dirname(self.filepath[self.currentContext])
_logger.info("Context dir: %s", context_dir)
except KeyError as err:
context_dir = None
_logger.info("Cannot load explorer for context %s. Maybe it does "
"not have one, it is normal.", self.currentContext)
results = []
num_files = 0
# Search for files
if context_dir is not None:
file_match = '*' + self.currentExtension
results = [y for x in os.walk(context_dir) for y in glob(os.path.join(x[0], file_match))]
num_files = len(results)
self.ui.listWidgetExplorer.clear()
self.ui.dockWidgetExplorer.setVisible(bool(num_files))
# Populate list with filenames, tool tip is the filepath.
for filepath in results:
filename = os.path.basename(filepath)
item = QtGui.QListWidgetItem(filename, self.ui.listWidgetExplorer)
item.setToolTip(filepath)
self.ui.listWidgetExplorer.addItem(item)
[docs] def about(self):
"""Show about message."""
title = "About {} v{}".format(mrsprint.project, mrsprint.__version__)
message = ("{}\n\n"
"Authors: {}\n"
"Emails: {}\n\n"
"License: {}\n"
"Copyright: {}\n\n"
"Project: {}\n"
"Documentation: {}\n"
"PyPI: {}").format(mrsprint.description,
mrsprint.authors_string,
mrsprint.emails_string,
mrsprint.license,
mrsprint.copyright,
mrsprint.url,
mrsprint.rtd_url,
mrsprint.pypi_url)
message_box = QtGui.QMessageBox(QtGui.QMessageBox.Information, title, message)
message_box.exec()
[docs] def closeEvent(self, event):
"""Close event."""
self._writeConfigFile()