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 (inst->getName() == "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 = inst->getName();
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 std::shared_ptr<const IComponent> det = inst->getComponentByName(bankName);
242 if (inst->getName() == "CORELLI") // for Corelli with sixteenpack under bank
243 {
244 const size_t bankIndex = componentInfo.indexOfAny(bankName);
245 const auto children = componentInfo.children(bankIndex);
246 if (!children.empty()) {
247 det.reset(componentInfo.componentID(children[0]), NoDeleting());
248 }
249 }
250 if (det) {
251 // Center of the detector.
252 // NOTE: componentID() above returns the *base* component, whose getPos() ignores the ParameterMap and so
253 // reports uncalibrated IDF geometry. ComponentInfo is the parameterized view, so query it instead.
254 V3D center = componentInfo.position(componentInfo.indexOf(det->getComponentID()));
255
256 // Distance to center of detector
257 double detd = (center - inst->getSample()->getPos()).norm();
258 int NCOLS, NROWS;
259 double xsize, ysize;
260 sizeBanks(bankName, NCOLS, NROWS, xsize, ysize);
261 // Base unit vector (along the horizontal, X axis)
262 int midX = NCOLS / 2;
263 int midY = NROWS / 2;
264 V3D base = findPixelPos(bankName, midX + 1, midY) - findPixelPos(bankName, midX, midY);
265 base.normalize();
266
267 // Up unit vector (along the vertical, Y axis)
268 V3D up = findPixelPos(bankName, midX, midY + 1) - findPixelPos(bankName, midX, midY);
269 up.normalize();
270
271 // Write the line
272 out << "5 " << std::setw(6) << std::right << bank << " " << std::setw(6) << std::right << NROWS << " "
273 << std::setw(6) << std::right << NCOLS << " " << std::setw(7) << std::right << std::fixed
274 << std::setprecision(4) << 100.0 * xsize << " " << std::setw(7) << std::right << std::fixed
275 << std::setprecision(4) << 100.0 * ysize << " "
276 << " 0.2000 " << std::setw(6) << std::right << std::fixed << std::setprecision(2) << 100.0 * detd << " "
277 << std::setw(9) << std::right << std::fixed << std::setprecision(4) << 100.0 * center.X() << " "
278 << std::setw(9) << std::right << std::fixed << std::setprecision(4) << 100.0 * center.Y() << " "
279 << std::setw(9) << std::right << std::fixed << std::setprecision(4) << 100.0 * center.Z() << " "
280 << std::setw(8) << std::right << std::fixed << std::setprecision(5) << base.X() << " " << std::setw(8)
281 << std::right << std::fixed << std::setprecision(5) << base.Y() << " " << std::setw(8) << std::right
282 << std::fixed << std::setprecision(5) << base.Z() << " " << std::setw(8) << std::right << std::fixed
283 << std::setprecision(5) << up.X() << " " << std::setw(8) << std::right << std::fixed << std::setprecision(5)
284 << up.Y() << " " << std::setw(8) << std::right << std::fixed << std::setprecision(5) << up.Z() << " \n";
285
286 } else
287 g_log.warning() << "Information about detector module " << bankName << " not found and recognised\n";
288 }
289 }
290 // HKL's are flipped by -1 because of the internal Q convention
291 // unless Crystallography convention
292 double qSign = -1.0;
293 if (m_ws->getConvention() == "Crystallography")
294 qSign = 1.0;
295
296 // Save all Peaks
297 // Go in order of run numbers
298 int sequenceNumber = appendPeakNumb;
299 for (const auto &runBankMap : runMap) {
300 // Start of a new run
301 const int run = runBankMap.first;
302 const auto &bankMap = runBankMap.second;
303
304 for (const auto &bankIDs : bankMap) {
305 // Start of a new bank.
306 const int bank = bankIDs.first;
307 const auto &ids = bankIDs.second;
308
309 if (!ids.empty()) {
310 // Write the bank header
311 out << "0 NRUN DETNUM CHI PHI OMEGA MONCNT\n";
312 out << "1 " << std::setw(5) << run << std::setw(7) << std::right << bank;
313
314 // Determine goniometer angles by calculating from the goniometer matrix
315 // of a peak in the list
316 Goniometer gon(peaks[ids[0]].getGoniometerMatrix());
317 std::vector<double> angles = gon.getEulerAngles("yzy");
318
319 double phi = angles[2];
320 double chi = angles[1];
321 double omega = angles[0];
322
323 out << std::setw(8) << std::fixed << std::setprecision(2) << chi << " ";
324 out << std::setw(8) << std::fixed << std::setprecision(2) << phi << " ";
325 out << std::setw(8) << std::fixed << std::setprecision(2) << omega << " ";
326
327 // Get the monitor count from the first peak (should all be the same for
328 // one run)
329 const size_t first_peak_index = ids[0];
330 const auto &first_peak = peaks[first_peak_index];
331 const double monct = first_peak.getMonitorCount();
332 out << std::setw(12) << static_cast<int>(monct) << '\n';
333 out << header << '\n';
334
335 // Go through each peak at this run / bank
336 for (auto wi : ids) {
337 const auto &peak = peaks[wi];
338
339 // Sequence (run) number
340 std::string firstNumber = "3";
342 firstNumber = "9";
343 }
344 if (renumber) {
345 out << firstNumber << std::setw(7) << sequenceNumber;
346 sequenceNumber++;
347 } else {
348 out << firstNumber << std::setw(7) << peak.getPeakNumber() + appendPeakNumb;
349 }
350
351 // HKL's are flipped by -1 because of the internal Q convention
352 // unless Crystallography convention
354 const V3D mod = peak.getIntMNP();
355 const auto intHKL = peak.getIntHKL();
356 out << std::setw(5) << Utils::round(qSign * intHKL.X()) << std::setw(5) << Utils::round(qSign * intHKL.Y())
357 << std::setw(5) << Utils::round(qSign * intHKL.Z());
358
359 out << std::setw(5) << Utils::round(qSign * mod[0]) << std::setw(5) << Utils::round(qSign * mod[1])
360 << std::setw(5) << Utils::round(qSign * mod[2]);
361 } else {
362 out << std::setw(5) << Utils::round(qSign * peak.getH()) << std::setw(5)
363 << Utils::round(qSign * peak.getK()) << std::setw(5) << Utils::round(qSign * peak.getL());
364 }
365
366 // Row/column
367 out << std::setw(8) << std::fixed << std::setprecision(2) << static_cast<double>(peak.getCol()) << " ";
368
369 out << std::setw(8) << std::fixed << std::setprecision(2) << static_cast<double>(peak.getRow()) << " ";
370
371 out << std::setw(8) << std::fixed << std::setprecision(0) << peak.getTOF() << " ";
372
373 out << std::setw(9) << std::fixed << std::setprecision(3) << (peak.getL2() * 100.0) << " ";
374
375 // This is the scattered beam direction
376 const V3D dir = peak.getDetPos() - inst->getSample()->getPos();
377 double scattering, azimuth;
378
379 // Two-theta = polar angle = scattering angle = between +Z vector and
380 // the scattered beam
381 scattering = dir.angle(V3D(0.0, 0.0, 1.0));
382
383 // "Azimuthal" angle: project the scattered beam direction onto the XY
384 // plane,
385 // and calculate the angle between that and the +X axis (right-handed)
386 azimuth = atan2(dir.Y(), dir.X());
387
388 out << std::setw(9) << std::fixed << std::setprecision(5) << scattering << " "; // two-theta scattering
389
390 out << std::setw(9) << std::fixed << std::setprecision(5) << azimuth << " ";
391
392 out << std::setw(10) << std::fixed << std::setprecision(6) << peak.getWavelength() << " ";
393
394 out << std::setw(9) << std::fixed << std::setprecision(4) << peak.getDSpacing() << " ";
395
396 out << std::setw(8) << std::fixed << std::setprecision(0) << int(peak.getBinCount()) << " ";
397
398 out << std::setw(10) << std::fixed << std::setprecision(2) << peak.getIntensity() << " ";
399
400 out << std::setw(7) << std::fixed << std::setprecision(2) << peak.getSigmaIntensity() << " ";
401
402 int thisReflag = 310;
403 out << std::setw(5) << thisReflag;
404
405 out << '\n';
406
407 Workspace2D_sptr wsProfile2D = getProperty("ProfileWorkspace");
408 if (wsProfile2D) {
409 out << "8";
410 const auto &yValues = wsProfile2D->y(wi);
411 for (size_t j = 0; j < yValues.size(); j++) {
412 out << std::setw(8) << static_cast<int>(yValues[j]);
413 if ((j + 1) % 10 == 0) {
414 out << '\n';
415 if (j + 1 != yValues.size())
416 out << "8";
417 }
418 }
419 }
420 }
421 }
422 }
423 }
424
425 out.flush();
426 out.close();
427}
428
429bool SaveIsawPeaks::bankMasked(size_t componentIndex, const Geometry::DetectorInfo &detectorInfo) {
430 const auto &componentInfo = this->m_ws->componentInfo();
431 auto children = componentInfo.children(componentIndex);
432
433 if (!children.empty() && componentInfo.name(children[0]) == "sixteenpack") {
434 children = componentInfo.children(children[0]);
435 }
436
437 for (const auto &colIndex : children) { // cppcheck-suppress useStlAlgorithm
438 auto grandchildren = componentInfo.children(colIndex);
439 for (const auto &rowIndex : grandchildren) { // cppcheck-suppress useStlAlgorithm
440 if (componentInfo.isDetector(rowIndex)) {
441 const auto detID = detectorInfo.detid(rowIndex);
442 if (detID < 0)
443 continue;
444 const auto detIndex = detectorInfo.indexOf(detID);
445 if (!detectorInfo.isMasked(detIndex)) {
446 return false;
447 }
448 }
449 }
450 }
451 return true;
452}
453
454V3D SaveIsawPeaks::findPixelPos(const std::string &bankName, int col, int row) {
455 auto parent = inst->getComponentByName(bankName);
456 if (parent->type() == "RectangularDetector") {
457 const auto &componentInfo = this->m_ws->componentInfo();
458 const size_t bankIndex = componentInfo.indexOf(parent->getComponentID());
459 return componentInfo.position(componentInfo.detectorIndexAtXYZ(bankIndex, col, row, 0));
460 } else {
461 const auto &componentInfo = this->m_ws->componentInfo();
462 const size_t parentIndex = componentInfo.indexOfAny(bankName);
463 auto children = componentInfo.children(parentIndex);
464
465 if (!children.empty() && componentInfo.name(children[0]) == "sixteenpack") {
466 children = componentInfo.children(children[0]);
467 }
468 int col0 = col - 1;
469 // WISH detectors are in bank in this order in instrument
470 if (inst->getName() == "WISH")
471 col0 = (col % 2 == 0 ? col / 2 + 75 : (col - 1) / 2);
472
473 auto grandchildren = componentInfo.children(children[col0]);
474 // NOTE: query ComponentInfo rather than dereferencing componentID(). The latter yields the *base* component,
475 // whose getPos() ignores the ParameterMap and so reports uncalibrated IDF geometry.
476 return componentInfo.position(grandchildren[row - 1]);
477 }
478}
479void SaveIsawPeaks::sizeBanks(const std::string &bankName, int &NCOLS, int &NROWS, double &xsize, double &ysize) {
480 if (bankName == "None")
481 return;
482 const auto &componentInfo = this->m_ws->componentInfo();
483 size_t parentIndex;
484 try {
485 parentIndex = componentInfo.indexOfAny(bankName);
486 } catch (std::invalid_argument &) {
487 return;
488 }
489 if (componentInfo.isGridDetector(parentIndex)) {
490 const auto grid = componentInfo.pixelGridComponent(parentIndex);
491
492 NCOLS = grid.nX;
493 NROWS = grid.nY;
494 xsize = grid.nX * grid.xStep;
495 ysize = grid.nY * grid.yStep;
496 } else {
497 auto children = componentInfo.children(parentIndex);
498
499 if (!children.empty() && componentInfo.name(children[0]) == "sixteenpack") {
500 children = componentInfo.children(children[0]);
501 }
502
503 auto grandchildren = componentInfo.children(children[0]);
504 NROWS = static_cast<int>(grandchildren.size());
505 NCOLS = static_cast<int>(children.size());
506 // NOTE: measure via ComponentInfo::position(), not by dereferencing componentID(). The latter yields the *base*
507 // component, whose getDistance() ignores the ParameterMap and so reports uncalibrated IDF geometry.
508 xsize = componentInfo.position(children[0]).distance(componentInfo.position(children[NCOLS - 1]));
509 ysize = componentInfo.position(grandchildren[0]).distance(componentInfo.position(grandchildren[NROWS - 1]));
510 }
511}
512void SaveIsawPeaks::writeOffsets(std::ofstream &out, double qSign, const std::vector<double> &offset) {
513 for (size_t i = 0; i < 3; i++) {
514 out << std::setw(12) << std::fixed << std::setprecision(6) << qSign * offset[i] << " ";
515 }
516}
517} // namespace Mantid::Crystal
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:542
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:423
@ 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:115
int getRunNumber() const override
Return the run number this peak was measured at.
Definition BasePeak.cpp:77
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:347
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:34
double distance(const V3D &v) const noexcept
Calculates the distance between two vectors.
Definition V3D.h:293
constexpr double X() const noexcept
Get x.
Definition V3D.h:238
double normalize()
Make a normalized vector (return norm value)
Definition V3D.cpp:129
constexpr double Y() const noexcept
Get y.
Definition V3D.h:239
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:240
This functor is used as the deleter object of a shared_ptr to effectively erase ownership Raw pointer...
Definition IComponent.h:171
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:696
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:53