Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 2 additions & 12 deletions src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from sas.qtgui.Plotting.PlotterData import Data1D
from sas.qtgui.Utilities.BackgroundColor import BG_DEFAULT, BG_ERROR
from sas.qtgui.Utilities.ExtrapolationSlider import ExtrapolationSlider, SliderPerspective
from sas.qtgui.Utilities.Reports import ReportBase
from sas.qtgui.Utilities.Reports import ReportBase, format_report_parameters
from sas.qtgui.Utilities.Reports.reportdata import ReportData
from sas.sascalc.corfunc.calculation_data import (
GuinierData,
Expand Down Expand Up @@ -1046,17 +1046,7 @@ def getReport(self) -> ReportData | None:
report.add_data_details(self.data)

# Format keys
parameters = self.getState()
fancy_parameters = {}

for key in parameters:
nice_key = " ".join([s.capitalize() for s in key.split("_")])
if parameters[key].strip() == '':
fancy_parameters[nice_key] = '-'
else:
fancy_parameters[nice_key] = parameters[key]

report.add_table_dict(fancy_parameters, ("Parameter", "Value"))
report.add_table_dict(format_report_parameters(self.getState()), ("Parameter", "Value"))
report.add_plot(self.q_space_figure)
report.add_plot(self.real_space_figure)
report.add_plot(self.extraction_figure)
Expand Down
35 changes: 12 additions & 23 deletions src/sas/qtgui/Perspectives/Fitting/ReportPageLogic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import html2text
from bumps import options
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure

from sasmodels import __version__ as SASMODELS_VERSION

import sas.qtgui.Plotting.PlotHelper as PlotHelper
import sas.qtgui.Utilities.GuiUtils as GuiUtils
from sas.qtgui.Plotting.PlotterBase import PlotterBase
from sas.qtgui.Utilities.Reports.reportdata import ReportData
from sas.system.version import __version__ as SASVIEW_VERSION

Expand Down Expand Up @@ -80,12 +80,11 @@ def reportHeader(self) -> str:

return report

def buildPlotsForReport(self, images: list[PlotterBase]) -> str:
def buildPlotsForReport(self, images: list[Figure]) -> str:
""" Convert Matplotlib figure 'fig' into a <img> tag for HTML use using base64 encoding. """
html = FEET_1.format(self.data.name)

for fig in images:
canvas = FigureCanvas(fig)
png_output = BytesIO()
try:
dpi = 150 if sys.platform == "darwin" else 75
Expand All @@ -98,7 +97,6 @@ def buildPlotsForReport(self, images: list[PlotterBase]) -> str:
feet = FEET_3 if sys.platform == "darwin" else FEET_2
html += feet.format(data_to_print) + ELINE
png_output.close()
del canvas
return html

def reportParams(self) -> str:
Expand Down Expand Up @@ -156,7 +154,7 @@ def getFitSmearing(self) -> str:
smear_format = CENTRE.format(f"Smearing Information: {smear_format}")
return smear_format

def getResultsPlots(self) -> list[FigureCanvas]:
def getResultsPlots(self) -> list[Figure]:
"""Gather the plots from the bumps results panel."""
plots = []
if hasattr(self.parent, 'parent') and hasattr(self.parent.parent, 'results_panel'):
Expand Down Expand Up @@ -199,27 +197,18 @@ def getBatchResults(self) -> str:
batch_results_table += '</table>'
return batch_results_table

def getImages(self) -> list[PlotterBase]:
"""Create MPL figures for the current fit"""
graphs = []
def getImages(self) -> list[Figure]:
"""Create MPL figures for the current fit.

Uses `PlotHelper.figures_for_plot_ids` to collect any live figures shown
for the dataset, then appends result-panel figures.
"""
modelname = self.kernel_module.name
if not modelname or self._index is None:
return []
plot_ids = [plot.id for plot in GuiUtils.plotsFromModel(modelname, self._index)]

# Active plots
import sas.qtgui.Plotting.PlotHelper as PlotHelper
shown_plot_names = PlotHelper.currentPlotIds()

# current_plots = list of graph names of currently shown plots
# which are related to this dataset
current_plots = [name for name in shown_plot_names if PlotHelper.plotById(name).data[0].id in plot_ids]

for name in current_plots:
# get the plotter object first
plotter = PlotHelper.plotById(name)
graphs.append(plotter.figure)

plot_ids = [plot.id for plot in GuiUtils.plotsFromModel(modelname, self._index)]
graphs = PlotHelper.figures_for_plot_ids(plot_ids)
graphs.extend(self.getResultsPlots())

return graphs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from types import TracebackType

import numpy as np
from matplotlib.figure import Figure
from PySide6 import QtCore, QtGui, QtWidgets

from sasdata.dataloader.data_info import Data1D as LoadData1D

import sas.qtgui.Plotting.PlotHelper as PlotHelper
from sas.qtgui.Perspectives.perspective import Perspective
from sas.qtgui.Perspectives.SizeDistribution.SizeDistributionLogic import (
SizeDistributionLogic,
Expand All @@ -24,6 +26,8 @@
)
from sas.qtgui.Plotting.PlotterData import Data1D
from sas.qtgui.Utilities import GuiUtils
from sas.qtgui.Utilities.Reports import ReportBase, format_report_parameters
from sas.qtgui.Utilities.Reports.reportdata import ReportData

ASPECT_RATIO: float = 1.0
DIAMETER_MIN: float = 10.0
Expand Down Expand Up @@ -124,6 +128,11 @@ def isSerializable(self) -> bool:
"""Tell the caller that this perspective writes its state."""
return True

@property
def supports_reports(self) -> bool:
"""Tell the caller that this perspective can generate reports."""
return True

def closeEvent(self, event: QtGui.QCloseEvent) -> None:
"""Overwrite QDialog close method to allow for custom widget close."""
# Close report widgets before closing/minimizing main widget
Expand Down Expand Up @@ -503,6 +512,55 @@ def getState(self) -> dict[str, str | bool]:
"scale_low_q": self.txtScaleLowQ.text(),
}

