Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added 20*log10(mag) for Pyqtgraph #395

Merged
merged 20 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fc27f24
Added Log(mag**2)
jeremiahnlin May 24, 2023
a1c566a
Merge branch 'log_scale' of https://github.com/jeremiahnlin/plottr in…
jeremiahnlin May 24, 2023
3767bb0
changed label of logmag
jeremiahnlin May 24, 2023
af3a0cf
added log lines to new complexRepresentation
jeremiahnlin May 25, 2023
0091ebb
made so the complexRepresentation did not have log scale for phase, m…
jeremiahnlin May 25, 2023
e50f25c
changed complexRepresentation to 20*log10(mag) and took out logarithm…
jeremiahnlin May 26, 2023
738a4fd
Merge branch 'toolsforexperiments:master' into log_scale
jeremiahnlin May 26, 2023
f4c81e7
made style revisions
jeremiahnlin May 26, 2023
6a7c1b0
more style edits
jeremiahnlin May 26, 2023
98a7b31
Merge branch 'toolsforexperiments:master' into log_scale
jeremiahnlin May 31, 2023
d28f399
disabled logmag for two independent axes
jeremiahnlin Jun 7, 2023
e8ce42b
changed so non-imaginary data will only have the option for real comp…
jeremiahnlin Jun 7, 2023
d523cee
Merge branch 'toolsforexperiments:master' into log_scale
jeremiahnlin Jun 7, 2023
f8260f0
stylistic changes
jeremiahnlin Jun 7, 2023
875fc75
mypy check fix maybe?
jeremiahnlin Jun 7, 2023
9fa3772
maybe fixed the mypy issues
jeremiahnlin Jun 7, 2023
d4ad24b
style changes and fixed crashing error
jeremiahnlin Jun 9, 2023
a5638bc
fixed minor naming issue
jeremiahnlin Jun 9, 2023
41dad4d
helped readability with _1dPlot function
jeremiahnlin Jun 9, 2023
d5c1ed2
changed readability of _1dPlot
jeremiahnlin Jun 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions plottr/plot/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ class PlotDataType(Enum):
#: grid data with 2 dependents
grid2d = auto()

#: logarithmic scatter-type data with 1 dependent (data is not on a grid)
log10_scatter1d = auto()

#: logarithmic line data with 1 dependent (data is on a grid)
log10_line1d = auto()


class ComplexRepresentation(LabeledOptions):
"""Options for plotting complex-valued data."""
Expand All @@ -264,6 +270,9 @@ class ComplexRepresentation(LabeledOptions):
#: magnitude and phase
magAndPhase = "Mag/Phase"

#: Natural Logarithmic magnitude and phase
log_MagAndPhase = "logMag/Phase"


