Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
35093e4
Setup base APIs to show dialog comparing stored array and live array
georgweiss Nov 28, 2025
2f5314b
Add pv name to dialog layout
georgweiss Dec 1, 2025
ab385da
Update comparison table when PV is connected
georgweiss Dec 1, 2025
c9bacba
Render delta value in comparison dialog
georgweiss Dec 2, 2025
4838f2b
Add styling of diff column
georgweiss Dec 2, 2025
78385ca
Adding comparison dialog title
georgweiss Dec 2, 2025
85b257d
Additional string resources
georgweiss Dec 2, 2025
e3a5f09
More styling and fixed sorting on delta column
georgweiss Dec 3, 2025
6c6eef4
Clickable delta cell to launch dialog if arrays are not equal
georgweiss Dec 3, 2025
7141bd3
Using VTypes instead of priminitives for the comparison dialog
georgweiss Dec 4, 2025
0f70bee
Handle update if live data has fewer elements than stored snapshot
georgweiss Dec 4, 2025
b97b54d
Proper handling of differences in array length in comparisoon dialog
georgweiss Dec 5, 2025
0024eed
Minor layout and code cleanup changes
georgweiss Dec 5, 2025
9a8d40c
Javadoc and code cleanup
georgweiss Dec 5, 2025
21e8517
Support for setting threshold when comparing array/table elements
georgweiss Dec 8, 2025
357bdd8
Cleanup code when comparison dialog is closed
georgweiss Dec 8, 2025
fa7bb3c
Use VTypeHelper to determine array size
georgweiss Dec 8, 2025
ba95624
Adding documentation
georgweiss Dec 8, 2025
77aacfb
Return infinite delta if arrays are of different length
georgweiss Dec 9, 2025
b764710
Merge branch 'master' into CSSTUDIO-3585
georgweiss Dec 9, 2025
fc547ad
Updated ordering and documentation
georgweiss Dec 9, 2025
d355c64
Updated ordering and documentation
georgweiss Dec 9, 2025
24f8a08
Add dimensions and non-equal count to comparison dialog
georgweiss Dec 10, 2025
d2fba8b
Do not launch comparison dialog if live PV is not connected
georgweiss Dec 10, 2025
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions app/save-and-restore/app/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,29 @@ are shown by default. The left-most columns in the toolbar can be used to show/h
.. image:: images/toggle-readback.png
:width: 80%

While comparison of scalar values in the snapshot view is straight-forward, array (or table) type data is difficult
to compare from the single table cells. User may instead click on the highlighted ":math:`{\Delta}` Live" cell to launch a dialog
showing stored, live and :math:`{\Delta}` for the selected PV:

.. image:: images/snapshot-view-with-delta.png

User clicks "Click to compare":

.. image:: images/compare-arrays.png

The threshold settings works in the same manner is in the snapshot view and operates on each element (row) in the
table view.

In case the stored and live value of the array/table data are of different dimensions, cells where no value is available
will be rendered as "---". Moreover, since in these cases an absolute delta cannot be computed, the delta column will also show
"---".

User may click the table header of the delta column to sort on the delta value to quickly find rows where either the
stored or live value is not defined (due to difference in dimension). For such rows the absolute delta will be treated
as infinite, which impacts ordering on the delta column:

.. image:: images/compare-arrays-infinite-delta.png