def getReport(self) -> ReportData | None:
"""Build a report for the current Size Distribution analysis."""
if self.logic.data is None:
return None

report = ReportBase("Size Distribution")
report.add_data_details(self.logic.data)

report.add_table_dict(format_report_parameters(self.getState()), ("Parameter", "Value"))

result_details: dict[str, str] = {}
if self.txtChiSq.text().strip():
result_details["Chi Squared"] = self.txtChiSq.text().strip()
if self.txtVolume.text().strip():
result_details["Volume"] = self.txtVolume.text().strip()
if self.txtDiameterMean.text().strip():
result_details["Diameter Mean"] = self.txtDiameterMean.text().strip()
if self.txtDiameterMedian.text().strip():
result_details["Diameter Median"] = self.txtDiameterMedian.text().strip()
if self.txtDiameterMode.text().strip():
result_details["Diameter Mode"] = self.txtDiameterMode.text().strip()

if result_details:
report.add_table_dict(result_details, ("Result", "Value"))

images = self.getImages()
# Add existing figures to the report (use their axes title if present)
for fig in images:
title = fig.axes[0].get_title() if fig.axes else None
report.add_plot(fig, figure_title=title)

return report.report_data

def getImages(self) -> list[Figure]:
"""Return live Matplotlib figures for the current model item.

This collects the Data1D/Data2D ids for `self._model_item` and then
delegates to `PlotHelper.figures_for_plot_ids`. Returns empty list
if no model item or no plots found.
"""
model_item = getattr(self, '_model_item', None)
if not model_item:
return []

plot_data = GuiUtils.plotsFromModel("", model_item)
plot_ids = [p.id for p in plot_data]

return PlotHelper.figures_for_plot_ids(plot_ids)

def removeData(self, data_list: list | None = None) -> None:
"""Remove the existing data reference from the Size Distribution Perspective."""
if not data_list or self._model_item not in data_list:
Expand Down
31 changes: 31 additions & 0 deletions src/sas/qtgui/Plotting/PlotHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import sys
import weakref

from matplotlib.figure import Figure

# TODO Refactor to allow typing without circular import
#from sas.qtgui.Plotting.PlotterBase import PlotterBase

Expand Down Expand Up @@ -58,3 +60,32 @@ def idOfPlot(plot):
break

return plot_id


def figures_for_plot_ids(plot_ids: list) -> list[Figure]:
"""
Return a list of Matplotlib `Figure` objects for active plotters whose data ids
are in `plot_ids`.

Notes:
- `plot_ids` may contain non-string IDs (e.g., integers) and may include `None`.
`None` values are ignored.
- Returns an empty list if no matches are found.
"""
graphs: list[Figure] = []
# filter out None so comparisons are meaningful
allowed_ids: list = [pid for pid in plot_ids if pid is not None]
if not allowed_ids:
return graphs

shown_plot_names = currentPlotIds()

for name in shown_plot_names:
plotter = plotById(name)
data = getattr(plotter, "data", None)
if data and getattr(data[0], "id", None) in allowed_ids:
fig = getattr(plotter, "figure", None)
if fig is not None:
graphs.append(fig)

return graphs
2 changes: 1 addition & 1 deletion src/sas/qtgui/Utilities/Reports/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .reportdata import ReportData
from .reports import ReportBase
from .reports import ReportBase, format_report_parameters
12 changes: 12 additions & 0 deletions src/sas/qtgui/Utilities/Reports/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@
logger = logging.getLogger(__name__)


def format_report_parameters(parameters: dict[str, Any], empty_value: str = "-") -> dict[str, Any]:
"""Return a report-friendly copy of a parameter dictionary."""
formatted_parameters: dict[str, Any] = {}
for key, value in parameters.items():
nice_key = " ".join(part.capitalize() for part in key.split("_"))
if isinstance(value, str) and value.strip() == "":
formatted_parameters[nice_key] = empty_value
else:
formatted_parameters[nice_key] = value
return formatted_parameters


#
# Utility classes
#
Expand Down
Loading