def determinePlotDataType(data: Optional[DataDictBase]) -> PlotDataType:
"""
Expand Down Expand Up @@ -463,6 +472,35 @@ def _splitComplexData(self, plotItem: PlotItem) -> List[PlotItem]:

return [re_plotItem, im_plotItem]

elif self.complexRepresentation == ComplexRepresentation.log_MagAndPhase:
data = plotItem.data[-1]

# this check avoids a numpy ComplexWarning when we're working with MaskedArray (almost always)
mag_data = np.ma.abs(data).real if isinstance(data, np.ma.MaskedArray) else np.abs(data)
phase_data = np.angle(data)

if label == '':
mag_label, phase_label = '20*log10(Mag)', 'Phase'
else:
mag_label, phase_label = label + ' 20*log10(Mag)', label + ' (Phase)'

mag_plotItem = plotItem
phase_plotItem = deepcopy(mag_plotItem)

mag_plotItem.data[-1] = mag_data
phase_plotItem.data[-1] = phase_data
phase_plotItem.id = mag_plotItem.id + 1
phase_plotItem.subPlot = mag_plotItem.subPlot + 1

# this is a bit of a silly check (see top of the function -- should certainly be True!).
# but it keeps mypy happy.
assert isinstance(mag_plotItem.labels, list)
mag_plotItem.labels[-1] = mag_label
assert isinstance(phase_plotItem.labels, list)
phase_plotItem.labels[-1] = phase_label

return [mag_plotItem, phase_plotItem]

else: # means that self.complexRepresentation is ComplexRepresentation.magAndPhase:
data = plotItem.data[-1]

Expand Down
101 changes: 83 additions & 18 deletions plottr/plot/pyqtgraph/autoplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,15 @@ def plot(self, plotItem: PlotItem) -> None:
plotItem.plotDataType = PlotDataType.scatter1d
elif len(plotItem.data) == 3:
plotItem.plotDataType = PlotDataType.scatter2d

if plotItem.plotDataType in [PlotDataType.scatter1d, PlotDataType.line1d]:

#If the Complex Representation is correct
if self.complexRepresentation == ComplexRepresentation.log_MagAndPhase:

#Switch the 1d plots to the logarithmic variation
if plotItem.plotDataType == PlotDataType.scatter1d and plotItem.subPlot == 0: plotItem.plotDataType = PlotDataType.log10_scatter1d
if plotItem.plotDataType == PlotDataType.line1d and plotItem.subPlot == 0: plotItem.plotDataType = PlotDataType.log10_line1d
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert these 2 lines into 4. There is no need to save on vertical space


if plotItem.plotDataType in [PlotDataType.scatter1d, PlotDataType.line1d,PlotDataType.log10_line1d,PlotDataType.log10_scatter1d]:
self._1dPlot(plotItem)
elif plotItem.plotDataType == PlotDataType.grid2d:
self._colorPlot(plotItem)
Expand All @@ -212,11 +219,22 @@ def _1dPlot(self, plotItem: PlotItem) -> None:
return subPlot.plot.plot(x.flatten(), y.flatten(), name=name,
pen=mkPen(color, width=1), symbol=symbol, symbolBrush=color,
symbolPen=None, symbolSize=symbolSize)
else:
elif plotItem.plotDataType == PlotDataType.log10_line1d:
name = plotItem.labels[-1] if isinstance(plotItem.labels, list) else ''
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax is cool, but it makes it harder to read compared to a normal if else block

return subPlot.plot.plot(x.flatten(), 20*np.log10(y.flatten()), name=name,
pen=mkPen(color, width=1), symbol=symbol, symbolBrush=color,
symbolPen=None, symbolSize=symbolSize)
elif plotItem.plotDataType == PlotDataType.scatter1d:
name = plotItem.labels[-1] if isinstance(plotItem.labels, list) else ''
return subPlot.plot.plot(x.flatten(), y.flatten(), name=name,
pen=None, symbol=symbol, symbolBrush=color,
symbolPen=None, symbolSize=symbolSize)
#instance of PlotDataType.log10_scatter1d
else:
name = plotItem.labels[-1] if isinstance(plotItem.labels, list) else ''
return subPlot.plot.plot(x.flatten(), 20*np.log10(y.flatten()), name=name,
pen=None, symbol=symbol, symbolBrush=color,
symbolPen=None, symbolSize=symbolSize)

def _colorPlot(self, plotItem: PlotItem) -> None:
subPlot = self.subPlotFromId(plotItem.subPlot)
Expand All @@ -226,6 +244,7 @@ def _colorPlot(self, plotItem: PlotItem) -> None:
def _scatterPlot2d(self, plotItem: PlotItem) -> None:
subPlot = self.subPlotFromId(plotItem.subPlot)
assert isinstance(subPlot, PlotWithColorbar) and len(plotItem.data) == 3
assert not self.complexRepresentation == ComplexRepresentation.log_MagAndPhase
subPlot.setScatter2d(*plotItem.data)


Expand Down Expand Up @@ -312,6 +331,22 @@ def _plotData(self, **kwargs: Any) -> None:
self.fmWidget.setTitle(self.data.meta_val('title'))
self.title = self.data.meta_val('title')

#update FigOptions numAxes and imagData
self.figOptions.numAxes = len(inds)
for val in dvals:
if isinstance(val, np.complex128):
if not val.imag == 0:
self.figOptions.imagData = True
break
if not all(val.imag == 0):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is currently crashing on my machine

self.figOptions.imagData = True
break
#Assertions to make mypy happy
assert self.figConfig is not None
assert self.figConfig.updateComplexButton() is not None

self.figConfig.updateComplexButton()

@Slot()
def _refreshPlot(self) -> None:
self._plotData()
Expand Down Expand Up @@ -346,7 +381,7 @@ def onfigSaved(self) -> None:
screenshot.save(str(path.parent)+'/'+filename, format='PNG')
return

logger.error("Could not find the path of the figuer. Figure has not been saved")
logger.error("Could not find the path of the figure. Figure has not been saved")

# TODO: Allow for the option to choose filetypes and the name/directory

Expand All @@ -361,6 +396,10 @@ class FigureOptions:
#: how to represent complex data
complexRepresentation: ComplexRepresentation = ComplexRepresentation.realAndImag

numAxes: int = 0

imagData: bool = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be comments explaining what they are? they look pretty self explanatory but maybe?



class FigureConfigToolBar(QtWidgets.QToolBar):
"""Simple toolbar to configure the figure."""
Expand Down Expand Up @@ -394,11 +433,39 @@ def __init__(self, options: FigureOptions,
lambda: self._setOption('combineLinePlots',
combineLinePlots.isChecked())
)
complexOptions = QtWidgets.QMenu(parent=self)
complexGroup = QtWidgets.QActionGroup(complexOptions)
complexGroup.setExclusive(True)
self._createComplexRepresentation()

# Adding functionality to copy and save the graph
self.copyFig = self.addAction('Copy Figure', self._copyFig)
self.saveFig = self.addAction('Save Figure', self._saveFig)


def _setOption(self, option: str, value: Any) -> None:
setattr(self.options, option, value)
self.optionsChanged.emit()

def _copyFig(self) -> None:
self.figCopied.emit()

def _saveFig(self) -> None:
self.figSaved.emit()

def _createComplexRepresentation(self) -> bool:
#constructs/reconstructs the Complex Button with different viewing options based upon input data

complexOptions = QtWidgets.QMenu(parent=self)
complexGroup = QtWidgets.QActionGroup(complexOptions)
complexGroup.setExclusive(True)

for k in ComplexRepresentation:

#Checks instance of non-imaginary data (to only enable real view) and 2 independent variables (to disable logMag view)
if not self.options.imagData and not k == ComplexRepresentation.real: continue
if self.options.numAxes == 2 and k == ComplexRepresentation.log_MagAndPhase: continue

a = QtWidgets.QAction(k.label, complexOptions)
a.setCheckable(True)
complexGroup.addAction(a)
Expand All @@ -413,18 +480,16 @@ def __init__(self, options: FigureOptions,
complexButton.setText('Complex')
complexButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
complexButton.setMenu(complexOptions)
self.addWidget(complexButton)

# Adding functionality to copy and save the graph
self.copyFig = self.addAction('Copy Figure', self._copyFig)
self.saveFig = self.addAction('Save Figure', self._saveFig)

def _setOption(self, option: str, value: Any) -> None:
setattr(self.options, option, value)
self.optionsChanged.emit()

def _copyFig(self) -> None:
self.figCopied.emit()

def _saveFig(self) -> None:
self.figSaved.emit()
#stylistic edit to ensure that complexButton is the second button, also to ensure that the updateComplexButton removes the correct button
if len(self.actions()) == 1:
self.addWidget(complexButton)
else:
self.insertAction(self.actions()[1],self.addWidget(complexButton))
return True

def updateComplexButton(self) -> bool:
#remove the second action in the list (currently corresponding to the complexRepresentation button)
self.removeAction(self.actions()[1])
self._createComplexRepresentation()
return True