Skip to content
Merged
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
60 changes: 58 additions & 2 deletions trace/widgets/data_insight_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from qtpy.QtWidgets import (
QLabel,
QWidget,
QCheckBox,
QComboBox,
QFileDialog,
QHBoxLayout,
Expand Down Expand Up @@ -101,6 +102,7 @@ def __init__(self, parent: QObject = None) -> None:
self.unit = None
self.description = None
self.caget_thread = None
self._decode_as_string = False

self.network_manager = QNetworkAccessManager()
self.network_manager.finished.connect(self.recieve_archive_reply)
Expand All @@ -123,6 +125,8 @@ def data(self, index: QModelIndex, role: Qt.ItemDataRole = Qt.DisplayRole) -> st
return None
elif role == Qt.DisplayRole:
val = self.df.iat[index.row(), index.column()]
if index.column() == 1 and self.decode_as_string:
val = self.list_to_ascii(val)
return str(val)
return None

Expand All @@ -131,6 +135,20 @@ def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDat
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.df.columns[section]

@property
def decode_as_string(self) -> bool:
"""weather or not to show the value column as a string or raw data"""
return self._decode_as_string

@decode_as_string.setter
def decode_as_string(self, decode_as_string: bool):
"""set the value column to display as string or raw data"""
if decode_as_string != self._decode_as_string:
self._decode_as_string = decode_as_string
start = self.index(0, 1)
end = self.index(self.rowCount(), 1)
self.dataChanged.emit(start, end)

def set_description(self, description: str) -> None:
"""Set the description of the curve. This is called when the CAGetThread
emits a result_ready signal.
Expand Down Expand Up @@ -267,7 +285,6 @@ def recieve_archive_reply(self, reply: QNetworkReply) -> None:
reply : QNetworkReply
Reply to the network request made in request_archive_data
"""
self.reply_recieved.emit()
if reply.error() == QNetworkReply.NoError:
bytes_str = reply.readAll()
try:
Expand All @@ -280,6 +297,7 @@ def recieve_archive_reply(self, reply: QNetworkReply) -> None:
f"Request for data from archiver failed, request url: {reply.url()} retrieved header: "
f"{reply.header(QNetworkRequest.ContentTypeHeader)} error: {reply.error()}"
)
self.reply_recieved.emit()
reply.deleteLater()

def set_archive_data(self, data_dict: dict) -> None:
Expand Down Expand Up @@ -333,9 +351,12 @@ def export_data(self, file_path: Path, extension: str) -> None:

header_dict = {"Address": self.address, "Unit": self.unit, "Description": self.description}

export_df = self.df.copy()
export_df = self.df.copy(deep=True)
export_df["Datetime"] = export_df["Datetime"].astype("int64") / 1e9

if self.decode_as_string:
export_df["Value"] = export_df["Value"].apply(self.list_to_ascii)

if extension == ".csv":
file_header = "".join([f"{k}: {v}\n" for k, v in header_dict.items()])
with file_path.open("w") as file:
Expand All @@ -352,6 +373,20 @@ def export_data(self, file_path: Path, extension: str) -> None:
with file_path.open("w") as file:
json.dump(export_dict, file, indent=2)

def has_waveform_data(self) -> bool:
"""Return True if any value in the Value column is a list or numpy array"""
return bool(self.df["Value"].apply(lambda v: isinstance(v, (list, np.ndarray))).any())

@staticmethod
def list_to_ascii(val: list[int]) -> str:
"""Convert a list of integers into an ASCII string, ignoring null characters"""
if not isinstance(val, (list, np.ndarray)):
return str(val)
characters = map(chr, list(val))
string = "".join(characters)
string = string.replace("\u0000", "")
return string


class DataInsightTool(QWidget):
"""The Data Insight Tool is a standalone widget that allows users to display
Expand All @@ -370,8 +405,10 @@ def __init__(self, parent: QObject, plot: PyDMArchiverTimePlot = None) -> None:
self.unopened = True

self.data_vis_model.reply_recieved.connect(self.loading_label.hide)
self.data_vis_model.reply_recieved.connect(self.update_decode_as_string_visibility)
self.data_vis_model.description_changed.connect(self.set_meta_data)
self.export_button.clicked.connect(self.export_data_to_file)
self.decode_as_string_checkbox.toggled.connect(self.set_decode_as_string)
self.pv_select_box.currentIndexChanged.connect(self.get_data)
self.refresh_button.clicked.connect(self.get_data)

Expand Down Expand Up @@ -414,6 +451,11 @@ def layout_init(self) -> None:
self.meta_data_label = QLabel()
self.metadata_layout.addWidget(self.meta_data_label, alignment=Qt.AlignLeft)

self.decode_as_string_checkbox = QCheckBox()
self.decode_as_string_checkbox.setText("Decode As String")
self.decode_as_string_checkbox.hide()
self.metadata_layout.addWidget(self.decode_as_string_checkbox)

self.refresh_button = QPushButton("Refresh Data")
self.metadata_layout.addWidget(self.refresh_button, alignment=Qt.AlignRight)
self.main_layout.addLayout(self.metadata_layout)
Expand Down Expand Up @@ -458,6 +500,19 @@ def combobox_to_curve(self, combobox_ind: int) -> ArchivePlotCurveItem:
combobox_ind = self.pv_select_box.currentIndex()
return self.plot.curveAtIndex(combobox_ind)

def set_decode_as_string(self) -> None:
"""set the decode_as_string flag on the self.data_vis_model based off of the self.decode_as_string_checkbox
then emit the dataChanged signal from the data_vis_model for the value column"""
if self.decode_as_string_checkbox.isChecked():
self.data_vis_model.decode_as_string = True
else:
self.data_vis_model.decode_as_string = False

@Slot()
def update_decode_as_string_visibility(self) -> None:
"""Show the decode_as_string_checkbox only when the model's Value column contains arrays"""
self.decode_as_string_checkbox.setVisible(self.data_vis_model.has_waveform_data())

@Slot()
def update_pv_select_box(self) -> None:
"""Populate the pv_select_box with all curves in the plot. This is called
Expand Down Expand Up @@ -509,6 +564,7 @@ def get_data(self, combobox_index: int = -1) -> None:
curve_item = self.combobox_to_curve(combobox_index)
x_range = self.plot.getXAxis().range

self.decode_as_string_checkbox.hide()
self.data_vis_model.set_all_data(curve_item, x_range)
self.set_meta_data()
self.loading_label.show()
Loading