Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add table view for group peak workspace #37166

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion Framework/API/inc/MantidAPI/WorkspaceGroup.h
Expand Up @@ -69,8 +69,10 @@ class MANTID_API_DLL WorkspaceGroup : public Workspace {
/// This method returns true if the group is empty (no member workspace)
bool isEmpty() const;
bool areNamesSimilar() const;
/// Inidicates that the workspace group can be treated as multiperiod.
/// Indicates that the workspace group can be treated as multiperiod.
bool isMultiperiod() const;
/// Check if the workspace group contains just peak workspaces
bool isGroupPeaksWorkspaces() const;
/// Check if a workspace is included in this group or any nested groups.
bool isInGroup(const Workspace &workspaceToCheck, size_t level = 0) const;
/// Prints the group to the screen using the logger at debug
Expand Down
15 changes: 12 additions & 3 deletions Framework/API/src/WorkspaceGroup.cpp
Expand Up @@ -6,6 +6,7 @@
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidAPI/WorkspaceGroup.h"
#include "MantidAPI/AnalysisDataService.h"
#include "MantidAPI/IPeaksWorkspace.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/Run.h"
#include "MantidKernel/IPropertyManager.h"
Expand Down Expand Up @@ -455,6 +456,14 @@ bool WorkspaceGroup::isMultiperiod() const {
return true;
}

/**
* @return :: True if all of the workspaces in the group are peak workspaces
*/
bool WorkspaceGroup::isGroupPeaksWorkspaces() const {
return std::all_of(m_workspaces.begin(), m_workspaces.end(),
[](auto ws) { return dynamic_cast<IPeaksWorkspace *>(ws.get()) != nullptr; });
}

/**
* @param workspaceToCheck :: A workspace to check.
* @param level :: The current nesting level. Intended for internal use only
Expand All @@ -471,7 +480,7 @@ bool WorkspaceGroup::isInGroup(const Workspace &workspaceToCheck, size_t level)
for (const auto &workspace : m_workspaces) {
if (workspace.get() == &workspaceToCheck)
return true;
auto *group = dynamic_cast<WorkspaceGroup *>(workspace.get());
const auto *group = dynamic_cast<WorkspaceGroup *>(workspace.get());
if (group) {
if (group->isInGroup(workspaceToCheck, level + 1))
return true;
Expand Down Expand Up @@ -503,7 +512,7 @@ namespace Mantid::Kernel {
template <>
MANTID_API_DLL Mantid::API::WorkspaceGroup_sptr
IPropertyManager::getValue<Mantid::API::WorkspaceGroup_sptr>(const std::string &name) const {
auto *prop = dynamic_cast<PropertyWithValue<Mantid::API::WorkspaceGroup_sptr> *>(getPointerToProperty(name));
const auto *prop = dynamic_cast<PropertyWithValue<Mantid::API::WorkspaceGroup_sptr> *>(getPointerToProperty(name));
if (prop) {
return *prop;
} else {
Expand All @@ -516,7 +525,7 @@ IPropertyManager::getValue<Mantid::API::WorkspaceGroup_sptr>(const std::string &
template <>
MANTID_API_DLL Mantid::API::WorkspaceGroup_const_sptr
IPropertyManager::getValue<Mantid::API::WorkspaceGroup_const_sptr>(const std::string &name) const {
auto *prop = dynamic_cast<PropertyWithValue<Mantid::API::WorkspaceGroup_sptr> *>(getPointerToProperty(name));
const auto *prop = dynamic_cast<PropertyWithValue<Mantid::API::WorkspaceGroup_sptr> *>(getPointerToProperty(name));
if (prop) {
return prop->operator()();
} else {
Expand Down
Expand Up @@ -5,6 +5,8 @@
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidAPI/AnalysisDataService.h"
#include "MantidAPI/IPeaksWorkspace.h"
#include "MantidAPI/WorkspaceGroup.h"
#include "MantidKernel/WarningSuppressions.h"
#include "MantidPythonInterface/core/Converters/PySequenceToVector.h"
#include "MantidPythonInterface/core/Converters/ToPyList.h"
Expand Down Expand Up @@ -67,6 +69,28 @@ list retrieveWorkspaces(AnalysisDataServiceImpl const *const self, const list &n
return Converters::ToPyList<WeakPtr>()(wsWeakPtrs);
}

list retrieveGroupPeaksWorkspaces(AnalysisDataServiceImpl const *const self, const list &names) {
using WeakPtr = std::weak_ptr<Workspace>;

auto wsSharedPtrs = self->retrieveWorkspaces(Converters::PySequenceToVector<std::string>(names)(), false);

auto isNotGroupPeakWorkspace = [](const Workspace_sptr &wksp) {
if (auto gws = dynamic_cast<WorkspaceGroup *>(wksp.get())) {
return !gws->isGroupPeaksWorkspaces();
}
return true;
};
auto end = std::remove_if(wsSharedPtrs.begin(), wsSharedPtrs.end(), isNotGroupPeakWorkspace);
wsSharedPtrs.erase(end, wsSharedPtrs.end());

std::vector<WeakPtr> wsWeakPtrs;
wsWeakPtrs.reserve(wsSharedPtrs.size());
std::transform(wsSharedPtrs.cbegin(), wsSharedPtrs.cend(), std::back_inserter(wsWeakPtrs),
[](const Workspace_sptr &wksp) -> WeakPtr { return WeakPtr(wksp); });

return Converters::ToPyList<WeakPtr>()(wsWeakPtrs);
}

GNU_DIAG_OFF("unused-local-typedef")
// Ignore -Wconversion warnings coming from boost::python
// Seen with GCC 7.1.1 and Boost 1.63.0
Expand All @@ -86,6 +110,7 @@ void export_AnalysisDataService() {
.def("retrieveWorkspaces", retrieveWorkspaces,
AdsRetrieveWorkspacesOverloads("Retrieve a list of workspaces by name",
(arg("self"), arg("names"), arg("unrollGroups") = false)))
.def("retrieveGroupPeaksWorkspaces", retrieveGroupPeaksWorkspaces, (arg("self"), arg("names")))
.def("addToGroup", &AnalysisDataServiceImpl::addToGroup, (arg("groupName"), arg("wsName")),
"Add a workspace in the ADS to a group in the ADS")
.def("removeFromGroup", &AnalysisDataServiceImpl::removeFromGroup, (arg("groupName"), arg("wsName")),
Expand Down
10 changes: 4 additions & 6 deletions buildconfig/CMake/CppCheck_Suppressions.txt.in
Expand Up @@ -399,12 +399,10 @@ constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/SpectraAxisValidator.
constVariableReference:${CMAKE_SOURCE_DIR}/Framework/Crystal/src/ConnectedComponentLabeling.cpp:337
constVariableReference:${CMAKE_SOURCE_DIR}/Framework/Crystal/src/ConnectedComponentLabeling.cpp:338
constVariableReference:${CMAKE_SOURCE_DIR}/Framework/Crystal/src/CalculateUMatrix.cpp:65
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:78
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:437
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:474
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:519
useStlAlgorithm:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:125
useStlAlgorithm:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:406
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:79
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:438
useStlAlgorithm:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:126
useStlAlgorithm:${CMAKE_SOURCE_DIR}/Framework/API/src/WorkspaceGroup.cpp:407
constVariableReference:${CMAKE_SOURCE_DIR}/Framework/API/src/MultiPeriodGroupWorker.cpp:91
constParameterPointer:${CMAKE_SOURCE_DIR}/Framework/API/src/MultiPeriodGroupWorker.cpp:151
constVariablePointer:${CMAKE_SOURCE_DIR}/Framework/API/src/MultiPeriodGroupWorker.cpp:191
Expand Down
13 changes: 13 additions & 0 deletions qt/applications/workbench/workbench/plugins/workspacewidget.py
Expand Up @@ -288,6 +288,19 @@ def _do_show_data(self, names):
import matplotlib.pyplot

parent, flags = get_window_config()
# Process group peak workspaces first and remove them from the list
for ws in self._ads.retrieveGroupPeaksWorkspaces(names):
try:
TableWorkspaceDisplay.supports(ws)
presenter = TableWorkspaceDisplay(ws, plot=matplotlib.pyplot, parent=parent, window_flags=flags, group=True)
presenter.show_view()
names.remove(ws.name())
except ValueError as e:
logger.error(str(e))
logger.error(
"Could not open workspace: {0} with neither " "MatrixWorkspaceDisplay nor TableWorkspaceDisplay." "".format(ws.name())
)

for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True):
try:
MatrixWorkspaceDisplay.supports(ws)
Expand Down
@@ -0,0 +1,172 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# coding=utf-8
# This file is part of the mantidqt package.
from mantid.api import WorkspaceGroup
from mantidqt.widgets.workspacedisplay.table.error_column import ErrorColumn
from mantidqt.widgets.workspacedisplay.table.marked_columns import MarkedColumns
from mantidqt.widgets.workspacedisplay.table.model import TableWorkspaceColumnTypeMapping
from collections import defaultdict


class GroupTableWorkspaceDisplayModel:
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a lot of similar code here and in TableWorkspaceDisplayModel - is there any way this could inherit or is that not possible? if so it could remove quite a lot of the boiler plate getters etc.

SPECTRUM_PLOT_LEGEND_STRING = "{}-{}"
BIN_PLOT_LEGEND_STRING = "{}-bin-{}"
EDITABLE_COLUMN_NAMES = ["h", "k", "l"]

ALLOWED_WORKSPACE_TYPES = [WorkspaceGroup]

@classmethod
def supports(cls, ws: WorkspaceGroup):
"""
Checks that the provided workspace is supported by this display.
:param ws: Workspace to be checked for support
:raises ValueError: if the workspace is not supported
"""
if not any(isinstance(ws, allowed_type) for allowed_type in cls.ALLOWED_WORKSPACE_TYPES):
raise ValueError("The workspace type is not supported: {0}".format(ws))

def __init__(self, ws: WorkspaceGroup):
"""
Initialise the model with the workspace
:param ws: Workspace to be used for providing data
:raises ValueError: if the workspace is not supported
"""
self.supports(ws)

self.ws: WorkspaceGroup = ws
self.group_name = self.ws.name()
self.child_names = self.ws.getNames()
self.ws_num_rows = sum(peakWs.rowCount() for peakWs in ws)
self.ws_num_cols = 0
if self.ws.size() != 0:
self.ws_num_cols = self.ws[0].columnCount() + 2
self.marked_columns = MarkedColumns()
self._original_column_headers = self.get_column_headers()
self.block_model_replace = False
self._row_mapping = self._make_row_mapping()
if self.ws_num_cols:
self._load_col_types()

def _make_row_mapping(self):
row_index = 0
row_mapping = []
for group_index, peaksWs in enumerate(self.ws):
group_start = row_index
group_end = row_index + len(peaksWs)
row_mapping.extend([(group_index, index - group_start) for index in range(group_start, group_end)])
row_index += len(peaksWs)
return row_mapping

def _load_col_types(self):
for col in range(2, len(self._original_column_headers)):
plot_type = self.ws[0].getPlotType(col - 2)
if plot_type == TableWorkspaceColumnTypeMapping.X:
self.marked_columns.add_x(col)
elif plot_type == TableWorkspaceColumnTypeMapping.Y:
self.marked_columns.add_y(col)
elif plot_type == TableWorkspaceColumnTypeMapping.YERR:
err_for_column = self.ws[0].getLinkedYCol(col - 2)
if err_for_column >= 0:
self.marked_columns.add_y_err(ErrorColumn(col, err_for_column + 2))

def original_column_headers(self):
return self._original_column_headers[:]

def build_current_labels(self):
return self.marked_columns.build_labels()

def get_name(self):
return self.group_name

def get_child_names(self):
return self.child_names

def get_column_headers(self):
if self.ws.size() != 0:
return ["WS Index", "Group Index"] + self.ws[0].getColumnNames()
return []

def get_column(self, index):
column_data = []

for i, ws_item in enumerate(self.ws):
if index == 0:
column_data.extend([i] * ws_item.rowCount())
elif index == 1:
column_data.extend([i for i in range(ws_item.rowCount())])
else:
column_data.extend(ws_item.column(index - 2))

return column_data

def get_number_of_rows(self):
return self.ws_num_rows

def get_number_of_columns(self):
return self.ws_num_cols

def get_column_header(self, index):
return self.get_column_headers()[index]

def is_editable_column(self, icol):
return self.get_column_headers()[icol] in self.EDITABLE_COLUMN_NAMES

def set_column_type(self, col, type, linked_col_index=-1):
self.ws[0].setPlotType(col - 2, type, linked_col_index - 2 if linked_col_index != -1 else linked_col_index)

def workspace_equals(self, workspace_name):
return self.ws.name() == workspace_name

def get_cell(self, row, column):
group_index, ws_index = self._row_mapping[row]

if column == 0:
return ws_index

if column == 1:
return group_index

column = column - 2

return self.ws[group_index].cell(ws_index, column)

def set_cell_data(self, row, col, data, is_v3d):
group_index, ws_index = row
col_name = self.get_column_header(col)

p = self.ws[group_index].getPeak(ws_index)

if col_name == "h":
p.setH(data)
elif col_name == "k":
p.setK(data)
elif col_name == "l":
p.setL(data)

def delete_rows(self, selected_rows):
from mantid.simpleapi import DeleteTableRows

row_to_ws_index = defaultdict(list)
for group_index, ws_index in selected_rows:
row_to_ws_index[group_index].append(ws_index)

for group_index in row_to_ws_index:
DeleteTableRows(self.ws[group_index], ",".join(map(str, row_to_ws_index[group_index])))

def get_statistics(self, selected_columns):
from mantid.simpleapi import StatisticsOfTableWorkspace

stats = StatisticsOfTableWorkspace(self.ws, selected_columns)
return stats

def sort(self, column_index, sort_ascending):
from mantid.simpleapi import SortPeaksWorkspace

column_name = self.get_column_header(column_index)

SortPeaksWorkspace(InputWorkspace=self.ws, OutputWorkspace=self.ws, ColumnNameToSortBy=column_name, SortAscending=sort_ascending)
@@ -0,0 +1,49 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2024 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# This file is part of the mantidqt package.

from qtpy.QtCore import Qt

from mantidqt.widgets.workspacedisplay.table.table_model import TableModel

from mantid.kernel import V3D
from numpy import ndarray


class GroupTableModel(TableModel):
def __init__(self, data_model, parent=None):
super().__init__(data_model, parent=parent)

def setData(self, index, value, role):
if index.isValid() and role == Qt.EditRole:
model = index.model()
ws_index = int(model.data(model.index(index.row(), 0)))
group_index = int(model.data(model.index(index.row(), 1)))
row = (group_index, ws_index)

try:
self._data_model.set_cell_data(row, index.column(), value, False)
except ValueError:
print(self.ITEM_CHANGED_INVALID_DATA_MESSAGE)
return False
except Exception as x:
print(self.ITEM_CHANGED_UNKNOWN_ERROR_MESSAGE.format(x))
return False
self.dataChanged.emit(index, index)
return True
else:
return False

def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if index.row() >= self.max_rows() or index.row() < 0:
return None
if role in (Qt.DisplayRole, Qt.EditRole):
data = self._data_model.get_cell(index.row(), index.column())
return str(data) if isinstance(data, (V3D, ndarray)) else data
return None