Source code for pylorenzmie.lmtool.ProfileWidget
import numpy as np
from numpy.typing import NDArray
import pyqtgraph as pg
from pyqtgraph.Qt.QtCore import (Qt, pyqtProperty)
from pylorenzmie.theory import LorenzMie
[docs]
class ProfileWidget(pg.PlotWidget):
'''Radial-profile plot showing experimental data and model prediction.'''
def __init__(self,
*args,
model: LorenzMie | None = None,
radius: int = 100,
**kwargs) -> None:
super().__init__(*args, **kwargs)
self._configurePlot()
self.model = LorenzMie() if model is None else model
self._data = None
self._stdev = None
self.radius = radius
def _configurePlot(self) -> None:
self.setBackground('w')
self.showGrid(True, True, 0.2)
opts = {'font-size': '14pt', 'color': 'gray'}
self.setLabel('bottom', 'r [pixels]', **opts)
self.setLabel('left', 'b(r)', **opts)
pen = pg.mkPen('k', width=3, style=Qt.PenStyle.DashLine)
self.addLine(y=1, pen=pen)
pen = pg.mkPen('k', width=3)
self.getAxis('bottom').setPen(pen)
self.getAxis('left').setPen(pen)
self.theory = pg.PlotCurveItem(pen=pg.mkPen('r', width=3))
self.experiment = pg.PlotCurveItem(pen=pg.mkPen('k', width=3))
pen = pg.mkPen('k', width=1, style=Qt.PenStyle.DashLine)
self.upper = pg.PlotCurveItem(pen=pen)
self.lower = pg.PlotCurveItem(pen=pen)
brush = pg.mkBrush(255, 165, 0, 128)
self.region = pg.FillBetweenItem(self.upper, self.lower, brush)
self.addItem(self.theory)
self.addItem(self.experiment)
self.addItem(self.upper)
self.addItem(self.lower)
self.addItem(self.region)
@pyqtProperty(dict)
def properties(self) -> dict[str, LorenzMie.Property]:
return self.model.properties
@properties.setter
def properties(self, properties: dict[str, LorenzMie.Property]) -> None:
# Profile is always centred at the origin, so x_p/y_p are irrelevant.
properties = {k: v for k, v in properties.items()
if k not in ('x_p', 'y_p')}
if len(properties) == 0:
return
self.model.properties = properties
self.plotTheory()
@pyqtProperty(LorenzMie)
def model(self) -> LorenzMie:
return self._model
@model.setter
def model(self, model: LorenzMie) -> None:
# Profile is always computed at the origin; zero the position in-place.
model.x_p = 0.0
model.y_p = 0.0
self._model = model
@pyqtProperty(tuple)
def data(self) -> tuple[NDArray[float], NDArray[float]]:
return (self._data, self._stdev)
@data.setter
def data(self, data: tuple[NDArray[float], NDArray[float]]) -> None:
self._data, self._stdev = data
self.plotData()
@pyqtProperty(int)
def radius(self) -> int:
return self._radius
@radius.setter
def radius(self, radius: int) -> None:
self._radius = radius
self.model.coordinates = np.arange(radius)
self.plotData()
self.plotTheory()
[docs]
def plotData(self) -> None:
if self._data is None:
return
radius = min(self.radius, len(self._data))
data = self._data[0:radius]
stdev = self._stdev[0:radius]
self.experiment.setData(data)
self.upper.setData(data + stdev)
self.lower.setData(data - stdev)
[docs]
@classmethod
def example(cls) -> None:
from pylorenzmie.theory import AberratedLorenzMie as model
app = pg.mkQApp()
widget = cls()
widget.model = model()
widget.radius = 150
widget.show()
app.exec()
if __name__ == '__main__': # pragma: no cover
ProfileWidget.example()