Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
183f0ff
Fix generating random particles dataset
psavery Feb 4, 2026
716dada
Truncate error on external pipeline execution
psavery Feb 4, 2026
bc4a29c
Prevent divide-by-zero errors when normalizing
psavery Feb 4, 2026
4049de0
Automatically add `@apply_to_each_array`
psavery Feb 13, 2026
5fd4916
Remove manual `@apply_to_each_array` decorators
psavery Feb 13, 2026
9803e74
Add ability to save tilt angles to a .txt file
psavery Feb 13, 2026
7634ac4
Fixes for new automatic decorating
psavery Feb 13, 2026
cb84038
add ModulePlot and LineView to display operators Table results
alesgenova Jan 27, 2026
868edde
add options to ModulePlot to log scale x and y axes
alesgenova Jan 28, 2026
829df71
let operators provide xy axes labels and log scaling
alesgenova Feb 17, 2026
6bf0f4c
Add support for Scan IDs to Tomviz
psavery Feb 17, 2026
f3c83c6
add ability to insert operators before the selected operator
alesgenova Feb 17, 2026
36a23c4
Add alternative pixel size name support for ptycho
psavery Feb 17, 2026
af9943c
Automatically find and validate `pyxrf-utils` exec
psavery Feb 18, 2026
88eb8a6
Omit unused parameter name to fix compile warning
psavery Feb 18, 2026
fff843f
Refactor FxiWorkflowWidget to ShiftRotationCenter
psavery Feb 17, 2026
7ac7725
Ensure operator dialog appears above main window
psavery Feb 18, 2026
0658733
add optional breakpoints to the pipeline at each operator
alesgenova Feb 18, 2026
7c51f80
Only set UseColorPaletteForBackground if available
psavery Feb 18, 2026
015dc37
Fix tomopy operator and generalize it
psavery Feb 18, 2026
df52da1
Add several safety checks to prevent seg faults
psavery Feb 19, 2026
3f4fdb3
Add checks that ptycho directory exists
psavery Feb 19, 2026
c9cadf8
fix pipeline execution with breakpoint when modifying operators
alesgenova Feb 19, 2026
eeb31eb
Merge pull request #16 from alesgenova/fix-breakpoint
psavery Feb 19, 2026
56e3f87
add ability to export table results as csv
alesgenova Feb 19, 2026
8eb0f57
Allow "and" and "or" for conditionals in json file
psavery Feb 19, 2026
445e628
Fix tomopy reconstruction
psavery Feb 19, 2026
fce079a
Merge pull request #17 from alesgenova/export-csv-table
psavery Feb 19, 2026
1a5f313
Ensure operator dialog appears above main window
psavery Feb 19, 2026
2035cba
Fixes to ShiftRotationCenterWidget
psavery Feb 20, 2026
9d05b7b
add PSD and FSC 1D metric operators
alesgenova Feb 20, 2026
8e9df07
Merge pull request #18 from alesgenova/nsls-operators
psavery Feb 20, 2026
29d3496
Always pad PSD to cubic
psavery Feb 20, 2026
b974dcb
Plot QiA and QN in ShiftRotationCenterWidget
psavery Feb 20, 2026
bbdcff9
Change to physical units in ShiftRotationCenter
psavery Feb 21, 2026
5f11c6b
Add ability to select scalars subset in operator
psavery Feb 23, 2026
3b3d52b
Prevent seg fault when module fails to load
psavery Feb 23, 2026
1fef799
Skip plot module outputs in save/load state file
psavery Feb 23, 2026
22936a8
Auto-generate plot colors using HSV spacing
psavery Feb 23, 2026
b78e787
Update FourierShellCorrelation to select scalars
psavery Feb 23, 2026
452f913
Add operator to remove scalar arrays
psavery Feb 23, 2026
8992efb
fix pipeline execution from start when needed
alesgenova Feb 23, 2026
511c5b5
fix operator ordering when loading a state file
alesgenova Feb 23, 2026
01d5bf5
fix pipeline when loading a tvh5 state file
alesgenova Feb 23, 2026
f29013d
more fixes to the pipeline execution when breakpoints are present
alesgenova Feb 23, 2026
14fc8f1
Merge pull request #19 from alesgenova/fix-pipeline-ordering
psavery Feb 23, 2026
2a0cbd4
Add question to verify insertion of operator
psavery Feb 23, 2026
e9c209e
Updates to ShiftRotationCenterWidget
psavery Feb 23, 2026
83bf7f1
Add support for setting Scan IDS from file
psavery Feb 25, 2026
bceaa12
Convert old-style Qt SIGNAL to function pointer
psavery Feb 25, 2026
077ad35
Add tests for all recent new features
psavery Feb 24, 2026
37c31c2
Fix compile warning
psavery Feb 25, 2026
cfec3bc
Use scoped-name variable for Qt test PYTHONPATH
psavery Feb 25, 2026
bab06ba
Simplify and clarify "Insert Operator?" message
psavery Feb 25, 2026
7427cc9
Add safety checks to avoid template crashes
psavery Feb 25, 2026
6ddd61e
Automatically create/select view for modules
psavery Feb 25, 2026
09df09c
Ensure "Test Rotations" isn't hidden by slice view
psavery Feb 25, 2026
79c9c95
Clean up appearance of ShiftRotationCenterWidget
psavery Feb 25, 2026
42c966b
Make operator dialogs show on main window monitor
psavery Feb 25, 2026
4e1835e
Fix bug where saving tilt angles should be hidden
psavery Feb 25, 2026
c784eed
Add .txt extension automatically if not present
psavery Feb 26, 2026
146cc2f
Update camera position after Ptycho workflow
psavery Feb 26, 2026
7a8ae51
Fix error when deleting operator before child
psavery Feb 26, 2026
5b132c2
Ensure we catch process errors in PyXRF workflow
psavery Feb 26, 2026
f8be14e
add the deconv denoise and similarity metric operators
alesgenova Feb 25, 2026
3e31524
Merge pull request #20 from alesgenova/denoise-operators
psavery Mar 2, 2026
cede473
Run `black` on new operators for formatting
psavery Mar 2, 2026
d98da32
Change default mu parameter to 0.01
psavery Mar 2, 2026
d57f480
Add test for deconvolution/denoise operator
psavery Mar 2, 2026
fa8824e
Ensure child data source used for scalars options
psavery Mar 2, 2026
dba406c
Only allow denoising to be used on tilt series
psavery Mar 3, 2026
c445e37
Increase max iterations for deconvolution/denoise
psavery Mar 4, 2026
d076728
Allow cancellation for denoise and similarity ops
psavery Mar 4, 2026
6d7f406
Fix hard coding of admm max iterations
psavery Mar 4, 2026
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
15 changes: 12 additions & 3 deletions tests/cxx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ include_directories(SYSTEM
${PARAVIEW_INCLUDE_DIRS}
${GTEST_INCLUDE_DIRS})
include_directories(${PROJECT_SOURCE_DIR}/tomviz)
include_directories(${PROJECT_SOURCE_DIR}/tomviz/modules)
include_directories(${PROJECT_SOURCE_DIR}/tomviz/operators)
include_directories(${PROJECT_SOURCE_DIR}/tomviz/animations)
include_directories(${PROJECT_SOURCE_DIR}/tomviz/loguru)
include_directories(${PROJECT_SOURCE_DIR}/tomviz/acquisition)