Restoring A Snapshot
--------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ public class Messages {
public static String buttonSearch;
public static String cannotCompareHeader;
public static String cannotCompareTitle;
public static String clickToCompare;
public static String closeConfigurationWarning;
public static String closeCompositeSnapshotWarning;
public static String closeSnapshotWarning;
public static String closeCompositeSnapshotTabPrompt;
public static String closeConfigurationTabPrompt;
public static String closeSnapshotTabPrompt;
public static String compositeSnapshotConsistencyCheckFailed;
public static String comparisonDialogLunchError;
public static String comparisonDialogTitle;
public static String contextMenuAddTag;
@Deprecated
public static String contextMenuAddTagWithComment;
Expand Down Expand Up @@ -107,6 +110,7 @@ public class Messages {
public static String importSnapshotLabel;
public static String includeThisPV;
public static String inverseSelection;
public static String live;
public static String liveReadbackVsSetpoint;
public static String liveSetpoint;
public static String login;
Expand Down Expand Up @@ -159,6 +163,7 @@ public class Messages {
public static String snapshotFromPvs;
public static String status;
public static String storedReadbackValue;
public static String stored;
public static String storedValues;
public static String tableColumnDeltaValue;
public static String tagAddFailed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
*/
package org.phoebus.applications.saveandrestore.ui;

import org.epics.vtype.VNumber;
import org.epics.vtype.VString;
import org.epics.vtype.VType;
import org.phoebus.core.vtypes.VTypeHelper;
import org.phoebus.saveandrestore.util.Threshold;
import org.phoebus.saveandrestore.util.VNoData;

import java.util.Optional;

Expand All @@ -35,7 +39,8 @@ public class VTypePair {
public final Optional<Threshold<?>> threshold;

/**
* Constructs a new pair.
* Constructs a new pair. In the context of save-and-restore snapshots, the {@link #value} field
* is used to hold a stored value, while {@link #base} holds the live PV value.
*
* @param base the base value
* @param value the value that can be compared to base
Expand All @@ -47,6 +52,39 @@ public VTypePair(VType base, VType value, Optional<Threshold<?>> threshold) {
this.threshold = threshold;
}

/**
* Computes absolute delta for the delta between {@link #base} and {@link #value}. When applied to
* {@link VString} types, {@link String#compareTo(String)} is used for comparison, but then converted to
* an absolute value.
*
* <p>
* Main use case for this is ordering on delta. Absolute delta may be more useful as otherwise zero
* deltas would be found between positive and negative deltas.
* </p>
* <p>
* If {@link #base} or {@link #value} are <code>null</code> or {@link VNoData#INSTANCE}, then
* the delta cannot be computed as a number. In this case {@link Double#MAX_VALUE} is returned
* to indicate an "infinite delta".
* </p>
* @return Absolute delta between {@link #base} and {@link #value}.
*/
public double getAbsoluteDelta(){
if(base.equals(VNoData.INSTANCE) ||
value.equals(VNoData.INSTANCE) ||
base == null ||
value == null){
return Double.MAX_VALUE;
}
if(base instanceof VNumber){
return Math.abs(((VNumber)base).getValue().doubleValue() -
((VNumber)value).getValue().doubleValue());
}
else if(base instanceof VString){
return Math.abs(((VString)base).getValue().compareTo(((VString)value).getValue()));
}
else return 0.0;
}

/*
* (non-Javadoc)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ protected void updateItem(TableEntry item, boolean empty) {
});

showDeltaPercentage.addListener((ob, o, n) -> deltaColumn.setCellFactory(e -> {
VDeltaCellEditor<VTypePair> vDeltaCellEditor = new VDeltaCellEditor<>();
VDeltaCellEditor<TableEntry, VTypePair> vDeltaCellEditor = new VDeltaCellEditor<>();
vDeltaCellEditor.setShowDeltaPercentage(n);
return vDeltaCellEditor;
}));
Expand Down Expand Up @@ -1453,7 +1453,7 @@ private void addSnapshot(Snapshot snapshot) {
"", minWidth);
deltaCol.setCellValueFactory(e -> e.getValue().compareValueProperty(additionalSnapshots.size()));
deltaCol.setCellFactory(e -> {
VDeltaCellEditor<VTypePair> vDeltaCellEditor = new VDeltaCellEditor<>();
VDeltaCellEditor<TableEntry, VTypePair> vDeltaCellEditor = new VDeltaCellEditor<>();
vDeltaCellEditor.setShowDeltaPercentage(showDeltaPercentage.get());
return vDeltaCellEditor;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@
package org.phoebus.applications.saveandrestore.ui.snapshot;

import javafx.scene.control.Tooltip;
import org.epics.vtype.VEnumArray;
import org.epics.vtype.VNumberArray;
import org.epics.vtype.VStringArray;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.ui.VTypePair;
import org.phoebus.applications.saveandrestore.ui.snapshot.compare.ComparisonDialog;
import org.phoebus.core.vtypes.VDisconnectedData;
import org.phoebus.saveandrestore.util.Utilities;
import org.phoebus.saveandrestore.util.VNoData;
import org.phoebus.ui.dialog.DialogHelper;

import java.util.Formatter;

Expand All @@ -34,7 +40,7 @@
* @param <T>
* @author Kunal Shroff
*/
public class VDeltaCellEditor<T> extends VTypeCellEditor<T> {
public class VDeltaCellEditor<S, T> extends VTypeCellEditor<S, T> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is the type parameter S added to VTypeCellEditor?

Copy link
Collaborator Author

@georgweiss georgweiss Dec 8, 2025

Choose a reason for hiding this comment

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

In order to be able to use VTypeCellEditor for both TableEntry and ColumnEntry


private final Tooltip tooltip = new Tooltip();

Expand All @@ -44,7 +50,7 @@ protected void setShowDeltaPercentage(boolean showDeltaPercentage) {
this.showDeltaPercentage = showDeltaPercentage;
}

VDeltaCellEditor() {
public VDeltaCellEditor() {
super();
}

Expand Down Expand Up @@ -72,17 +78,36 @@ public void updateItem(T item, boolean empty) {
setStyle(TableCellColors.DISCONNECTED_STYLE);
} else if (pair.value == VNoData.INSTANCE) {
setText(pair.value.toString());
} else {
} else if(pair.base == VNoData.INSTANCE){
setText(VNoData.INSTANCE.toString());
}
else {
Utilities.VTypeComparison vtc = Utilities.deltaValueToString(pair.value, pair.base, pair.threshold);
String percentage = Utilities.deltaValueToPercentage(pair.value, pair.base);
if (!percentage.isEmpty() && showDeltaPercentage) {
Formatter formatter = new Formatter();
setText(formatter.format("%g", Double.parseDouble(vtc.getString())) + " (" + percentage + ")");
} else {
setText(vtc.getString());
}
if (!vtc.isWithinThreshold()) {
if (vtc.getValuesEqual() != 0 &&
(pair.base instanceof VNumberArray ||
pair.base instanceof VStringArray ||
pair.base instanceof VEnumArray)) {
TableEntry tableEntry = (TableEntry) getTableRow().getItem();
setText(Messages.clickToCompare);
setStyle(TableCellColors.ALARM_MAJOR_STYLE);
setOnMouseClicked(e -> {
ComparisonDialog comparisonDialog = new ComparisonDialog(tableEntry.getSnapshotVal().get(), tableEntry.getConfigPv().getPvName());
DialogHelper.positionDialog(comparisonDialog, getTableView(), -400, -400);
comparisonDialog.show();
});
} else {
// Do not handle mouse clicked, e.g. if live PV is disconnected.
setOnMouseClicked(e -> {});
String percentage = Utilities.deltaValueToPercentage(pair.value, pair.base);
if (!percentage.isEmpty() && showDeltaPercentage) {
Formatter formatter = new Formatter();
setText(formatter.format("%g", Double.parseDouble(vtc.getString())) + " (" + percentage + ")");
} else {
setText(vtc.getString());
}
if (!vtc.isWithinThreshold()) {
setStyle(TableCellColors.ALARM_MAJOR_STYLE);
}
}
}
tooltip.setText(item.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@
* @param <T> {@link org.epics.vtype.VType} or {@link org.phoebus.applications.saveandrestore.ui.VTypePair}
* @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
*/
public class VTypeCellEditor<T> extends MultitypeTableCell<TableEntry, T> {
public class VTypeCellEditor<S, T> extends MultitypeTableCell<S, T> {
private final Tooltip tooltip = new Tooltip();

VTypeCellEditor() {
public VTypeCellEditor() {

setConverter(new StringConverter<>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2025 European Spallation Source ERIC.
*/

package org.phoebus.applications.saveandrestore.ui.snapshot.compare;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.epics.vtype.VNumber;
import org.epics.vtype.VType;
import org.phoebus.applications.saveandrestore.SafeMultiply;
import org.phoebus.applications.saveandrestore.ui.VTypePair;
import org.phoebus.saveandrestore.util.Threshold;
import org.phoebus.saveandrestore.util.Utilities;
import org.phoebus.saveandrestore.util.VNoData;

import java.util.Optional;

/**
* Data class for one column in the comparison table.
*/
public class ColumnEntry {

/**
* The {@link VType} value as stored in a {@link org.phoebus.applications.saveandrestore.model.Snapshot}
*/
private final ObjectProperty<VType> storedValue = new SimpleObjectProperty<>(this, "storedValue", null);
/**
* A {@link VTypePair} property holding data for the purpose of calculating and showing a delta.
*/
private final ObjectProperty<VTypePair> delta = new SimpleObjectProperty<>(this, "delta", null);
/**
* The live {@link VType} value as read from a connected PV.
*/
private final ObjectProperty<VType> liveValue = new SimpleObjectProperty<>(this, "liveValue", VNoData.INSTANCE);

private Optional<Threshold<?>> threshold = Optional.empty();

public ColumnEntry(VType storedValue) {
this.storedValue.set(storedValue);
}

public ObjectProperty<VType> storedValueProperty() {
return storedValue;
}

public void setLiveVal(VType liveValue) {
this.liveValue.set(liveValue);
VTypePair vTypePair = new VTypePair(liveValue, storedValue.get(), threshold);
delta.set(vTypePair);
}

public ObjectProperty<VType> liveValueProperty() {
return liveValue;
}

public ObjectProperty<VTypePair> getDelta() {
return delta;
}

/**
* Set the threshold value for this entry. All value comparisons related to this entry are calculated using the
* threshold (if it exists).
*
* @param ratio the threshold
*/
public void setThreshold(double ratio) {
if (storedValue.get() instanceof VNumber) {
VNumber vNumber = SafeMultiply.multiply((VNumber) storedValue.get(), ratio);
boolean isNegative = vNumber.getValue().doubleValue() < 0;
Threshold t = new Threshold<>(isNegative ? SafeMultiply.multiply(vNumber.getValue(), -1.0) : vNumber.getValue());
this.delta.set(new VTypePair(liveValue.get(), storedValue.get(), Optional.of(t)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2025 European Spallation Source ERIC.
*/

package org.phoebus.applications.saveandrestore.ui.snapshot.compare;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import org.epics.vtype.VType;
import org.phoebus.applications.saveandrestore.ui.VTypePair;
import org.phoebus.saveandrestore.util.Threshold;
import org.phoebus.saveandrestore.util.Utilities;

import java.util.List;
import java.util.Optional;

/**
* Data class for the {@link javafx.scene.control.TableView} of the comparison dialog.
*/
public class ComparisonData {

/**
* Index (=row number) for this instance.
*/
private final IntegerProperty index = new SimpleIntegerProperty(this, "index");
/**
* {@link List} of {@link ColumnEntry}s, one for each column in the data. For array data this will
* hold only one element.
*/
private final List<ColumnEntry> columnEntries;

public ComparisonData(int index, List<ColumnEntry> columnEntries) {
this.index.set(index);
this.columnEntries = columnEntries;
}

@SuppressWarnings("unused")
public IntegerProperty indexProperty() {
return index;
}

public List<ColumnEntry> getColumnEntries() {
return columnEntries;
}

/**
* Set the threshold value for this entry. All value comparisons related to this entry are calculated using the
* threshold (if it exists).
*
* @param ratio the threshold
*/
public void setThreshold(double ratio) {
columnEntries.forEach(columnEntry -> columnEntry.setThreshold(ratio));
}
}
Loading