#! python
# -*- coding: utf-8 -*-
"""Module for plotting related classes and functions.
Authors:
* Victor Hugo de Mello Pessoa <victor.pessoa@usp.br>
* Daniel Cosmo Pizetta <daniel.pizetta@usp.br>
Since:
2017/07/01
"""
import logging
import numpy as np
import pyqtgraph as pg
import pyqtgraph.opengl as gl
from pyqtgraph.Qt import QtCore, QtGui
from mrsprint.simulator import (reduce_magnetization_in_frequency,
reduce_magnetization_in_position)
_logger = logging.getLogger(__name__)
[docs]class Plot():
"""Class that contains the plots of simulation.
Args:
settings (Settings): Represents the program settings.
mx (np.ndarray): Magnetization in x.
my (np.ndarray): Magnetization in y.
mz (np.ndarray): Magnetization in z.
gr (np.ndarray): Gradients of magnetic field.
tp (np.ndarray): Time.
freq_shift (np.ndarray): Frequency shift due to field inhomogeneity.
position (np.ndarray): Position of spins.
max_magnetization (float): Maximum value of magnetization.
"""
def __init__(self, settings, mx, my, mz, gr, tp, freq_shift, position, max_magnetization):
self.mx = mx
self.my = my
self.mz = mz
self.gr = gr
self.tp = tp
self.freq_shift = freq_shift
self.position = position
self.max_magnetization = max_magnetization
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update)
self.index_pos = 0
self.show3DAxes = settings.simulator_group.show3DAxes.value()
self.show3DGrid = settings.simulator_group.show3DGrid.value()
self.plot3DGrad = settings.simulator_group.plot3DGrad.value()
self.plot3DOffResMag = settings.simulator_group.plot3DOffResMag.value()
self.plot3DOffResPercentage = settings.simulator_group.plot3DOffResPercentage.value()
self.plot3DTotalMag = settings.simulator_group.plot3DTotalMag.value()
self.plot3DTotalMagZero = settings.simulator_group.plot3DTotalMagZero.value()
[docs] def plotMagnetization(self, mag_win):
"""Create the graphics of magnetization.
Args:
mag_win (pg.graphicsWindows.GraphicsWindow): Window where plot will be made.
"""
# reduce and rescale the magnetization
mx, my, mz = reduce_magnetization_in_position(self.mx, self.my, self.mz, self.position, self.freq_shift)
# transversal magnetization
self.reduced_mxy = mx + 1.0j * my
mag_mxy = np.absolute(self.reduced_mxy)
phase_mxy = np.angle(self.reduced_mxy, deg=False)
# create the graphics of magnetization
mx_plot = plot_item(mag_win, self.tp, mx, "r", False, "", "", True, "Mag (x)", "")
my_plot = plot_item(mag_win, self.tp, my, "g", False, "", "", True, "Mag (y)", "")
mz_plot = plot_item(mag_win, self.tp, mz, "b", False, "", "", True, "Mag (z)", "")
mxy_plot = plot_item(mag_win, self.tp, mag_mxy, "c", False, "", "", True, "Mag transv", "")
ph_mxy_plot = plot_item(mag_win, self.tp, phase_mxy, "c", True, "Time", "", True, "Phase", "")
ph_mxy_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')]])
# connect the visualization of the graphics
plot = [mx_plot, my_plot, mz_plot, mxy_plot, ph_mxy_plot]
for i in range(len(plot) - 1):
plot[i].getViewBox().setXLink(plot[i + 1].getViewBox())
self.timelines = [pg.InfiniteLine(pos=0, angle=90, pen="w", movable=False) for i in plot]
for i in range(len(plot)):
plot[i].addItem(self.timelines[i])
[docs] def plotSpin(self, view):
"""Create the 3D exhibition of magnetization over the experiment.
Args:
view (pg.graphicsWindows.GraphicsWindow): Window where the plot will be made.
"""
# index for animation loop
self.index = 0
# index step for reduction of the frames to increase speed
self.index_step = 20
# frequency shift array size
self.fsa_size = self.freq_shift.size - 1
# create a 3d view
max_abs_pos = np.max([np.abs(np.min(self.position)), np.max(self.position)]) + 1
view.setCameraPosition(distance=max_abs_pos * 3)
# show lines for each axis centered in 0,0,0
if self.show3DAxes:
# create axis lines for each axis
xline = gl.GLLinePlotItem(pos=np.array([0, 0, 0, max_abs_pos, 0, 0]).reshape(2, 3),
color=(255, 0, 0, 1), width=3, antialias=True)
xline.setGLOptions('additive')
yline = gl.GLLinePlotItem(pos=np.array([0, 0, 0, 0, max_abs_pos, 0]).reshape(2, 3),
color=(0, 255, 0, 1), width=3, antialias=True)
yline.setGLOptions('additive')
zline = gl.GLLinePlotItem(pos=np.array([0, 0, 0, 0, 0, max_abs_pos]).reshape(2, 3),
color=(0, 0, 255, 1), width=3, antialias=True)
zline.setGLOptions('additive')
# draw axis lines after surfaces since they may be translucent
xline.setDepthValue(10)
yline.setDepthValue(10)
zline.setDepthValue(10)
# add axis lines to the view
view.addItem(xline)
view.addItem(yline)
view.addItem(zline)
# show a 3D grid centered in 0,0,0
if self.show3DGrid:
zgrid = gl.GLGridItem(color=(255, 255, 255, 0.01))
zgrid.setGLOptions('additive')
zgrid.setDepthValue(10)
view.addItem(zgrid)
self.mag_sum = []
self.mag_pos = []
self.mag_neg = []
self.mag_zero = []
self.grad_x = []
self.grad_y = []
self.grad_z = []
self.index_pos = 0
self.mag_negs = []
self.mag_poss = []
# sum the frequency components
self.mx_sum_fs, self.my_sum_fs, self.mz_sum_fs = reduce_magnetization_in_frequency(self.mx, self.my, self.mz, self.freq_shift, self.freq_shift.size)
self.mx_sum_fs *= self.max_magnetization
self.my_sum_fs *= self.max_magnetization
self.mz_sum_fs *= self.max_magnetization
for x_pos, y_pos, z_pos in list(zip(self.position[0], self.position[1], self.position[2])):
# create lines that represents the gradients - this is dynamic
if self.plot3DGrad:
self.grad_x.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos, y_pos, z_pos]).reshape(2, 3), color=(255, 0, 0, 0.5), antialias=True, mode='lines', width=1))
self.grad_y.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos, y_pos, z_pos]).reshape(2, 3), color=(0, 255, 0, 0.5), antialias=True, mode='lines', width=1))
self.grad_z.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos, y_pos, z_pos]).reshape(2, 3), color=(0, 0, 255, 0.5), antialias=True, mode='lines', width=1))
view.addItem(self.grad_x[self.index_pos])
view.addItem(self.grad_y[self.index_pos])
view.addItem(self.grad_z[self.index_pos])
# create lines that represents the magnetization, show the summation, max and min freq shift - this is dynamic
if self.plot3DTotalMag:
self.mag_sum.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos, y_pos, z_pos]).reshape(2, 3), color=pg.glColor(255, 255, 255), antialias=True, mode='lines', width=5))
view.addItem(self.mag_sum[self.index_pos])
if self.plot3DTotalMagZero:
if (self.fsa_size >= 1):
# for more than one dimension, because of the return of bloch function
if self.position.shape[1] != 1:
self.mag_zero.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx_sum_fs[self.index_pos][0], y_pos + self.my_sum_fs[self.index_pos][0], z_pos + self.mz_sum_fs[self.index_pos][0]]).reshape(2, 3), color=(255, 255, 255, 0.3), antialias=True, mode='lines', width=10))
else:
self.mag_zero.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx_sum_fs[0], y_pos + self.my_sum_fs[0], z_pos + self.mz_sum_fs[0]]).reshape(2, 3), color=(255, 255, 255, 0.3), antialias=True, mode='lines', width=10))
else:
self.mag_zero.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx_sum_fs[0], y_pos + self.my_sum_fs[0], z_pos + self.mz_sum_fs[0]]).reshape(2, 3), color=(255, 255, 255, 0.3), antialias=True, mode='lines', width=10))
view.addItem(self.mag_zero[self.index_pos])
if (self.fsa_size >= 1) and self.plot3DOffResMag:
# for orl in range(0, int((plot3DOffResPercentage / 100.) * (self.fsa_size - 1) / 2.) + 1):
# orl = 0
self.mag_neg.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos, y_pos, z_pos]).reshape(2, 3), color=(0, 255, 255, 0.3), antialias=True, mode='lines', width=5))
self.mag_pos.append(gl.GLLinePlotItem(pos=np.array([x_pos, y_pos, z_pos, x_pos, y_pos, z_pos]).reshape(2, 3), color=(255, 0, 255, 0.3), antialias=True, mode='lines', width=5))
# must be 2d vector
# self.mag_neg.append(self.mag_neg)
# self.mag_pos.append(self.mag_pos)
view.addItem(self.mag_pos[self.index_pos])
view.addItem(self.mag_neg[self.index_pos])
self.index_pos += 1
[docs] def update(self):
"""Update magnetization position vectors and timelines on graphics dynamically."""
self.index_pos = 0
gr_max_global = np.max(self.gr)
if self.index > self.tp.size - self.index_step:
self.timer.stop()
return
for x_pos, y_pos, z_pos in list(zip(self.position[0], self.position[1], self.position[2])):
# update off resonance magnetization for each time point
if self.plot3DOffResMag:
if self.fsa_size >= 1:
orl = 0
# for more than one dimension, because of the return of the bloch function
if self.position.shape[1] != 1:
# for orl in range(0, int((plot3DOffResPercentage / 100.) * (self.fsa_size - 1) / 2.) + 1):
self.mag_neg[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx[0 + orl][self.index_pos][self.index], y_pos + self.my[0 + orl][self.index_pos][self.index], z_pos + self.mz[0 + orl][self.index_pos][self.index]]).reshape(2, 3))
self.mag_pos[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx[self.fsa_size - orl][self.index_pos][self.index], y_pos + self.my[self.fsa_size - orl][self.index_pos][self.index], z_pos + self.mz[self.fsa_size - orl][self.index_pos][self.index]]).reshape(2, 3))
else:
# for orl in range(0, int((plot3DOffResPercentage / 100.) * (self.fsa_size - 1) / 2.) + 1):
self.mag_neg[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx[0 + orl][self.index], y_pos + self.my[0 + orl][self.index], z_pos + self.mz[0 + orl][self.index]]).reshape(2, 3))
self.mag_pos[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx[self.fsa_size - orl][self.index], y_pos + self.my[self.fsa_size - orl][self.index], z_pos + self.mz[self.fsa_size - orl][self.index]]).reshape(2, 3))
# update total magnetization for each time point
if self.plot3DTotalMag:
if (self.fsa_size >= 1):
# for more than one dimension, because of the return of the bloch function
if self.position.shape[1] != 1:
self.mag_sum[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx_sum_fs[self.index_pos][self.index], y_pos + self.my_sum_fs[self.index_pos][self.index], z_pos + self.mz_sum_fs[self.index_pos][self.index]]).reshape(2, 3))
else:
self.mag_sum[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx_sum_fs[self.index], y_pos + self.my_sum_fs[self.index], z_pos + self.mz_sum_fs[self.index]]).reshape(2, 3))
else:
self.mag_sum[self.index_pos].setData(pos=np.array([x_pos, y_pos, z_pos, x_pos + self.mx_sum_fs[self.index], y_pos + self.my_sum_fs[self.index], z_pos + self.mz_sum_fs[self.index]]).reshape(2, 3))
# update gradients for each time point
if self.plot3DGrad:
self.grad_x[self.index_pos].setData(pos=np.array([x_pos, 0, 0, x_pos, 0, x_pos * self.gr[0][self.index] / gr_max_global]).reshape(2, 3))
self.grad_y[self.index_pos].setData(pos=np.array([0, y_pos, 0, 0, y_pos, y_pos * self.gr[1][self.index] / gr_max_global]).reshape(2, 3))
self.grad_z[self.index_pos].setData(pos=np.array([0, 0, z_pos, 0, 0, z_pos + z_pos * self.gr[2][self.index] / gr_max_global]).reshape(2, 3))
self.index_pos += 1
# update timeline in 2d graphics
for timeline in self.timelines:
timeline.setValue(self.tp[self.index])
self.index += self.index_step
[docs] def run(self):
"""Run the simulation on plot, from zero."""
self.index = 0
self.timer.start()
[docs] def play(self):
"""Start the simulation on plot."""
self.timer.start()
[docs] def pause(self):
"""Stop the simulation on plot."""
self.timer.stop()
[docs]def plot_item(graphics, x, y, line_color, x_axis=True, x_label="", x_unit="", y_axis=True, y_label="", y_unit=""):
"""Create a new plot item in a graph.
Args:
graphics (pl.graphicsWindow): The GraphicsWindow where the plot will be shown.
x (np.array): List of x coordinates.
y (np.array): List of y coordinates.
line_color (str): String containing the color ("r", "g", "b", "w", for example).
x_axis (bool): Visibility of x axis.
x_label (str): Label of x axis.
x_unit(str): Unit of x axis.
y_axis (bool): Visibility of y axis.
y_label (str): Label of y axis.
y_unit(str): Unit of y axis.
Returns:
pg.graphicsItems.PlotItem: Plot.
"""
item = graphics.addPlot(x=x, y=y, pen=line_color)
if x_axis:
if x_label or x_unit:
item.setLabel(axis="bottom", text=x_label, units=x_unit)
else:
item.getAxis("bottom").setStyle(showValues=False)
item.showAxis(axis="bottom")
if y_axis:
if y_label or y_unit:
item.setLabel(axis="left", text=y_label, units=y_unit)
else:
item.getAxis("left").setStyle(showValues=False)
item.showAxis(axis="left")
item.setRange(xRange=(min(x), max(x)))
item.getViewBox().setLimits(xMin=min(x), xMax=max(x))
item.getAxis("top").setStyle(showValues=False)
item.getAxis("right").setStyle(showValues=False)
item.showAxis(axis="top")
item.showAxis(axis="right")
graphics.nextRow()
return item