include(CheckIncludeFileCXX)
Expand Down Expand Up @@ -49,11 +53,16 @@ set(_pythonpath "${_pythonpath}${_separator}$ENV{PYTHONPATH}")
# Add the test cases
add_cxx_test(OperatorPython PYTHONPATH ${_pythonpath})
add_cxx_test(Variant)

add_cxx_test(ScanID)
add_cxx_test(Utilities)
add_cxx_qtest(ModulePlot)
add_cxx_qtest(Tvh5Data)
add_cxx_qtest(InterfaceBuilder)
add_cxx_qtest(PipelineExecution PYTHONPATH ${_pythonpath})
add_cxx_qtest(DockerUtilities)
add_cxx_qtest(AcquisitionClient PYTHONPATH "${CMAKE_SOURCE_DIR}/acquisition")
add_cxx_qtest(PtychoWorkflow)
add_cxx_qtest(PyXRFWorkflow)
add_cxx_qtest(PtychoWorkflow PYTHONPATH ${_pythonpath})
add_cxx_qtest(PyXRFWorkflow PYTHONPATH ${_pythonpath})

# Generate the executable
create_test_executable(tomvizTests)
Expand Down
16 changes: 10 additions & 6 deletions tests/cxx/CxxTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ macro(add_cxx_qtest name)
set(_one_value_args PYTHONPATH)
cmake_parse_arguments(fn "" "" "${_one_value_args}" "" ${ARGN})

