Mantid
Loading...
Searching...
No Matches
SaveIsawPeaks.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 +
10#include "MantidAPI/Run.h"
11#include "MantidAPI/Sample.h"
22#include "MantidKernel/Utils.h"
23#include <boost/algorithm/string/trim.hpp>
24#include <filesystem>
25#include <fstream>
26
27using namespace Mantid::Geometry;
28using namespace Mantid::DataObjects;
29using namespace Mantid::Kernel;
30using namespace Mantid::API;
31
32namespace Mantid::Crystal {
33
34// Register the algorithm into the AlgorithmFactory
35DECLARE_ALGORITHM(SaveIsawPeaks)
36
37
39void SaveIsawPeaks::init() {
40 declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>("InputWorkspace", "", Direction::Input,
41 std::make_shared<InstrumentValidator>()),
42 "An input PeaksWorkspace with an instrument.");
43
44 declareProperty("AppendFile", false,
45 "Append to file if true.\n"
46 "If false, new file (default).");
47
48 const std::vector<std::string> exts{".peaks", ".integrate"};
49 declareProperty(std::make_unique<FileProperty>("Filename", "", FileProperty::Save, exts),
50 "Path to an ISAW-style peaks or integrate file to save.");
51
52 declareProperty(std::make_unique<WorkspaceProperty<Workspace2D>>("ProfileWorkspace", "", Direction::Input,
54 "An optional Workspace2D of profiles from integrating cylinder.");
55
56 declareProperty("RenumberPeaks", false,
57 "If true, sequential peak numbers\n"
58 "If false, keep original numbering (default).");
59}
60
64 // Section header
65 std::string header = "2 SEQN H K L COL ROW CHAN "
66 " L2 2_THETA AZ WL D IPK "
67 " INTI SIGI RFLG";
68
69 const std::string filename = getPropertyValue("Filename");
70 m_ws = getProperty("InputWorkspace");
71 const auto &peaks = m_ws->getPeaks();
72 inst = m_ws->getInstrument();
73 if (!inst)
74 throw std::runtime_error("No instrument in the Workspace. Cannot save DetCal file.");
75 const auto &detectorInfo = m_ws->detectorInfo();
76
77 // We must sort the peaks first by run, then bank #, and save the list of
78 // workspace indices of it
79 using bankMap_t = std::map<int, std::vector<size_t>>;
80 using runMap_t = std::map<int, bankMap_t>;
81 std::set<int, std::less<int>> uniqueBanks;
82 // We cannot assume the peaks have bank type detector modules, so we have a
83 // string to check this
84 std::string bankPart = "bank";
85 if (m_ws->getInstrumentName() == "WISH")
86 bankPart = "WISHpanel";
87
88 // Get all children
89 const auto &componentInfo = m_ws->componentInfo();
90 // iterate over the top level components, which contain the banks
91 size_t const rootIndex = componentInfo.root();
92 auto const topChildren = componentInfo.children(rootIndex);
93 for (size_t const i : topChildren) {
94 size_t bankParent = componentInfo.findBankParent(i, bankPart);
95 auto const children = componentInfo.children(bankParent);
96 for (size_t const child : children) {
97 std::string bankName = componentInfo.name(child);
98 boost::trim(bankName);
99 boost::erase_all(bankName, bankPart);
100 int bank = 0;
101 Strings::convert(bankName, bank);
102 if (bank == 0)
103 continue;
104 if (bankMasked(child, detectorInfo))
105 continue;
106 // Track unique bank numbers
107 uniqueBanks.insert(bank);
108 }
109 }
110 runMap_t runMap;
111 for (size_t i = 0; i < peaks.size(); ++i) {
112 const Peak &p = peaks[i];
113 if (p.getIntMNP() != V3D(0, 0, 0))
115 int run = p.getRunNumber();
116 int bank = 0;
117 std::string bankName = p.getBankName();
118 if (bankName.size() <= 4) {
119 g_log.information() << "Could not interpret bank number of peak " << i << "(" << bankName << ")\n";
120 continue;
121 }
122 // Save the "bank" part once to check whether it really is a bank
123 if (bankPart == "?")
124 bankPart = bankName.substr(0, 4);
125 // Take out the "bank" part of the bank name and convert to an int
126 if (bankPart == "bank")
127 bankName = bankName.substr(4, bankName.size() - 4);
128 else if (bankPart == "WISHpanel")
129 bankName = bankName.substr(9, bankName.size() - 9);
130 Strings::convert(bankName, bank);
131
132 // Save in the map
133 runMap[run][bank].emplace_back(i);
134 }
136 header = "2 SEQN H K L M N P COL ROW CHAN "
137 " L2 2_THETA AZ WL D IPK "
138 " INTI SIGI RFLG";
139
140 if (!inst)
141 throw std::runtime_error("No instrument in PeaksWorkspace. Cannot save peaks file.");
142
143 if (bankPart != "bank" && bankPart != "WISHpanel" && bankPart != "?") {
144 std::ostringstream mess;
145 mess << "Detector module of type " << bankPart << " not supported in ISAWPeaks. Cannot save peaks file";
146 throw std::runtime_error(mess.str());
147 }
148
149 double l1;
150 V3D beamline;
151 double beamline_norm;
152 V3D samplePos;
153 inst->getInstrumentParameters(l1, beamline, beamline_norm, samplePos);
154
155 std::ofstream out;
156 bool append = getProperty("AppendFile");
157 const bool renumber = getProperty("RenumberPeaks");
158
159 // do not append if file does not exist
160 if (!std::filesystem::exists(filename))
161 append = false;
162
163 int appendPeakNumb = 0;
164 if (append) {
165 std::ifstream infile(filename.c_str());
166 std::string line;
167 while (!infile.eof()) // To get you all the lines.
168 {
169 getline(infile, line); // Saves the line in STRING.
170 if (infile.eof())
171 break;
172 std::stringstream ss(line);
173 double three;
174 ss >> three;
175 if (three == 3) {
176 int peakNumber;
177 ss >> peakNumber;
178 appendPeakNumb = std::max(peakNumber, appendPeakNumb);
179 }
180 }
181
182 infile.close();
183 out.open(filename.c_str(), std::ios::app);
184 appendPeakNumb = appendPeakNumb + 1;
185 } else {
186 out.open(filename.c_str());
187
188 const auto instrumentName = m_ws->getInstrumentName();
189 std::string facilityName;
190 try {
191 facilityName = ConfigService::Instance().getInstrument(instrumentName).facility().name();
192 } catch (Exception::NotFoundError &) {
193 g_log.warning() << "Instrument " << instrumentName
194 << " not found at any defined facility. Setting facility "
195 "name to Unknown\n";
196 facilityName = "Unknown";
197 }
198 out << "Version: 2.0 Facility: " << facilityName;
199 out << " Instrument: " << instrumentName << " Date: ";
200
201 // TODO: The experiment date might be more useful than the instrument date.
202 // For now, this allows the proper instrument to be loaded back after
203 // saving.
204 Types::Core::DateAndTime expDate = inst->getValidFromDate() + 1.0;
205 out << expDate.toISO8601String();
207 out << " MOD";
208 out << '\n';
209
210 out << "6 L1 T0_SHIFT\n";
211 out << "7 " << std::setw(10);
212 out << std::setprecision(4) << std::fixed << (l1 * 100);
213 out << std::setw(12) << std::setprecision(3) << std::fixed;
214 // Time offset from property
215 const API::Run &run = m_ws->run();
216 double T0 = 0.0;
217 if (run.hasProperty("T0")) {
218 T0 = run.getPropertyValueAsType<double>("T0");
219 if (T0 != 0) {
220 g_log.notice() << "T0 = " << T0 << '\n';
221 }
222 }
223 out << T0 << '\n';
224
225 // Save .detcal info
226 out << "4 DETNUM NROWS NCOLS WIDTH HEIGHT DEPTH DETD CenterX "
227 " CenterY CenterZ BaseX BaseY BaseZ UpX UpY "
228 " UpZ\n";
229 // Here would save each detector...
230 for (const auto bank : uniqueBanks) {
231 // Build up the bank name
232 std::ostringstream mess;
233 if (bankPart == "bank")
234 mess << "bank" << bank;
235 else if (bankPart == "WISHpanel") {
236 mess << "WISHpanel" << std::setfill('0') << std::setw(2) << bank;
237 }
238
239 std::string bankName = mess.str();
240 // Retrieve it
241 size_t bankIndex = ComponentInfo::invalidIndex;
242 try {
243 bankIndex = componentInfo.indexOfAny(bankName);
244 } catch (const std::invalid_argument &) {
245 // No such bank
246 }
247 if (bankIndex != ComponentInfo::invalidIndex && m_ws->getInstrumentName() == "CORELLI") {
248 // for Corelli with sixteenpack under bank
249 const auto children = componentInfo.children(bankIndex);
250 if (!children.empty()) {
251 bankIndex = children[0];
252 }
253 }
254 if (bankIndex != ComponentInfo::invalidIndex) {
255 // Center of the detector
256 V3D center = componentInfo.position(bankIndex);
257
258 // Distance to center of detector
259 double detd = (center - componentInfo.samplePosition()).norm();
260 int NCOLS, NROWS;
261 double xsize, ysize;
262 sizeBanks(bankName, NCOLS, NROWS, xsize, ysize);
263 // Base unit vector (along the horizontal, X axis)
264 int midX = NCOLS / 2;
265 int midY = NROWS / 2;
266 V3D base = findPixelPos(bankName, midX + 1, midY) - findPixelPos(bankName, midX, midY);
267 base.normalize();
268
269 // Up unit vector (along the vertical, Y axis)
270 V3D up = findPixelPos(bankName, midX, midY + 1) - findPixelPos(bankName, midX, midY);
271 up.normalize();
272
273 // Write the line
274 out << "5 " << std::setw(6) << std::right << bank << " " << std::setw(6) << std::right << NROWS << " "
275 << std::setw(6) << std::right << NCOLS << " " << std::setw(7) << std::right << std::fixed
276 << std::setprecision(4) << 100.0 * xsize << " " << std::setw(7) << std::right << std::fixed
277 << std::setprecision(4) << 100.0 * ysize << " "
278 << " 0.2000 " << std::setw(6) << std::right << std::fixed << std::setprecision(2) << 100.0 * detd << " "
279 << std::setw(9) << std::right << std::fixed << std::setprecision(4) << 100.0 * center.X() << " "
280 << std::setw(9) << std::right << std::fixed << std::setprecision(4) << 100.0 * center.Y() << " "
281 << std::setw(9) << std::right << std::fixed << std::setprecision(4) << 100.0 * center.Z() << " "
282 << std::setw(8) << std::right << std::fixed << std::setprecision(5) << base.X() << " " << std::setw(8)
283 << std::right << std::fixed << std::setprecision(5) << base.Y() << " " << std::setw(8) << std::right
284 << std::fixed << std::setprecision(5) << base.Z() << " " << std::setw(8) << std::right << std::fixed
285 << std::setprecision(5) << up.X() << " " << std::setw(8) << std::right << std::fixed << std::setprecision(5)
286 << up.Y() << " " << std::setw(8) << std::right << std::fixed << std::setprecision(5) << up.Z() << " \n";
287
288 } else
289 g_log.warning() << "Information about detector module " << bankName << " not found and recognised\n";
290 }
291 }
292 // HKL's are flipped by -1 because of the internal Q convention
293 // unless Crystallography convention
294 double qSign = -1.0;
295 if (m_ws->getConvention() == "Crystallography")
296 qSign = 1.0;
297
298 // Save all Peaks
299 // Go in order of run numbers
300 int sequenceNumber = appendPeakNumb;
301 for (const auto &runBankMap : runMap) {
302 // Start of a new run
303 const int run = runBankMap.first;
304 const auto &bankMap = runBankMap.second;
305
306 for (const auto &bankIDs : bankMap) {
307 // Start of a new bank.
308 const int bank = bankIDs.first;
309 const auto &ids = bankIDs.second;
310
311 if (!ids.empty()) {
312 // Write the bank header
313 out << "0 NRUN DETNUM CHI PHI OMEGA MONCNT\n";
314 out << "1 " << std::setw(5) << run << std::setw(7) << std::right << bank;
315
316 // Determine goniometer angles by calculating from the goniometer matrix
317 // of a peak in the list
318 Goniometer gon(peaks[ids[0]].getGoniometerMatrix());
319 std::vector<double> angles = gon.getEulerAngles("yzy");
320
321 double phi = angles[2];
322 double chi = angles[1];
323 double omega = angles[0];
324
325 out << std::setw(8) << std::fixed << std::setprecision(2) << chi << " ";
326 out << std::setw(8) << std::fixed << std::setprecision(2) << phi << " ";
327 out << std::setw(8) << std::fixed << std::setprecision(2) << omega << " ";
328
329 // Get the monitor count from the first peak (should all be the same for
330 // one run)
331 const size_t first_peak_index = ids[0];
332 const auto &first_peak = peaks[first_peak_index];
333 const double monct = first_peak.getMonitorCount();
334 out << std::setw(12) << static_cast<int>(monct) << '\n';
335 out << header << '\n';
336
337 // Go through each peak at this run / bank
338 for (auto wi : ids) {
339 const auto &peak = peaks[wi];
340
341 // Sequence (run) number
342 std::string firstNumber = "3";
344 firstNumber = "9";
345 }
346 if (renumber) {
347 out << firstNumber << std::setw(7) << sequenceNumber;
348 sequenceNumber++;
349 } else {
350 out << firstNumber << std::setw(7) << peak.getPeakNumber() + appendPeakNumb;
351 }
352
353 // HKL's are flipped by -1 because of the internal Q convention
354 // unless Crystallography convention
356 const V3D mod = peak.getIntMNP();
357 const auto intHKL = peak.getIntHKL();
358 out << std::setw(5) << Utils::round(qSign * intHKL.X()) << std::setw(5) << Utils::round(qSign * intHKL.Y())
359 << std::setw(5) << Utils::round(qSign * intHKL.Z());
360
361 out << std::setw(5) << Utils::round(qSign * mod[0]) << std::setw(5) << Utils::round(qSign * mod[1])
362 << std::setw(5) << Utils::round(qSign * mod[2]);
363 } else {
364 out << std::setw(5) << Utils::round(qSign * peak.getH()) << std::setw(5)
365 << Utils::round(qSign * peak.getK()) << std::setw(5) << Utils::round(qSign * peak.getL());
366 }
367
368 // Row/column
369 out << std::setw(8) << std::fixed << std::setprecision(2) << static_cast<double>(peak.getCol()) << " ";
370
371 out << std::setw(8) << std::fixed << std::setprecision(2) << static_cast<double>(peak.getRow()) << " ";
372
373 out << std::setw(8) << std::fixed << std::setprecision(0) << peak.getTOF() << " ";
374
375 out << std::setw(9) << std::fixed << std::setprecision(3) << (peak.getL2() * 100.0) << " ";
376
377 // This is the scattered beam direction
378 const V3D dir = peak.getDetPos() - inst->getSample()->getPos();
379 double scattering, azimuth;
380
381 // Two-theta = polar angle = scattering angle = between +Z vector and
382 // the scattered beam
383 scattering = dir.angle(V3D(0.0, 0.0, 1.0));
384
385 // "Azimuthal" angle: project the scattered beam direction onto the XY
386 // plane,
387 // and calculate the angle between that and the +X axis (right-handed)
388 azimuth = atan2(dir.Y(), dir.X());
389
390 out << std::setw(9) << std::fixed << std::setprecision(5) << scattering << " "; // two-theta scattering
391
392 out << std::setw(9) << std::fixed << std::setprecision(5) << azimuth << " ";
393
394 out << std::setw(10) << std::fixed << std::setprecision(6) << peak.getWavelength() << " ";
395
396 out << std::setw(9) << std::fixed << std::setprecision(4) << peak.getDSpacing() << " ";
397
398 out << std::setw(8) << std::fixed << std::setprecision(0) << int(peak.getBinCount()) << " ";
399
400 out << std::setw(10) << std::fixed << std::setprecision(2) << peak.getIntensity() << " ";
401
402 out << std::setw(7) << std::fixed << std::setprecision(2) << peak.getSigmaIntensity() << " ";
403
404 int thisReflag = 310;
405 out << std::setw(5) << thisReflag;
406
407 out << '\n';
408
409 Workspace2D_sptr wsProfile2D = getProperty("ProfileWorkspace");
410 if (wsProfile2D) {
411 out << "8";
412 const auto &yValues = wsProfile2D->y(wi);
413 for (size_t j = 0; j < yValues.size(); j++) {
414 out << std::setw(8) << static_cast<int>(yValues[j]);
415 if ((j + 1) % 10 == 0) {
416 out << '\n';
417 if (j + 1 != yValues.size())
418 out << "8";
419 }
420 }
421 }
422 }
423 }
424 }
425 }
426
427 out.flush();
428 out.close();
429}
430
431bool SaveIsawPeaks::bankMasked(size_t componentIndex, const Geometry::DetectorInfo &detectorInfo) {
432 const auto &componentInfo = this->m_ws->componentInfo();
433 auto children = componentInfo.children(componentIndex);
434
435 if (!children.empty() && componentInfo.name(children[0]) == "sixteenpack") {
436 children = componentInfo.children(children[0]);
437 }
438
439 for (const auto &colIndex : children) { // cppcheck-suppress useStlAlgorithm
440 auto grandchildren = componentInfo.children(colIndex);
441 for (const auto &rowIndex : grandchildren) { // cppcheck-suppress useStlAlgorithm
442 if (componentInfo.isDetector(rowIndex)) {
443 const auto detID = detectorInfo.detid(rowIndex);
444 if (detID < 0)
445 continue;
446 const auto detIndex = detectorInfo.indexOf(detID);
447 if (!detectorInfo.isMasked(detIndex)) {
448 return false;
449 }
450 }
451 }
452 }
453 return true;
454}
455
456V3D SaveIsawPeaks::findPixelPos(const std::string &bankName, int col, int row) {
457 auto parent = inst->getComponentByName(bankName);
458 if (parent->type() == "RectangularDetector") {
459 const auto &componentInfo = this->m_ws->componentInfo();
460 const size_t bankIndex = componentInfo.indexOf(parent->getComponentID());
461 return componentInfo.position(componentInfo.detectorIndexAtXYZ(bankIndex, col, row, 0));
462 } else {
463 const auto &componentInfo = this->m_ws->componentInfo();
464 const size_t parentIndex = componentInfo.indexOfAny(bankName);
465 auto children = componentInfo.children(parentIndex);
466
467 if (!children.empty() && componentInfo.name(children[0]) == "sixteenpack") {
468 children = componentInfo.children(children[0]);
469 }
470 int col0 = col - 1;
471 // WISH detectors are in bank in this order in instrument
472 if (m_ws->getInstrumentName() == "WISH")
473 col0 = (col % 2 == 0 ? col / 2 + 75 : (col - 1) / 2);
474
475 auto grandchildren = componentInfo.children(children[col0]);
476 // NOTE: query ComponentInfo rather than dereferencing componentID(). The latter yields the *base* component,
477 // whose getPos() ignores the ParameterMap and so reports uncalibrated IDF geometry.
478 return componentInfo.position(grandchildren[row - 1]);
479 }
480}
481void SaveIsawPeaks::sizeBanks(const std::string &bankName, int &NCOLS, int &NROWS, double &xsize, double &ysize) {
482 if (bankName == "None")
483 return;
484 const auto &componentInfo = this->m_ws->componentInfo();
485 size_t parentIndex;
486 try {
487 parentIndex = componentInfo.indexOfAny(bankName);
488 } catch (std::invalid_argument &) {
489 return;
490 }
491 if (componentInfo.isGridDetector(parentIndex)) {
492 const auto grid = componentInfo.pixelGridComponent(parentIndex);
493
494 NCOLS = grid.nX;
495 NROWS = grid.nY;
496 xsize = grid.nX * grid.xStep;
497 ysize = grid.nY * grid.yStep;
498 } else {
499 auto children = componentInfo.children(parentIndex);
500
501 if (!children.empty() && componentInfo.name(children[0]) == "sixteenpack") {
502 children = componentInfo.children(children[0]);
503 }
504
505 auto grandchildren = componentInfo.children(children[0]);
506 NROWS = static_cast<int>(grandchildren.size());
507 NCOLS = static_cast<int>(children.size());
508 // NOTE: measure via ComponentInfo::position(), not by dereferencing componentID(). The latter yields the *base*
509 // component, whose getDistance() ignores the ParameterMap and so reports uncalibrated IDF geometry.
510 xsize = componentInfo.position(children[0]).distance(componentInfo.position(children[NCOLS - 1]));
511 ysize = componentInfo.position(grandchildren[0]).distance(componentInfo.position(grandchildren[NROWS - 1]));
512 }
513}
514void SaveIsawPeaks::writeOffsets(std::ofstream &out, double qSign, const std::vector<double> &offset) {
515 for (size_t i = 0; i < 3; i++) {
516 out << std::setw(12) << std::fixed << std::setprecision(6) << qSign * offset[i] << " ";
517 }
518}
519} // namespace Mantid::Crystal
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:547
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:426
@ Save
to specify a file to write to, the file may or may not exist
bool hasProperty(const std::string &name) const
Does the property exist on the object.
HeldType getPropertyValueAsType(const std::string &name) const
Get the value of a property as the given TYPE.
This class stores information regarding an experimental run as a series of log entries.
Definition Run.h:36
A property class for workspaces.
Save a PeaksWorkspace to a ISAW-style ASCII .peaks file.
bool m_isModulatedStructure
Flag for writing modulated structures.
DataObjects::PeaksWorkspace_sptr m_ws
void writeOffsets(std::ofstream &out, double qSign, const std::vector< double > &offset)
void exec() override
Run the algorithm.
Kernel::V3D findPixelPos(const std::string &bankName, int col, int row)
find position for rectangular and non-rectangular
bool bankMasked(size_t componentIndex, const Geometry::DetectorInfo &detectorInfo)
Geometry::Instrument_const_sptr inst
void sizeBanks(const std::string &bankName, int &NCOLS, int &NROWS, double &xsize, double &ysize)
Mantid::Kernel::V3D getIntMNP() const override
Return the int MNP vector.
Definition BasePeak.cpp:116
int getRunNumber() const override
Return the run number this peak was measured at.
Definition BasePeak.cpp:78
Structure describing a single-crystal peak.
Definition Peak.h:34
const std::string & getBankName() const
Find the name of the bank that is the parent of the detector.
Definition Peak.cpp:371
static constexpr size_t invalidIndex
Returned by indexOfOrInvalid() for a component ID that is not part of this instrument.
Geometry::DetectorInfo is an intermediate step towards a DetectorInfo that is part of Instrument-2....
bool isMasked(const size_t index) const
Returns true if the detector is masked.
size_t indexOf(const detid_t id) const
Returns the index of the detector with the given detector ID.
detid_t detid(const size_t index) const
Returns the detector ID for the given logical detector index.
Class to represent a particular goniometer setting, which is described by the rotation matrix.
Definition Goniometer.h:55
std::vector< double > getEulerAngles(const std::string &convention="YZX")
Return Euler angles acording to a convention.
Exception for when an item is not found in a collection.
Definition Exception.h:145
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
Class for 3D vectors.
Definition V3D.h:35
double distance(const V3D &v) const noexcept
Calculates the distance between two vectors.
Definition V3D.h:294
constexpr double X() const noexcept
Get x.
Definition V3D.h:239
double normalize()
Make a normalized vector (return norm value)
Definition V3D.cpp:129
constexpr double Y() const noexcept
Get y.
Definition V3D.h:240
double angle(const V3D &) const
Angle between this and another vector.
Definition V3D.cpp:162
constexpr double Z() const noexcept
Get z.
Definition V3D.h:241
std::shared_ptr< Workspace2D > Workspace2D_sptr
shared pointer to Mantid::DataObjects::Workspace2D
int convert(const std::string &A, T &out)
Convert a string into a number.
Definition Strings.cpp:697
long round(double x)
Custom rounding method for a double->long because none is portable in C++ (!)
Definition Utils.h:37
@ Input
An input workspace.
Definition Property.h:54