Skip to content
Merged
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
10 changes: 10 additions & 0 deletions trace/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@

datetime_pv = loaded_json["datetime_pv"]

# Set default save file directory
# If the directory does not exist, set it to the home directory
save_file_dir = Path(os.path.expandvars(loaded_json["save_file_dir"]))
if not save_file_dir.is_dir():
logger.warning(f"Config file's save_file_dir path does not exist: {save_file_dir}")
save_file_dir = Path.home()
logger.warning(f"Setting save_file_dir to home: {save_file_dir}")

# Set default color palette
color_palette = [QColor(hex_code) for hex_code in loaded_json["colors"]]

# Set the default thread count for numexpr
# 8 is determined to be a safe default for most systems according to numxerpr documentation
numexpr_threads = os.environ.get("NUMEXPR_MAX_THREADS", None)
if numexpr_threads is None:
os.environ["NUMEXPR_MAX_THREADS"] = "8"
logger.debug("NUMEXPR_MAX_THREADS not set, defaulting to 8")
78 changes: 68 additions & 10 deletions trace/widgets/data_insight_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Slot,
Signal,
QObject,
QThread,
QModelIndex,
QAbstractTableModel,
)
Expand Down Expand Up @@ -52,13 +53,38 @@
handler.setLevel("DEBUG")


class CAGetThread(QThread):
"""Thread for making a CA get request to the given address. This is used
to get the description of the curve.
"""

result_ready = Signal(object)

def __init__(self, parent: QObject = None, address: str = "") -> None:
super().__init__(parent=parent)
self.address = address
self.stop_flag = False

def run(self) -> None:
value = epics.caget(self.address)

if self.stop_flag:
return
self.result_ready.emit(value)

def stop(self) -> None:
"""Set the stop flag"""
self.stop_flag = True


class DataVisualizationModel(QAbstractTableModel):
"""Table Model for fetching and storing the data for a given curve on the
model. Gathers live data directly from the curve, but makes an HTTP request
to the Archiver Appliance
"""

reply_recieved = Signal()
description_changed = Signal()

def __init__(self, parent: QObject = None) -> None:
super().__init__(parent)
Expand All @@ -67,6 +93,7 @@ def __init__(self, parent: QObject = None) -> None:
self.address = None
self.unit = None
self.description = None
self.caget_thread = None

self.network_manager = QNetworkAccessManager()
self.network_manager.finished.connect(self.recieve_archive_reply)
Expand Down Expand Up @@ -97,6 +124,18 @@ def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDat
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.df.columns[section]

def set_description(self, description: str) -> None:
"""Set the description of the curve. This is called when the CAGetThread
emits a result_ready signal.

Parameters
----------
description : str
The description of the curve
"""
self.description = description
self.description_changed.emit()

def set_all_data(self, curve_item: TimePlotCurveItem, x_range: Iterable[int]) -> None:
"""Set the model's data for the given curve and the given time range.
This function determines what kind of data should be saved and prompts
Expand All @@ -110,9 +149,18 @@ def set_all_data(self, curve_item: TimePlotCurveItem, x_range: Iterable[int]) ->
x_range : Iterable[int]
The time range to collect and store data between
"""
self.address = curve_item.address
self.address = curve_item.address if curve_item.address else ""
self.unit = curve_item.units
self.description = epics.caget(curve_item.address + ".DESC")

# Set the meta data label of the DataInsightTool
self.set_description("Loading...")

# Create a new CAGetThread to get the description of the curve
if isinstance(self.caget_thread, CAGetThread) and self.caget_thread.isRunning():
self.caget_thread.stop()
self.caget_thread = CAGetThread(self, self.address + ".DESC")
self.caget_thread.result_ready.connect(self.set_description)
self.caget_thread.start()

curve_range = (curve_item.min_x(), curve_item.max_x())
left_ts = max(x_range[0], curve_range[0])
Expand Down Expand Up @@ -140,8 +188,11 @@ def set_live_data(self, curve_item: TimePlotCurveItem, x_range: Iterable[int]) -
x_range : Iterable[int]
The time range to collect and store data between
"""
data_n = curve_item.getBufferSize()
data = curve_item.data_buffer[:, :data_n]
data_n = curve_item.points_accumulated
if data_n == 0:
return

data = curve_item.data_buffer[:, -data_n:]
indices = np.where((x_range[0] <= data[0]) & (data[0] <= x_range[1]))[0]

convert_data = {"Datetime": [], "Value": [], "Severity": []}
Expand Down Expand Up @@ -204,8 +255,11 @@ def recieve_archive_reply(self, reply: QNetworkReply) -> None:
self.reply_recieved.emit()
if reply.error() == QNetworkReply.NoError:
bytes_str = reply.readAll()
data_dict = json.loads(str(bytes_str, "utf-8"))
self.set_archive_data(data_dict)
try:
data_dict = json.loads(str(bytes_str, "utf-8"))
self.set_archive_data(data_dict)
except json.JSONDecodeError:
logger.warning("Data Insight Tool: No data received from archiver")
else:
logger.debug(
f"Request for data from archiver failed, request url: {reply.url()} retrieved header: "
Expand Down Expand Up @@ -304,6 +358,7 @@ def __init__(self, parent: QObject, plot: PyDMArchiverTimePlot = None) -> None:
self.layout_init()

self.data_vis_model.reply_recieved.connect(self.loading_label.hide)
self.data_vis_model.description_changed.connect(self.set_meta_data)
self.export_button.clicked.connect(self.export_data_to_file)
self.pv_select_box.currentIndexChanged.connect(self.get_data)
self.refresh_button.clicked.connect(self.get_data)
Expand Down Expand Up @@ -360,11 +415,12 @@ def layout_init(self) -> None:

def set_meta_data(self) -> None:
"""Populate the meta_data_label with the curve's unit (if any) and description."""
meta_str = ""
meta_labels = []
if self.data_vis_model.unit:
meta_str = self.data_vis_model.unit + ", "
meta_str += self.data_vis_model.description
self.meta_data_label.setText(meta_str)
meta_labels.append(str(self.data_vis_model.unit))
if self.data_vis_model.description:
meta_labels.append(str(self.data_vis_model.description))
self.meta_data_label.setText(", ".join(meta_labels))

def combobox_to_curve(self, combobox_ind: int) -> ArchivePlotCurveItem:
"""Convert an index for the pv_select_box combobox to the corresponding
Expand All @@ -389,7 +445,9 @@ def update_pv_select_box(self) -> None:
"""Populate the pv_select_box with all curves in the plot. This is called
when the plot is updated.
"""
self.pv_select_box.blockSignals(True)
self.pv_select_box.clear()
self.pv_select_box.blockSignals(False)
curve_names = [c.address for c in self.plot._curves if isinstance(c, ArchivePlotCurveItem)]
self.pv_select_box.addItems(curve_names)

Expand Down