Mantid
Loading...
Searching...
No Matches
CorelliCalibrationDatabase.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
12#include "MantidAPI/Run.h"
14#include "MantidAPI/TableRow.h"
26
27#include <boost/algorithm/string/classification.hpp>
28#include <boost/algorithm/string/split.hpp>
29#include <filesystem>
30#include <sstream>
31#include <string>
32
33namespace Mantid::Algorithms {
34
35using namespace Kernel;
36using namespace API;
37using namespace Geometry;
38using namespace DataObjects;
39using Types::Core::DateAndTime;
40
41namespace CorelliCalibration {
42
43//-----------------------------------------------------------------------------
47CalibrationTableHandler::CalibrationTableHandler() : mCalibWS{nullptr}, isSingleComponentTable{false} {}
48
49//-----------------------------------------------------------------------------
62 bool iscomponent) {
63
64 // Create table workspace
65 ITableWorkspace_sptr itablews = WorkspaceFactory::Instance().createTable();
66
67 // Add to ADS if workspace name is given
68 if (wsname.size() > 0) {
69 AnalysisDataService::Instance().addOrReplace(wsname, itablews);
70 }
71
72 TableWorkspace_sptr tablews = std::dynamic_pointer_cast<TableWorkspace>(itablews);
73
74 // Set up columns
75 if (iscomponent)
76 tablews->addColumn("str", "YYYMMDD"); // first column as date stamp
77 else {
78 tablews->addColumn("str", "ComponentName"); // first column as date stamp
79 }
80 for (size_t i = 1; i < CorelliCalibration::calibrationTableColumnNames.size(); ++i) {
83 tablews->addColumn(type, colname);
84 }
85
86 return tablews;
87}
88
89//-----------------------------------------------------------------------------
101 std::string &errormsg) {
102 // Check columns of
103 std::vector<std::string> colNames = calibws->getColumnNames();
104
105 bool valid = true;
106
107 if (colNames.size() != CorelliCalibration::calibrationTableColumnNames.size()) {
108 // column numbers mismatch
109 std::stringstream errorss;
110 errorss << "Calibration table workspace requires " << CorelliCalibration::calibrationTableColumnNames.size()
111 << " columns. Input workspace " << calibws->getName() << " get " << calibws->getColumnNames().size()
112 << "instead.";
113 errormsg = errorss.str();
114 valid = false;
115 } else {
116
117 // Check columns one by one
118 for (size_t i = 0; i < colNames.size(); ++i) {
120 std::stringstream errorss;
121 errorss << i << "-th column is supposed to be " << CorelliCalibration::calibrationTableColumnNames[i]
122 << ", but instead in TableWorkspace " << calibws->getName() << " it is " << colNames[i];
123 errormsg = errorss.str();
124 valid = false;
125 break;
126 }
127 }
128 }
129
130 return valid;
131}
132
133//-----------------------------------------------------------------------------
142 const std::string &datestamp, const ComponentPosition &pos) {
143 // check
144 if (tablews->columnCount() != calibrationTableColumnNames.size()) {
145 throw std::runtime_error("Single component calibration table workspace is not correct.");
146 }
147
148 // Append a new row
149 Mantid::API::TableRow sourceRow = tablews->appendRow();
150 // Date and positions
151 sourceRow << datestamp << pos.x << pos.y << pos.z << pos.xCosine << pos.yCosine << pos.zCosine << pos.rotAngle;
152}
153
160
161 std::string errmsg{""};
162
163 if (!isValidCalibrationTableWorkspace(calibws, errmsg))
164 throw std::runtime_error(errmsg);
165
166 // Set
167 mCalibWS = calibws;
168}
169
180
181 // Check workspace type
183 throw std::runtime_error("TableWorkspace contains a single component's "
184 "calibration in various dates");
185
186 std::vector<std::string> names = mCalibWS->getColVector<std::string>(0);
187
188 return names;
189}
190
198 // Check
199 if (!mCalibWS)
200 throw std::runtime_error("Calibration workspace has not been set up yet.");
201
202 // Get the row number of the specified component
203 size_t row_number = mCalibWS->rowCount();
204 for (size_t i = 0; i < row_number; ++i) {
205 if (mCalibWS->cell<std::string>(i, 0) == component) {
206 row_number = i;
207 break;
208 }
209 }
210 // Check
211 if (row_number == mCalibWS->rowCount())
212 throw std::runtime_error("Specified component does not exist");
213
214 // Get the values
216 pos.x = mCalibWS->cell<double>(row_number, 1);
217 pos.y = mCalibWS->cell<double>(row_number, 2);
218 pos.z = mCalibWS->cell<double>(row_number, 3);
219 pos.xCosine = mCalibWS->cell<double>(row_number, 4);
220 pos.yCosine = mCalibWS->cell<double>(row_number, 5);
221 pos.zCosine = mCalibWS->cell<double>(row_number, 6);
222 pos.rotAngle = mCalibWS->cell<double>(row_number, 7);
223
224 return pos;
225}
226
239CalibrationTableHandler::loadComponentCalibrationTable(const std::string &filename, const std::string &tablewsname) {
240 // Get algorithm handler
241 auto loadAsciiAlg = AlgorithmFactory::Instance().create("LoadAscii", 2);
242 // Set parameters
243 if (tablewsname.size() == 0) {
244 throw std::runtime_error("Failed to load ASCII as OutputWorkspace name is empty string.");
245 }
246 loadAsciiAlg->initialize();
247 loadAsciiAlg->setPropertyValue("Filename", filename);
248 loadAsciiAlg->setPropertyValue("OutputWorkspace", tablewsname);
249 loadAsciiAlg->setPropertyValue("Separator", "CSV");
250 loadAsciiAlg->setPropertyValue("CommentIndicator", "#");
251 loadAsciiAlg->execute();
252 // Convert to TableWorkspace
253 TableWorkspace_sptr tablews =
254 std::dynamic_pointer_cast<TableWorkspace>(AnalysisDataService::Instance().retrieve(tablewsname));
255
256 return tablews;
257}
258
259//-----------------------------------------------------------------------------
274 const std::string &component,
275 const std::string &filename) {
276
277 std::string tablewsname = component + "_" + datestamp;
278
279 // Load the database file for the specific component to a table workspace
280 // if extant, otherwise instantiate an empty table
281 TableWorkspace_sptr compcaltable = nullptr;
282 if (std::filesystem::exists(filename)) {
283 compcaltable = loadComponentCalibrationTable(filename, tablewsname);
284 } else {
285 compcaltable = createCalibrationTableWorkspace(tablewsname, true);
286 }
287
288 // Append a new row to the table containing the hisotry of positions for
289 // the specific component
290 ComponentPosition componentpos = getComponentCalibratedPosition(component);
291 appendCalibration(compcaltable, datestamp, componentpos);
292
293 // save the updated history of positions to the database file. Will overwrite
294 // the file if extant
295 // Note: only version 2 of SaveAscii can work with TableWorkspace
296 auto saveAsciiAlg = AlgorithmFactory::Instance().create("SaveAscii", 2);
297 saveAsciiAlg->initialize();
298 saveAsciiAlg->setProperty("InputWorkspace", compcaltable);
299 saveAsciiAlg->setProperty("Filename", filename);
300 saveAsciiAlg->setPropertyValue("CommentIndicator", "#");
301 saveAsciiAlg->setPropertyValue("Separator", "CSV");
302 saveAsciiAlg->setProperty("ColumnHeader", true);
303 saveAsciiAlg->setProperty("AppendToFile",
304 false); // always overwrite original file
305 // run
306 saveAsciiAlg->execute();
307
308 return compcaltable;
309}
310
311//-----------------------------------------------------------------------------
316void CalibrationTableHandler::saveCalibrationTable(const std::string &filename) {
317 // create algorithm: only version 2 of SaveAscii can work with TableWorkspace
318 auto saveAsciiAlg = AlgorithmFactory::Instance().create("SaveAscii", 2);
319 saveAsciiAlg->initialize();
320 saveAsciiAlg->setProperty("InputWorkspace", mCalibWS);
321 saveAsciiAlg->setProperty("Filename", filename);
322 saveAsciiAlg->setPropertyValue("CommentIndicator", "#");
323 saveAsciiAlg->setPropertyValue("Separator", "CSV");
324 saveAsciiAlg->setProperty("ColumnHeader", true);
325 // run
326 saveAsciiAlg->execute();
327}
328
329//-----------------------------------------------------------------------------
332
333 size_t num_rows = componentcaltable->rowCount();
334 ComponentPosition pos = CalibrationTableHandler::getCalibratedPosition(componentcaltable, num_rows - 1);
335
336 return pos;
337}
338
339//-----------------------------------------------------------------------------
342 size_t rownumber) {
343 // Get the values
345
346 pos.x = componentcaltable->cell<double>(rownumber, 1);
347 pos.y = componentcaltable->cell<double>(rownumber, 2);
348 pos.z = componentcaltable->cell<double>(rownumber, 3);
349 pos.xCosine = componentcaltable->cell<double>(rownumber, 4);
350 pos.yCosine = componentcaltable->cell<double>(rownumber, 5);
351 pos.zCosine = componentcaltable->cell<double>(rownumber, 6);
352 pos.rotAngle = componentcaltable->cell<double>(rownumber, 7);
353
354 return pos;
355}
356
357} // namespace CorelliCalibration
358
359// Register the algorithm into the AlgorithmFactory
361
362
365 auto wsValidator = std::make_shared<CompositeValidator>();
366 wsValidator->add<InstrumentValidator>();
367
368 // Input MatrixWorkspace which the calibration run is from
369 declareProperty(
370 std::make_unique<WorkspaceProperty<MatrixWorkspace>>("InputWorkspace", "", Direction::Input, wsValidator),
371 "Workspace containing the day-stamp of the calibration");
372
373 // Input calibration patch TableWorkspace
374 declareProperty(
375 std::make_unique<WorkspaceProperty<TableWorkspace>>("InputCalibrationPatchWorkspace", "", Direction::Input),
376 "Table workspace containing calibrated positions and "
377 "orientations for a subset of the banks");
378
379 // Output directory
380 declareProperty(std::make_unique<FileProperty>("DatabaseDirectory", "", FileProperty::Directory),
381 "The directory that the database (csv) files are saved to");
382
383 // Optional output calibration TableWorkspace
384 declareProperty(std::make_unique<WorkspaceProperty<TableWorkspace>>("OutputWorkspace", "", Direction::Output),
385 "Table workspace containing calibrated positions and "
386 "orientations for all banks");
387}
388
389// Validate inputs workspace first.
390std::map<std::string, std::string> CorelliCalibrationDatabase::validateInputs() {
391 std::map<std::string, std::string> errors;
392
393 mInputWS = getProperty("InputWorkspace");
394
395 // check for null pointers - this is to protect against workspace groups
396 if (!mInputWS) {
397 return errors;
398 }
399
400 // This algorithm will only work for CORELLI, check for CORELLI.
401 if (mInputWS->getInstrument()->getName() != "CORELLI")
402 errors["InputWorkspace"] = "This Algorithm will only work for Corelli.";
403 // Must include start_time
404 else if (!mInputWS->run().hasProperty("start_time") && !mInputWS->run().hasProperty("run_start"))
405 errors["InputWorkspace"] = "Workspace is missing property start_time.";
406
407 // check for calibration patch table workspace
408 mInputCalibrationTableWS = getProperty("InputCalibrationPatchWorkspace");
410 errors["InputCalibrationPatchWorkspace"] = "Input calibration patch workspace is not specified";
411 return errors;
412 }
413 // Check columns
414 else {
415 std::string error_msg{""};
417 mInputCalibrationTableWS, error_msg);
418
419 if (!isvalid) {
420 errors["InputCalibrationPatchWorkspace"] = error_msg;
421 }
422 }
423
424 return errors;
425}
426
427//-----------------------------------------------------------------------------
431 // parse input
432 if (!mInputWS)
433 throw std::runtime_error("input workspace not specified");
435 throw std::runtime_error("input calibration workspace not specified");
436
437 std::string calibDatabaseDir = getProperty("DatabaseDirectory");
438
439 // map for (component name, component calibration workspace
440 std::vector<std::string> orderedcomponents = retrieveInstrumentComponents(mInputWS);
441
442 std::map<std::string, TableWorkspace_sptr> component_caibws_map;
443 setComponentMap(orderedcomponents, component_caibws_map);
444
445 // Update component CSV files
446 updateComponentDatabaseFiles(calibDatabaseDir, component_caibws_map);
447
448 // Load data file if necessary and possible: component_caibws_map
449 loadNonCalibratedComponentDatabase(calibDatabaseDir, component_caibws_map);
450
451 // Create summary calibration workspace: input: component_caibws_map output:
452 // new calibration workspace
453 createOutputCalibrationTable(component_caibws_map, orderedcomponents);
454
455 // Create the summary CSV file
456 saveCalibrationTable(calibDatabaseDir);
457
458 // Clean up memory
459 for (const auto &[compname, calibws] : component_caibws_map) {
460 if (calibws) {
461 g_log.debug() << "Removing " << compname << "calibration table from ADS\n";
462 AnalysisDataService::Instance().remove(calibws->getName());
463 }
464 }
465
466 // output
467 setProperty("OutputWorkspace", mOutputWS);
468}
469
476 std::map<std::string, TableWorkspace_sptr> &calibwsmap) {
477 // Date stamp
478 std::string timestampstr{""};
479 if (mInputWS->run().hasProperty("start_time")) {
480 // string value from start_time
481 timestampstr = mInputWS->run().getProperty("start_time")->value();
482 } else {
483 // string value from run_start if start_time does not exist.
484 // with input workspace validate, there must be at least one exist
485 // between start_time and run_start
486 timestampstr = mInputWS->run().getProperty("run_start")->value();
487 }
488 // convert
489 mDateStamp = convertTimeStamp(timestampstr);
490
491 // Handler
494
495 // Loop over all the components that have been calibrated in the calibration
496 // table
497 size_t num_rows = mInputCalibrationTableWS->rowCount();
498 for (size_t i = 0; i < num_rows; ++i) {
499 // get component name
500 std::string compname = mInputCalibrationTableWS->cell<const std::string>(i, 0);
501 // get file name
502 std::string compdbname = corelliComponentDatabaseName(compname, calibdbdir);
503 // save
504 TableWorkspace_sptr comptablews = handler.saveCompomentDatabase(mDateStamp, compname, compdbname);
505 // add the map
506 calibwsmap[compname] = comptablews;
507 g_log.debug() << "Component " << compname << " is updated to " << compdbname << " and saved to "
508 << comptablews->getName() << "\n";
509 }
510}
511
518 const std::string &calibdbdir, std::map<std::string, TableWorkspace_sptr> &calibwsmap) {
519 // go through all the components
520 for (const auto &[componentname, componentcaltable] : calibwsmap) {
521 // check whether the calibration workspace has been loaded
522 if (componentcaltable) // skip if it has been loaded
523 continue;
524
525 // locate the file
526 std::string compdbname = corelliComponentDatabaseName(componentname, calibdbdir);
527 if (!isFileExist(compdbname)) // skip if the file does not exist
528 {
529 // skip if the database file does not exist
530 g_log.debug() << "Component " << componentname << ": No database file is found at " << compdbname << "\n";
531 continue;
532 }
533
534 // load the database (csv) file and set
536 compdbname, componentname + "_" + mDateStamp);
537 calibwsmap[componentname] = loaded_compcalibws;
538
539 g_log.debug() << "Component " << componentname << " is loaded from " << compdbname << " and saved to "
540 << loaded_compcalibws->getName() << "\n";
541 }
542}
543
544// Create summary calibration workspace: input: component_caibws_map output:
545// new calibration workspace
546void CorelliCalibrationDatabase::createOutputCalibrationTable(std::map<std::string, TableWorkspace_sptr> &calibwsmap,
547 const std::vector<std::string> &orderedcomponents) {
548 // Create an empty calibration table without setting to analysis data service
552
553 for (const auto &componentname : orderedcomponents) {
554 const auto componentcaltable = calibwsmap[componentname];
555 if (componentcaltable) {
556 // only take care of calibrated components (before and now)
558 handler.appendCalibration(mOutputWS, componentname, lastpos);
559 }
560 }
561}
562
563// Create the summary CSV file
564// File name exampe: corelli_instrument_20202015.csv
565void CorelliCalibrationDatabase::saveCalibrationTable(const std::string &calibdbdir) {
566 // file name
567 std::string filename = "corelli_instrument_" + mDateStamp + ".csv";
568 filename = joinPath(calibdbdir, filename);
569
570 // Call the handler: set and save
573 handler.saveCalibrationTable(filename);
574}
575
576//-----------------------------------------------------------------------------
582std::string CorelliCalibrationDatabase::convertTimeStamp(const std::string &run_start_time) {
583 // Get the first sub string by
584 std::string date_str = run_start_time.substr(0, run_start_time.find("T"));
585
586 // Separate year date and time
587 std::string year = date_str.substr(0, date_str.find("-"));
588 std::string monthday = date_str.substr(date_str.find("-") + 1, date_str.size()); // +1 to ignore delimit '-'
589 std::string month = monthday.substr(0, monthday.find("-"));
590 std::string day = monthday.substr(monthday.find("-") + 1,
591 monthday.size()); // +1 to ignore delimit
592 std::string datestamp = year + month + day;
593
594 return datestamp;
595}
596
597//-----------------------------------------------------------------------------
608std::string CorelliCalibrationDatabase::corelliComponentDatabaseName(const std::string &componentname,
609 const std::string &directory) {
610
611 // drop the suffix "/sixteenpack" if found in the component name
612 std::string shortName = componentname;
613 std::string suffix{"/sixteenpack"};
614 size_t pos = componentname.find(suffix);
615 if (pos != std::string::npos)
616 shortName.erase(pos, suffix.length());
617
618 std::string basename = shortName + ".csv";
619 std::string filename = joinPath(directory, basename);
620
621 return filename;
622}
623
624//-----------------------------------------------------------------------------
631std::string CorelliCalibrationDatabase::corelliCalibrationDatabaseName(const std::string &datestamp,
632 const std::string &directory) {
633 std::string basename = datestamp + ".csv";
634 std::string filename = joinPath(directory, basename);
635 return filename;
636}
637
638//-----------------------------------------------------------------------------
644bool CorelliCalibrationDatabase::isFileExist(const std::string &filepath) {
645
646 // TODO - replace by std::filesystem::exists(filename) until C++17 is properly
647 // supported
648 return std::filesystem::exists(filepath);
649}
650
651//-----------------------------------------------------------------------------
658std::string CorelliCalibrationDatabase::joinPath(const std::string &directory, const std::string &basename) {
659 std::filesystem::path dir(directory);
660 std::filesystem::path file(basename);
661 std::filesystem::path fullpath = dir / file;
662
663 return fullpath.string();
664}
665
674void CorelliCalibrationDatabase::setComponentMap(const std::vector<std::string> &componentnames,
675 std::map<std::string, DataObjects::TableWorkspace_sptr> &compmap) {
676 // Add entries
677 for (const auto &compname : componentnames)
678 compmap[compname] = nullptr;
679}
680
692 // Get access to instrument information
693 const auto &component_info = ws->componentInfo();
694
695 // Init output
696 std::vector<std::string> componentnames = {"moderator", "sample-position"};
697
698 // Loop over all the components for bankX/sixteenpack
699 const size_t num_components = component_info.size();
700 for (size_t i = 0; i < num_components; ++i) {
701 std::string compname = component_info.name(i);
702 // a component starts with bank must be a bank
703 if (compname.compare(0, 4, "bank") == 0) {
704 componentnames.push_back(compname + "/sixteenpack");
705 }
706 }
707
708 return componentnames;
709}
710
711} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:422
@ Directory
to specify a directory that must exist
A validator which checks that a workspace has a valid instrument.
TableRow represents a row in a TableWorkspace.
Definition TableRow.h:39
A property class for workspaces.
CorelliCalibrationDatabase: blablabla TODO.
static bool isFileExist(const std::string &filepath)
Check whether a given file does exist.
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
void loadNonCalibratedComponentDatabase(const std::string &calibddir, std::map< std::string, DataObjects::TableWorkspace_sptr > &calibwsmap)
Load data file if necessary and possible: component_caibws_map.
void setComponentMap(const std::vector< std::string > &componentnames, std::map< std::string, DataObjects::TableWorkspace_sptr > &compmap)
Set up a component name - TableWorkspace map for single component calibration.
DataObjects::TableWorkspace_sptr mInputCalibrationTableWS
Input calibration worksapce.
static std::string joinPath(const std::string &directory, const std::string &basename)
Join two string for a new path.
static std::string corelliComponentDatabaseName(const std::string &componentname, const std::string &directory)
get standard component calibration database (CSV) file name
DataObjects::TableWorkspace_sptr mOutputWS
Output calibration worksapce (merged with previous calibrated data)
API::MatrixWorkspace_sptr mInputWS
Input workspace where the calibration is from.
void createOutputCalibrationTable(std::map< std::string, DataObjects::TableWorkspace_sptr > &calibwsmap, const std::vector< std::string > &orderedcomponents)
Create output full set calibration workspace.
void updateComponentDatabaseFiles(const std::string &calibdbdir, std::map< std::string, DataObjects::TableWorkspace_sptr > &calibwsmap)
append the newly calibration to each component csv file
static std::string convertTimeStamp(const std::string &run_start_time)
A static method to convert Mantid datetime string to YYYYMMDD format.
static std::vector< std::string > retrieveInstrumentComponents(const API::MatrixWorkspace_sptr &ws)
Retrieve the bank level components names in order.
static std::string corelliCalibrationDatabaseName(const std::string &datestamp, const std::string &directory)
get standard date-base calibration database (CSV) file name
Class containing static and member methods to work with calibration table workspaces.
ComponentPosition getComponentCalibratedPosition(const std::string &component)
Get the calibration of a component.
static bool isValidCalibrationTableWorkspace(const DataObjects::TableWorkspace_sptr &calibws, std::string &errormsg)
Check whether a TableWorkspace is a valid Corelli geometry calibration table for all components.
static ComponentPosition getLatestCalibratedPosition(const DataObjects::TableWorkspace_sptr &componentcaltable)
Get the last entry (latest update) of a compoent calibrated position.
void setCalibrationTable(const DataObjects::TableWorkspace_sptr &calibws)
Set calibration table file.
static ComponentPosition getCalibratedPosition(const DataObjects::TableWorkspace_sptr &componentcaltable, size_t rownumber)
Get the calibration position in the table (component table or full calibration table)
static DataObjects::TableWorkspace_sptr loadComponentCalibrationTable(const std::string &filename, const std::string &tablewsname)
Load a single-component calibration table.
static DataObjects::TableWorkspace_sptr createCalibrationTableWorkspace(const std::string &wsname, bool iscomponent)
Create a calibration TableWorkspace from scratch for either single component or full set of component...
DataObjects::TableWorkspace_sptr saveCompomentDatabase(const std::string &datestamp, const std::string &component, const std::string &filename)
Save a single component in the calibration workspace.
void saveCalibrationTable(const std::string &filename)
Save the calibration table (of a single date)
static void appendCalibration(const DataObjects::TableWorkspace_sptr &tablews, const std::string &datestamp, const ComponentPosition &pos)
Append a new row to single component calibration table.
std::vector< std::string > getComponentNames()
Get component name from the table.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< ITableWorkspace > ITableWorkspace_sptr
shared pointer to Mantid::API::ITableWorkspace
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
static const std::vector< std::string > calibrationTableColumnTypes
static const std::vector< std::string > calibrationTableColumnNames
std::shared_ptr< TableWorkspace > TableWorkspace_sptr
shared pointer to Mantid::DataObjects::TableWorkspace
Load a single-component database file to a table workspace of history of positions for the component.
Structure to handle all the calibration component positions.
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54