# Use a name-scoped variable to avoid clobbering the caller's _pythonpath.
# CMake macros do not have their own scope, so a bare _pythonpath would
# overwrite the identically-named variable in the calling CMakeLists.txt.
set(_qtest_${name}_pythonpath "")
if(fn_PYTHONPATH)
set(_pythonpath "${fn_PYTHONPATH}")
message("PYTHONPATH for ${name}: ${_pythonpath})")
set(_qtest_${name}_pythonpath "${fn_PYTHONPATH}")
message("PYTHONPATH for ${name}: ${_qtest_${name}_pythonpath})")
endif()

set(_test_src ${name}Test.cxx)
Expand All @@ -31,13 +35,13 @@ macro(add_cxx_qtest name)
set_target_properties(${_executable_name} PROPERTIES ENABLE_EXPORTS TRUE)

add_test(NAME "${name}" COMMAND ${_executable_name})
if(_pythonpath)
if(_qtest_${name}_pythonpath)
if (WIN32)
string(REPLACE "\\;" ";" "_pythonpath" "${_pythonpath}")
string(REPLACE ";" "\\;" "_pythonpath" "${_pythonpath}")
string(REPLACE "\\;" ";" "_qtest_${name}_pythonpath" "${_qtest_${name}_pythonpath}")
string(REPLACE ";" "\\;" "_qtest_${name}_pythonpath" "${_qtest_${name}_pythonpath}")
endif()
set_tests_properties(${name}
PROPERTIES ENVIRONMENT "PYTHONPATH=${_pythonpath}")
PROPERTIES ENVIRONMENT "PYTHONPATH=${_qtest_${name}_pythonpath};TOMVIZ_APPLICATION=1")
endif()
endmacro()

Expand Down
199 changes: 199 additions & 0 deletions tests/cxx/InterfaceBuilderTest.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/* This source file is part of the Tomviz project, https://tomviz.org/.
It is released under the 3-Clause BSD License, see "LICENSE". */

#include <QCheckBox>
#include <QComboBox>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLayout>
#include <QMap>
#include <QStandardItemModel>
#include <QTest>
#include <QVariant>
#include <QWidget>

#include "InterfaceBuilder.h"

using namespace tomviz;

class InterfaceBuilderTest : public QObject
{
Q_OBJECT

private:
InterfaceBuilder* builder;
QWidget* parentWidget;

private slots:
void init()
{
builder = new InterfaceBuilder(this);
parentWidget = new QWidget();
}

void cleanup()
{
delete parentWidget;
parentWidget = nullptr;
}

void selectScalarsWidgetCreated()
{
QString desc = R"({
"name": "TestOp",
"label": "Test Operator",
"parameters": [
{"name": "selected_scalars", "type": "select_scalars",
"label": "Scalars"}
]
})";

builder->setJSONDescription(desc);
QLayout* layout = builder->buildInterface();
QVERIFY(layout != nullptr);

parentWidget->setLayout(layout);

auto* widget = parentWidget->findChild<QWidget*>("selected_scalars");
QVERIFY(widget != nullptr);
QCOMPARE(widget->property("type").toString(), QString("select_scalars"));
}

void selectScalarsParameterValues()
{
QString desc = R"({
"name": "TestOp",
"label": "Test Operator",
"parameters": [
{"name": "selected_scalars", "type": "select_scalars",
"label": "Scalars"}
]
})";

builder->setJSONDescription(desc);
QLayout* layout = builder->buildInterface();
QVERIFY(layout != nullptr);
parentWidget->setLayout(layout);

// The combo box is empty because we have no DataSource.
// Manually populate the model and check items to test parameterValues().
auto* container =
parentWidget->findChild<QWidget*>("selected_scalars");
QVERIFY(container != nullptr);

auto* applyAllCB =
container->findChild<QCheckBox*>("selected_scalars_apply_all");
QVERIFY(applyAllCB != nullptr);

auto* combo =
container->findChild<QComboBox*>("selected_scalars_combo");
QVERIFY(combo != nullptr);

auto* model = qobject_cast<QStandardItemModel*>(combo->model());
QVERIFY(model != nullptr);

// Add items and check them
auto* itemA = new QStandardItem("scalar_a");
itemA->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
itemA->setData(Qt::Checked, Qt::CheckStateRole);
model->appendRow(itemA);

auto* itemB = new QStandardItem("scalar_b");
itemB->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
itemB->setData(Qt::Checked, Qt::CheckStateRole);
model->appendRow(itemB);

// Uncheck "Apply to all" so individual selections are used
applyAllCB->setChecked(false);

auto result = InterfaceBuilder::parameterValues(parentWidget);
QVERIFY(result.contains("selected_scalars"));

auto resultScalars = result["selected_scalars"].toList();
QCOMPARE(resultScalars.size(), 2);
QCOMPARE(resultScalars[0].toString(), QString("scalar_a"));
QCOMPARE(resultScalars[1].toString(), QString("scalar_b"));
}

void basicParameterTypes()
{
QString desc = R"({
"name": "TestOp",
"label": "Test Operator",
"parameters": [
{"name": "int_param", "type": "int", "label": "Int", "default": 5},
{"name": "double_param", "type": "double", "label": "Double",
"default": 1.5},
{"name": "bool_param", "type": "bool", "label": "Bool", "default": true}
]
})";

builder->setJSONDescription(desc);
QLayout* layout = builder->buildInterface();
QVERIFY(layout != nullptr);

parentWidget->setLayout(layout);

auto result = InterfaceBuilder::parameterValues(parentWidget);
QVERIFY(result.contains("int_param"));
QVERIFY(result.contains("double_param"));
QVERIFY(result.contains("bool_param"));

QCOMPARE(result["int_param"].toInt(), 5);
QCOMPARE(result["double_param"].toDouble(), 1.5);
QCOMPARE(result["bool_param"].toBool(), true);
}

void enableIfVisibleIf()
{
QString desc = R"({
"name": "TestOp",
"label": "Test Operator",
"parameters": [
{"name": "toggle", "type": "bool", "label": "Toggle", "default": false},
{"name": "enabled_dep", "type": "int", "label": "Enabled Dep",
"default": 0, "enable_if": "toggle == true"},
{"name": "visible_dep", "type": "int", "label": "Visible Dep",
"default": 0, "visible_if": "toggle == true"}
]
})";

builder->setJSONDescription(desc);
QLayout* layout = builder->buildInterface();
QVERIFY(layout != nullptr);

parentWidget->setLayout(layout);
// Must show the parent so isVisible() works on children --
// Qt's isVisible() requires all ancestors to be visible.
parentWidget->show();

auto* toggleCheckBox =
parentWidget->findChild<QCheckBox*>("toggle");
QVERIFY(toggleCheckBox != nullptr);

auto* enabledWidget = parentWidget->findChild<QWidget*>("enabled_dep");
QVERIFY(enabledWidget != nullptr);

auto* visibleWidget = parentWidget->findChild<QWidget*>("visible_dep");
QVERIFY(visibleWidget != nullptr);

// Toggle is false by default -- enabled_dep should be disabled,
// visible_dep should be hidden
QVERIFY(!enabledWidget->isEnabled());
QVERIFY(!visibleWidget->isVisible());

// Toggle on -- both should become active
toggleCheckBox->setChecked(true);
QVERIFY(enabledWidget->isEnabled());
QVERIFY(visibleWidget->isVisible());

// Toggle back off -- both should revert
toggleCheckBox->setChecked(false);
QVERIFY(!enabledWidget->isEnabled());
QVERIFY(!visibleWidget->isVisible());
}
};

QTEST_MAIN(InterfaceBuilderTest)
#include "InterfaceBuilderTest.moc"
Loading