Mantid
Loading...
Searching...
No Matches
LoadFITS.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"
17#include "MantidKernel/Unit.h"
19
20#include <boost/algorithm/string.hpp>
21#include <boost/scoped_array.hpp>
22
23#include <Poco/BinaryReader.h>
24#include <Poco/Path.h>
25
26using namespace Mantid::DataHandling;
27using namespace Mantid::DataObjects;
28using namespace Mantid::API;
29using namespace Mantid::Kernel;
30
31namespace {
32
37template <typename InterpretType> double toDouble(uint8_t *src) {
38 // cppcheck-suppress invalidPointerCast
39 return static_cast<double>(*reinterpret_cast<InterpretType *>(src));
40}
41} // namespace
42
43namespace Mantid::DataHandling {
44
45// Register the algorithm into the AlgorithmFactory
47
48struct FITSInfo {
49 std::vector<std::string> headerItems;
50 std::map<std::string, std::string> headerKeys;
53 int offset;
55 std::vector<size_t> axisPixelLengths;
56 double scale;
57 std::string imageKey;
58 std::string extension;
59 std::string filePath;
60 bool isFloat;
61};
62
63// Static class constants
64const std::string LoadFITS::g_END_KEYNAME = "END";
65const std::string LoadFITS::g_COMMENT_KEYNAME = "COMMENT";
66const std::string LoadFITS::g_XTENSION_KEYNAME = "XTENSION";
67const std::string LoadFITS::g_BIT_DEPTH_NAME = "BitDepthName";
68const std::string LoadFITS::g_ROTATION_NAME = "RotationName";
69const std::string LoadFITS::g_AXIS_NAMES_NAME = "AxisNames";
70const std::string LoadFITS::g_IMAGE_KEY_NAME = "ImageKeyName";
71const std::string LoadFITS::g_HEADER_MAP_NAME = "HeaderMapFile";
72const std::string LoadFITS::g_defaultImgType = "SAMPLE";
73
74// Bits/bytes per pixel. Values fixed in the FITS standard
75const size_t LoadFITS::g_maxBitDepth = 64;
76const size_t LoadFITS::g_maxBytesPP = g_maxBitDepth / 8;
77
82 : m_headerScaleKey(), m_headerOffsetKey(), m_headerBitDepthKey(), m_headerRotationKey(), m_headerImageKeyKey(),
83 m_headerAxisNameKeys(), m_mapFile(), m_pixelCount(0) {
85}
86
94 // Should really improve this to check the file header (of first file at
95 // least) to make sure it contains the fields wanted
96 return (descriptor.extension() == ".fits" || descriptor.extension() == ".fit") ? 80 : 0;
97}
98
104 // Specify file extensions which can be associated with a FITS file.
105 std::vector<std::string> exts, exts2;
106
107 // Declare the Filename algorithm property. Mandatory. Sets the path to the
108 // file to load.
109 exts.clear();
110 exts.emplace_back(".fits");
111 exts.emplace_back(".fit");
112
113 exts2.emplace_back(".*");
114
115 declareProperty(std::make_unique<MultipleFileProperty>("Filename", exts),
116 "The name of the input file (note that you can give "
117 "multiple file names separated by commas).");
118
120 std::make_unique<API::WorkspaceProperty<API::Workspace>>("OutputWorkspace", "", Kernel::Direction::Output));
121
122 declareProperty(std::make_unique<Kernel::PropertyWithValue<bool>>("LoadAsRectImg", false, Kernel::Direction::Input),
123 "If enabled (not by default), the output Workspace2D will have "
124 "one histogram per row and one bin per pixel, such that a 2D "
125 "color plot (color fill plot) will display an image.");
126
127 auto zeroOrPosDbl = std::make_shared<BoundedValidator<double>>();
128 zeroOrPosDbl->setLower(0.0);
129 declareProperty("FilterNoiseLevel", 0.0, zeroOrPosDbl, "Threshold to remove noisy pixels. Try 50 for example.");
130
131 auto posInt = std::make_shared<BoundedValidator<int>>();
132 posInt->setLower(1);
133 declareProperty("BinSize", 1, posInt,
134 "Rebunch n*n on both axes, generating pixels with sums of "
135 "blocks of n by n original pixels.");
136
137 auto posDbl = std::make_shared<BoundedValidator<double>>();
138 posDbl->setLower(std::numeric_limits<double>::epsilon());
139 declareProperty("Scale", 80.0, posDbl, "Pixels per cm.", Kernel::Direction::Input);
140
141 declareProperty(std::make_unique<FileProperty>(g_HEADER_MAP_NAME, "", FileProperty::OptionalDirectory, "",
143 "A file mapping header key names to non-standard names [line separated "
144 "values in the format KEY=VALUE, e.g. BitDepthName=BITPIX] - do not use "
145 "this if you want to keep compatibility with standard FITS files.");
146}
147
152 // for non-standard headers, by default won't do anything
154
155 std::string fName = getPropertyValue("Filename");
156 std::vector<std::string> paths;
157 boost::split(paths, fName, boost::is_any_of(","));
158
159 int binSize = getProperty("BinSize");
160 double noiseThresh = getProperty("FilterNoiseLevel");
161 bool loadAsRectImg = getProperty("LoadAsRectImg");
162 const std::string outWSName = getPropertyValue("OutputWorkspace");
163
164 doLoadFiles(paths, outWSName, loadAsRectImg, binSize, noiseThresh);
165}
166
167void LoadFITS::loadHeader(const std::string &filePath, FITSInfo &header) {
168 header.extension = "";
169 header.filePath = filePath;
170
171 // Get various pieces of information from the file header which are used
172 // to
173 // create the workspace
174 try {
175 parseHeader(header);
176 } catch (std::exception &e) {
177 // Unable to parse the header, throw.
178 throw std::runtime_error("Severe problem found while parsing the header of "
179 "this FITS file (" +
180 filePath + "). This file may not be standard FITS. Error description: " + e.what());
181 }
182
183 // Get and convert specific MANDATORY standard header values which
184 // will are needed to know how to load the data: BITPIX, NAXIS,
185 // NAXISi (where i = 1..NAXIS, e.g. NAXIS2 for two axis).
186 try {
187 std::string tmpBitPix = header.headerKeys[m_headerBitDepthKey];
188 if (boost::contains(tmpBitPix, "-")) {
189 boost::erase_all(tmpBitPix, "-");
190 header.isFloat = true;
191 } else {
192 header.isFloat = false;
193 }
194
195 try {
196 header.bitsPerPixel = boost::lexical_cast<int>(tmpBitPix);
197 } catch (std::exception &e) {
198 throw std::runtime_error("Coult not interpret the entry number of bits per pixel (" + m_headerBitDepthKey +
199 ") as an integer. Error: " + e.what());
200 }
201 // Check that the files use valid BITPIX values
202 // http://archive.stsci.edu/fits/fits_standard/node39.html#SECTION00941000000000000000
203 if (header.bitsPerPixel != 8 && header.bitsPerPixel != 16 && header.bitsPerPixel != 32 && header.bitsPerPixel != 64)
204 // this implicitly includes when 'header.bitsPerPixel >
205 // g_maxBitDepth'
206 throw std::runtime_error("This algorithm only supports 8, 16, 32 or 64 "
207 "bits per pixel as allowed in the FITS standard. The header of "
208 "file '" +
209 filePath + "' says that its bit depth is: " + std::to_string(header.bitsPerPixel));
210 } catch (std::exception &e) {
211 throw std::runtime_error("Failed to process the '" + m_headerBitDepthKey +
212 "' entry (bits per pixel) in the header of this file: " + filePath +
213 ". Error description: " + e.what());
214 }
215
216 try {
217 // Add the image key, use the value in the FITS header if found,
218 // otherwise default (to SAMPLE).
219 auto it = header.headerKeys.find(m_headerImageKeyKey);
220 if (header.headerKeys.end() != it) {
221 header.imageKey = it->second;
222 } else {
223 header.imageKey = g_defaultImgType;
224 }
225 } catch (std::exception &e) {
226 throw std::runtime_error("Failed to process the '" + m_headerImageKeyKey +
227 "' entry (type of image: sample, dark, open) in "
228 "the header of this file: " +
229 filePath + ". Error description: " + e.what());
230 }
231
232 try {
233 header.numberOfAxis = static_cast<int>(m_headerAxisNameKeys.size());
234
235 for (int j = 0; j < header.numberOfAxis; ++j) {
236 header.axisPixelLengths.emplace_back(boost::lexical_cast<size_t>(header.headerKeys[m_headerAxisNameKeys[j]]));
237 // only debug level, when loading multiple files this is very verbose
238 g_log.debug() << "Found axis length header entry: " << m_headerAxisNameKeys[j] << " = "
239 << header.axisPixelLengths.back() << '\n';
240 }
241
242 // Various extensions to the FITS format are used elsewhere, and
243 // must be parsed differently if used. This loader Loader
244 // doesn't support this.
245 header.extension = header.headerKeys[g_XTENSION_KEYNAME];
246 } catch (std::exception &e) {
247 throw std::runtime_error("Failed to process the '" + m_headerNAxisNameKey +
248 "' entries (dimensions) in the header of this file: " + filePath +
249 ". Error description: " + e.what());
250 }
251
252 // scale parameter, header BSCALE in the fits standard
253 if (header.headerKeys[m_headerScaleKey].empty()) {
254 header.scale = 1;
255 } else {
256 try {
257 header.scale = boost::lexical_cast<double>(header.headerKeys[m_headerScaleKey]);
258 } catch (std::exception &e) {
259 throw std::runtime_error("Coult not interpret the entry number of bits per pixel (" + m_headerBitDepthKey +
260 " = " + header.headerKeys[m_headerScaleKey] +
261 ") as a floating point number (double). Error: " + e.what());
262 }
263 }
264
265 // data offsset parameter, header BZERO in the fits standard
266 if (header.headerKeys[m_headerOffsetKey].empty()) {
267 header.offset = 0;
268 } else {
269 try {
270 header.offset = boost::lexical_cast<int>(header.headerKeys[m_headerOffsetKey]);
271 } catch (std::exception & /*e*/) {
272 // still, second try with floating point format (as used for example
273 // by
274 // Starlight XPRESS cameras)
275 try {
276 auto doff = boost::lexical_cast<double>(header.headerKeys[m_headerOffsetKey]);
277 double intPart;
278 if (0 != modf(doff, &intPart)) {
279 // anyway we'll do a cast, but warn if there was a fraction
280 g_log.warning() << "The value given in the FITS header entry for the data "
281 "offset (" +
282 m_headerOffsetKey + " = " + header.headerKeys[m_headerOffsetKey] +
283 ") has a fractional part, and it will be ignored!\n";
284 }
285 header.offset = static_cast<int>(doff);
286 } catch (std::exception &e) {
287 throw std::runtime_error("Coult not interpret the entry number of data offset (" + m_headerOffsetKey + " = " +
288 header.headerKeys[m_headerOffsetKey] +
289 ") as an integer number nor a floating point "
290 "number (double). Error: " +
291 e.what());
292 }
293 }
294 }
295}
296
311void LoadFITS::headerSanityCheck(const FITSInfo &hdr, const FITSInfo &hdrFirst) {
312 bool valid = true;
313 if (!hdr.extension.empty()) {
314 valid = false;
315 g_log.error() << "File " << hdr.filePath << ": extensions found in the header.\n";
316 }
317 if (hdr.numberOfAxis != 2) {
318 valid = false;
319 g_log.error() << "File " << hdr.filePath << ": the number of axes is not 2 but: " << hdr.numberOfAxis << '\n';
320 }
321
322 // Test current item has same axis values as first item.
323 if (hdr.axisPixelLengths[0] != hdrFirst.axisPixelLengths[0]) {
324 valid = false;
325 g_log.error() << "File " << hdr.filePath
326 << ": the number of pixels in the first dimension differs "
327 "from the first file loaded ("
328 << hdrFirst.filePath << "): " << hdr.axisPixelLengths[0] << " != " << hdrFirst.axisPixelLengths[0]
329 << '\n';
330 }
331 if (hdr.axisPixelLengths[1] != hdrFirst.axisPixelLengths[1]) {
332 valid = false;
333 g_log.error() << "File " << hdr.filePath
334 << ": the number of pixels in the second dimension differs"
335 "from the first file loaded ("
336 << hdrFirst.filePath << "): " << hdr.axisPixelLengths[0] << " != " << hdrFirst.axisPixelLengths[0]
337 << '\n';
338 }
339
340 // Check the format is correct and create the Workspace
341 if (!valid) {
342 // Invalid files, record error
343 throw std::runtime_error("An issue has been found in the header of this FITS file: " + hdr.filePath +
344 ". This algorithm currently doesn't support FITS files with "
345 "non-standard extensions, more than two axis "
346 "of data, or has detected that all the files are "
347 "not similar.");
348 }
349}
350
370void LoadFITS::doLoadFiles(const std::vector<std::string> &paths, const std::string &outWSName, bool loadAsRectImg,
371 int binSize, double noiseThresh) {
372 std::vector<FITSInfo> headers;
373 headers.resize(paths.size());
374
375 loadHeader(paths[0], headers[0]);
376
377 // No extension is set -> it's the standard format which we can parse.
378 if (headers[0].numberOfAxis > 0)
379 m_pixelCount += headers[0].axisPixelLengths[0];
380
381 // Presumably 2 axis, but futureproofing.
382 for (int i = 1; i < headers[0].numberOfAxis; ++i) {
383 m_pixelCount *= headers[0].axisPixelLengths[i];
384 }
385
386 // Check consistency of binSize asap
387 for (int i = 0; i < headers[0].numberOfAxis; ++i) {
388 if (0 != (headers[0].axisPixelLengths[i] % binSize)) {
389 throw std::runtime_error("Cannot rebin this image in blocks of " + std::to_string(binSize) + " x " +
390 std::to_string(binSize) + " pixels as requested because the size of dimension " +
391 std::to_string(i + 1) + " (" + std::to_string(headers[0].axisPixelLengths[i]) +
392 ") is not a multiple of the bin size.");
393 }
394 }
395
396 MantidImage imageY(headers[0].axisPixelLengths[1], std::vector<double>(headers[0].axisPixelLengths[0]));
397 MantidImage imageE(headers[0].axisPixelLengths[1], std::vector<double>(headers[0].axisPixelLengths[0]));
398
399 size_t bytes = (headers[0].bitsPerPixel / 8) * m_pixelCount;
400 std::vector<char> buffer;
401 try {
402 buffer.resize(bytes);
403 } catch (std::exception &) {
404 throw std::runtime_error("Could not allocate enough memory to run when trying to allocate " +
405 std::to_string(bytes) + " bytes.");
406 }
407
408 // Create a group for these new workspaces, if the group already exists, add
409 // to it.
410 size_t fileNumberInGroup = 0;
411 WorkspaceGroup_sptr wsGroup;
412
413 if (auto &ads = AnalysisDataService::Instance(); ads.doesExist(outWSName)) {
414 // Get the name of the latest file in group to start numbering from
415 wsGroup = ads.retrieveWS<WorkspaceGroup>(outWSName);
416 std::string latestName = wsGroup->getNames().back();
417 // Set next file number
418 fileNumberInGroup = fetchNumber(latestName) + 1;
419 } else {
420 wsGroup = std::make_shared<WorkspaceGroup>();
421 wsGroup->setTitle(outWSName);
422 }
423
424 const size_t totalWS = headers.size();
425 // Create a progress reporting object
426 API::Progress progress(this, 0.0, 1.0, totalWS + 1);
427 progress.report(0, "Loading file(s) into workspace(s)");
428
429 // Create first workspace (with instrument definition). This is also used as
430 // a template for creating others
431 Workspace2D_sptr imgWS;
432 imgWS =
433 makeWorkspace(headers[0], fileNumberInGroup, buffer, imageY, imageE, imgWS, loadAsRectImg, binSize, noiseThresh);
434 progress.report(1, "First file loaded.");
435
436 wsGroup->addWorkspace(imgWS);
437
438 if (isInstrOtherThanIMAT(headers[0])) {
439 // For now we assume IMAT except when specific headers are found by
440 // isInstrOtherThanIMAT()
441 //
442 // TODO: do this conditional on INSTR='IMAT' when we have proper IMAT .fits
443 // files
444 try {
445 auto loadInst = createChildAlgorithm("LoadInstrument");
446 std::string directoryName = Kernel::ConfigService::Instance().getInstrumentDirectory();
447 directoryName = directoryName + "/IMAT_Definition.xml";
448 loadInst->setPropertyValue("Filename", directoryName);
449 loadInst->setProperty<MatrixWorkspace_sptr>("Workspace", std::dynamic_pointer_cast<MatrixWorkspace>(imgWS));
450 loadInst->execute();
451 } catch (std::exception &ex) {
452 g_log.information("Cannot load the instrument definition. " + std::string(ex.what()));
453 }
454 }
455
456 // don't feel tempted to parallelize this loop as it is - it uses the same
457 // imageY and imageE buffers for all the workspaces
458 for (int64_t i = 1; i < static_cast<int64_t>(totalWS); ++i) {
459 loadHeader(paths[i], headers[i]);
460 // Check each header is valid/supported: standard (no extension to
461 // FITS), has two axis, and it is consistent with the first header
462 headerSanityCheck(headers[i], headers[0]);
463
464 imgWS = makeWorkspace(headers[i], fileNumberInGroup, buffer, imageY, imageE, imgWS, loadAsRectImg, binSize,
465 noiseThresh);
466 progress.report("Loaded file " + std::to_string(i + 1) + " of " + std::to_string(totalWS));
467 wsGroup->addWorkspace(imgWS);
468 }
469
470 setProperty("OutputWorkspace", wsGroup);
471}
472
500 headerInfo.headerSizeMultiplier = 0;
501 std::ifstream istr(headerInfo.filePath.c_str(), std::ios::binary);
502 istr.seekg(0, istr.end);
503 const std::streampos fileSize = istr.tellg();
504 if (fileSize <= 0) {
505 throw std::runtime_error("Found a file that is readable but empty (0 bytes size): " + headerInfo.filePath);
506 }
507 istr.seekg(0, istr.beg);
508
509 Poco::BinaryReader reader(istr);
510
511 // Iterate 80 bytes at a time until header is parsed | 2880 bytes is the
512 // fixed header length of FITS
513 // 2880/80 = 36 iterations required
514 const std::string commentKW = g_COMMENT_KEYNAME;
515 bool endFound = false;
516
517 while (!endFound && (g_BASE_HEADER_SIZE * headerInfo.headerSizeMultiplier < fileSize)) {
518 headerInfo.headerSizeMultiplier++;
519 const int entriesPerHDU = 36;
520
521 for (int i = 0; i < entriesPerHDU; ++i) {
522 // Keep vect of each header item, including comments, and also keep a
523 // map of individual keys.
524 std::string part;
525 reader.readRaw(80, part);
526 headerInfo.headerItems.emplace_back(part);
527
528 // from the FITS standard about COMMENT: This keyword shall have no
529 // associated value; columns 9-80 may contain any ASCII text.
530 // That includes '='
531 if (boost::iequals(commentKW, part.substr(0, commentKW.size()))) {
532 continue;
533 }
534
535 // Add non-comment key/values. These hey and value are separated by the
536 // character '='. All keys should be unique.
537 // This will simply and silenty ignore any entry without the '='
538 auto eqPos = part.find('=');
539 if (eqPos > 0) {
540 std::string key = part.substr(0, eqPos);
541 std::string value = part.substr(eqPos + 1);
542
543 // Comments on header entries are added after the value separated by a /
544 // symbol. Exclude those comments.
545 auto slashPos = value.find('/');
546 if (slashPos > 0)
547 value = value.substr(0, slashPos);
548
549 boost::trim(key);
550 boost::trim(value);
551
552 if (key == g_END_KEYNAME)
553 endFound = true;
554
555 if (!key.empty())
556 headerInfo.headerKeys[key] = value;
557 }
558 }
559 }
560
561 if (!endFound) {
562 throw std::runtime_error("Could not find any valid END entry in the headers of this file after "
563 "scanning the file (" +
564 std::to_string(fileSize) +
565 " bytes). This does not look like a valid FITS file and "
566 "it is not possible to read it correctly as the boundary between "
567 "the headers and the data is undefined.");
568 }
569
570 istr.close();
571}
572
599Workspace2D_sptr LoadFITS::makeWorkspace(const FITSInfo &fileInfo, size_t &newFileNumber, std::vector<char> &buffer,
600 MantidImage &imageY, MantidImage &imageE, const Workspace2D_sptr &parent,
601 bool loadAsRectImg, int binSize, double noiseThresh) {
602 // Create workspace (taking into account already here if rebinning is
603 // going to happen)
605 if (!parent) {
606 if (!loadAsRectImg) {
607 size_t finalPixelCount = m_pixelCount / binSize * binSize;
608 ws = std::dynamic_pointer_cast<Workspace2D>(
609 WorkspaceFactory::Instance().create("Workspace2D", finalPixelCount, 2, 1));
610 } else {
611 ws = std::dynamic_pointer_cast<Workspace2D>(
612 WorkspaceFactory::Instance().create("Workspace2D",
613 fileInfo.axisPixelLengths[1] / binSize, // one bin per column
614 fileInfo.axisPixelLengths[0] / binSize + 1, // one spectrum per row
615 fileInfo.axisPixelLengths[0] / binSize));
616 }
617 } else {
618 ws = std::dynamic_pointer_cast<Workspace2D>(WorkspaceFactory::Instance().create(parent));
619 }
620
621 // this pixel scale property is used to set the workspace X values
622 double cm_1 = getProperty("Scale");
623 // amount of width units (e.g. cm) per pixel
624 double cmpp = 1; // cm per pixel == bin width
625 if (0.0 != cm_1)
626 cmpp /= cm_1;
627 cmpp *= static_cast<double>(binSize);
628
629 if (loadAsRectImg && 1 == binSize) {
630 // set data directly into workspace
631 readDataToWorkspace(fileInfo, cmpp, ws, buffer);
632 } else {
633 readDataToImgs(fileInfo, imageY, imageE, buffer);
634 doFilterNoise(noiseThresh, imageY, imageE);
635
636 // Note this can change the sizes of the images and the number of pixels
637 if (1 == binSize) {
638 ws->setImageYAndE(imageY, imageE, 0, loadAsRectImg, cmpp, false /* no parallel load */);
639
640 } else {
641 MantidImage rebinnedY(imageY.size() / binSize, std::vector<double>(imageY[0].size() / binSize));
642 MantidImage rebinnedE(imageE.size() / binSize, std::vector<double>(imageE[0].size() / binSize));
643
644 doRebin(binSize, imageY, imageE, rebinnedY, rebinnedE);
645 ws->setImageYAndE(rebinnedY, rebinnedE, 0, loadAsRectImg, cmpp, false /* no parallel load */);
646 }
647 }
648
649 try {
650 ws->setTitle(Poco::Path(fileInfo.filePath).getFileName());
651 } catch (std::runtime_error &) {
652 ws->setTitle(padZeros(newFileNumber, g_DIGIT_SIZE_APPEND));
653 }
654 ++newFileNumber;
655
656 addAxesInfoAndLogs(ws, loadAsRectImg, fileInfo, binSize, cmpp);
657
658 return ws;
659}
660
677void LoadFITS::addAxesInfoAndLogs(const Workspace2D_sptr &ws, bool loadAsRectImg, const FITSInfo &fileInfo, int binSize,
678 double cmpp) {
679 // add axes
680 size_t width = fileInfo.axisPixelLengths[0] / binSize;
681 size_t height = fileInfo.axisPixelLengths[1] / binSize;
682 if (loadAsRectImg) {
683 // width/X axis
684 auto axw = std::make_unique<Mantid::API::NumericAxis>(width + 1);
685 axw->title() = "width";
686 for (size_t i = 0; i < width + 1; i++) {
687 axw->setValue(i, static_cast<double>(i) * cmpp);
688 }
689 ws->replaceAxis(0, std::move(axw));
690 // "cm" width label unit
691 std::shared_ptr<Kernel::Units::Label> unitLbl =
692 std::dynamic_pointer_cast<Kernel::Units::Label>(UnitFactory::Instance().create("Label"));
693 unitLbl->setLabel("width", "cm");
694 ws->getAxis(0)->unit() = unitLbl;
695
696 // height/Y axis
697 auto axh = std::make_unique<Mantid::API::NumericAxis>(height);
698 axh->title() = "height";
699 for (size_t i = 0; i < height; i++) {
700 axh->setValue(i, static_cast<double>(i) * cmpp);
701 }
702 ws->replaceAxis(1, std::move(axh));
703 // "cm" height label unit
704 unitLbl = std::dynamic_pointer_cast<Kernel::Units::Label>(UnitFactory::Instance().create("Label"));
705 unitLbl->setLabel("height", "cm");
706 ws->getAxis(1)->unit() = unitLbl;
707
708 ws->setDistribution(true);
709 } else {
710 // TODO: what to do when loading 1pixel - 1 spectrum?
711 }
712 ws->setYUnitLabel("brightness");
713
714 // Add all header info to log.
715 for (const auto &headerKey : fileInfo.headerKeys) {
716 ws->mutableRun().removeLogData(headerKey.first, true);
717 ws->mutableRun().addLogData(new PropertyWithValue<std::string>(headerKey.first, headerKey.second));
718 }
719
720 // Add rotational data to log. Clear first from copied WS
721 auto it = fileInfo.headerKeys.find(m_sampleRotation);
722 ws->mutableRun().removeLogData("Rotation", true);
723 if (fileInfo.headerKeys.end() != it) {
724 auto rot = boost::lexical_cast<double>(it->second);
725 if (rot >= 0) {
726 ws->mutableRun().addLogData(new PropertyWithValue<double>("Rotation", rot));
727 }
728 }
729
730 // Add axis information to log. Clear first from copied WS
731 ws->mutableRun().removeLogData("Axis1", true);
732 ws->mutableRun().addLogData(new PropertyWithValue<int>("Axis1", static_cast<int>(fileInfo.axisPixelLengths[0])));
733 ws->mutableRun().removeLogData("Axis2", true);
734 ws->mutableRun().addLogData(new PropertyWithValue<int>("Axis2", static_cast<int>(fileInfo.axisPixelLengths[1])));
735
736 // Add image key data to log. Clear first from copied WS
737 ws->mutableRun().removeLogData("ImageKey", true);
738 ws->mutableRun().addLogData(new PropertyWithValue<std::string>("ImageKey", fileInfo.imageKey));
739}
740
753void LoadFITS::readDataToWorkspace(const FITSInfo &fileInfo, double cmpp, const Workspace2D_sptr &ws,
754 std::vector<char> &buffer) {
755 const size_t bytespp = (fileInfo.bitsPerPixel / 8);
756 const size_t len = m_pixelCount * bytespp;
757 readInBuffer(fileInfo, buffer, len);
758
759 const size_t nrows(fileInfo.axisPixelLengths[1]), ncols(fileInfo.axisPixelLengths[0]);
760 // Treat buffer as a series of bytes
761 auto *buffer8 = reinterpret_cast<uint8_t *>(buffer.data());
762
764 for (int i = 0; i < static_cast<int>(nrows); ++i) {
765 auto &xVals = ws->mutableX(i);
766 auto &yVals = ws->mutableY(i);
767 auto &eVals = ws->mutableE(i);
768 xVals = static_cast<double>(i) * cmpp;
769
770 for (size_t j = 0; j < ncols; ++j) {
771 // Map from 2D->1D index
772 const size_t start = ((i * (bytespp)) * nrows) + (j * (bytespp));
773 uint8_t const *const buffer8Start = buffer8 + start;
774 // Reverse byte order of current value. Make sure we allocate enough
775 // enough space to hold the size
776 uint8_t byteValue[g_maxBytesPP];
777 std::reverse_copy(buffer8Start, buffer8Start + bytespp, byteValue);
778
779 double val = 0;
780 if (fileInfo.bitsPerPixel == 8) {
781 val = toDouble<uint8_t>(byteValue);
782 } else if (fileInfo.bitsPerPixel == 16) {
783 val = toDouble<uint16_t>(byteValue);
784 } else if (fileInfo.bitsPerPixel == 32 && !fileInfo.isFloat) {
785 val = toDouble<uint32_t>(byteValue);
786 } else if (fileInfo.bitsPerPixel == 64 && !fileInfo.isFloat) {
787 val = toDouble<uint32_t>(byteValue);
788 } else if (fileInfo.bitsPerPixel == 32 && fileInfo.isFloat) {
789 val = toDouble<float>(byteValue);
790 } else if (fileInfo.bitsPerPixel == 64 && fileInfo.isFloat) {
791 val = toDouble<double>(byteValue);
792 }
793
794 val = fileInfo.scale * val - fileInfo.offset;
795 yVals[j] = val;
796 eVals[j] = sqrt(val);
797 }
798 }
799}
800
812void LoadFITS::readDataToImgs(const FITSInfo &fileInfo, MantidImage &imageY, MantidImage &imageE,
813 std::vector<char> &buffer) {
814
815 size_t bytespp = (fileInfo.bitsPerPixel / 8);
816 size_t len = m_pixelCount * bytespp;
817 readInBuffer(fileInfo, buffer, len);
818
819 // create pointer of correct data type to void pointer of the buffer:
820 auto *buffer8 = reinterpret_cast<uint8_t *>(&buffer[0]);
821 std::vector<char> buf(bytespp);
822 char *tmp = buf.data();
823 size_t start = 0;
824
825 for (size_t i = 0; i < fileInfo.axisPixelLengths[1]; ++i) { // width
826 for (size_t j = 0; j < fileInfo.axisPixelLengths[0]; ++j) { // height
827 // If you wanted to PARALLEL_...ize these loops (which doesn't
828 // seem to provide any speed up when loading images one at a
829 // time, you cannot use the start+=bytespp at the end of this
830 // loop. You'd need something like this:
831 //
832 // size_t start =
833 // ((i * (bytespp)) * fileInfo.axisPixelLengths[1]) +
834 // (j * (bytespp));
835 // Reverse byte order of current value
836 std::reverse_copy(buffer8 + start, buffer8 + start + bytespp, tmp);
837 double val = 0;
838 if (fileInfo.bitsPerPixel == 8)
839 val = static_cast<double>(*reinterpret_cast<uint8_t *>(tmp));
840 if (fileInfo.bitsPerPixel == 16)
841 val = static_cast<double>(*reinterpret_cast<uint16_t *>(tmp));
842 if (fileInfo.bitsPerPixel == 32 && !fileInfo.isFloat)
843 val = static_cast<double>(*reinterpret_cast<uint32_t *>(tmp));
844 if (fileInfo.bitsPerPixel == 64 && !fileInfo.isFloat)
845 val = static_cast<double>(*reinterpret_cast<uint64_t *>(tmp));
846 // cppcheck doesn't realise that these are safe casts
847 if (fileInfo.bitsPerPixel == 32 && fileInfo.isFloat) {
848 // cppcheck-suppress invalidPointerCast
849 val = static_cast<double>(*reinterpret_cast<float *>(tmp));
850 }
851 if (fileInfo.bitsPerPixel == 64 && fileInfo.isFloat) {
852 // cppcheck-suppress invalidPointerCast
853 val = *reinterpret_cast<double *>(tmp);
854 }
855 val = fileInfo.scale * val - fileInfo.offset;
856 imageY[i][j] = val;
857 imageE[i][j] = sqrt(val);
858 start += bytespp;
859 }
860 }
861}
862
874void LoadFITS::readInBuffer(const FITSInfo &fileInfo, std::vector<char> &buffer, size_t len) {
875 std::string filename = fileInfo.filePath;
876 std::ifstream file(filename, std::ios::in | std::ios::binary);
877 file.seekg(g_BASE_HEADER_SIZE * fileInfo.headerSizeMultiplier);
878 file.read(&buffer[0], len);
879 if (!file) {
880 throw std::runtime_error("Error while reading file: " + filename + ". Tried to read " + std::to_string(len) +
881 " bytes but got " + std::to_string(file.gcount()) +
882 " bytes. The file and/or its headers may be wrong.");
883 }
884 // all is loaded
885 file.close();
886}
887
897void LoadFITS::doFilterNoise(double thresh, MantidImage &imageY, MantidImage &imageE) {
898 if (thresh <= 0.0)
899 return;
900
901 MantidImage goodY = imageY;
902 MantidImage goodE = imageE;
903
904 // TODO: this is not very smart about the edge pixels (leftmost and
905 // rightmost columns, topmost and bottom rows)
906 for (size_t j = 1; j < (imageY.size() - 1); ++j) {
907 for (size_t i = 1; i < (imageY[0].size() - 1); ++i) {
908
909 if (((imageY[j][i] - imageY[j][i - 1]) > thresh) && ((imageY[j][i] - imageY[j][i + 1]) > thresh) &&
910 ((imageY[j][i] - imageY[j - 1][i]) > thresh) && ((imageY[j][i] - imageY[j + 1][i]) > thresh))
911 goodY[j][i] = 0;
912 else
913 goodY[j][i] = 1;
914
915 if (((imageE[j][i] - imageE[j][i - 1]) > thresh) && ((imageE[j][i] - imageE[j][i + 1]) > thresh) &&
916 ((imageE[j][i] - imageE[j - 1][i]) > thresh) && ((imageE[j][i] - imageE[j + 1][i]) > thresh))
917 goodE[j][i] = 0;
918 else
919 goodE[j][i] = 1;
920 }
921 }
922
923 for (size_t j = 1; j < (imageY.size() - 1); ++j) {
924 for (size_t i = 1; i < (imageY[0].size() - 1); ++i) {
925 if (goodY[j][i] == 0.0) {
926 if (goodY[j - 1][i] != 0.0 || goodY[j + 1][i] != 0.0 || goodY[j][i - 1] != 0.0 || goodY[j][i + 1] != 0.0) {
927 imageY[j][i] = goodY[j - 1][i] * imageY[j - 1][i] + goodY[j + 1][i] * imageY[j + 1][i] +
928 goodY[j][i - 1] * imageY[j][i - 1] + goodY[j][i + 1] * imageY[j][i + 1];
929 }
930 }
931
932 if (goodE[j][i] == 0.0) {
933 if (goodE[j - 1][i] != 0.0 || goodE[j + 1][i] != 0.0 || goodE[j][i - 1] != 0.0 || goodE[j][i + 1] != 0.0) {
934 imageE[j][i] = goodE[j - 1][i] * imageE[j - 1][i] + goodE[j + 1][i] * imageE[j + 1][i] +
935 goodE[j][i - 1] * imageE[j][i - 1] + goodE[j][i + 1] * imageE[j][i + 1];
936 }
937 }
938 }
939 }
940}
941
951void LoadFITS::doRebin(size_t rebin, const MantidImage &imageY, const MantidImage &imageE, MantidImage &rebinnedY,
952 MantidImage &rebinnedE) {
953 if (1 >= rebin)
954 return;
955
956 for (size_t j = 0; j < (rebinnedY.size() - rebin + 1); ++j) {
957 for (size_t i = 0; i < (rebinnedY[0].size() - rebin + 1); ++i) {
958 double accumY = 0.0;
959 double accumE = 0.0;
960 size_t origJ = j * rebin;
961 size_t origI = i * rebin;
962 for (size_t k = 0; k < rebin; ++k) {
963 for (size_t l = 0; l < rebin; ++l) {
964 accumY += imageY[origJ + k][origI + l];
965 accumE += imageE[origJ + k][origI + l];
966 }
967 }
968 rebinnedY[j][i] = accumY;
969 rebinnedE[j][i] = accumE;
970 }
971 }
972}
973
986 bool res = false;
987
988 // Images taken with Starlight camera contain this header entry:
989 // INSTRUME='Starlight Xpress CCD'
990 auto it = hdr.headerKeys.find("INSTRUME");
991 if (hdr.headerKeys.end() != it && boost::contains(it->second, "Starlight")) {
992 // For now, do nothing, just tell
993 // Cameras used for HiFi and EMU are in principle only used
994 // occasionally for calibration
995 g_log.information() << "Found this in the file headers: " << it->first << " = " << it->second
996 << ". This file seems to come from a Starlight camera, "
997 "as used for calibration of the instruments HiFi and EMU (and"
998 "possibly others). Note: not "
999 "loading instrument definition.\n";
1000 }
1001
1002 return res;
1003}
1004
1011 // Inits all the absolutely necessary keywords
1012 // standard headers (If SIMPLE=T)
1013 m_headerScaleKey = "BSCALE";
1014 m_headerOffsetKey = "BZERO";
1015 m_headerBitDepthKey = "BITPIX";
1016 m_headerImageKeyKey = "IMAGE_TYPE"; // This is a "HIERARCH Image/Type= "
1017 m_headerRotationKey = "ROTATION";
1018
1019 m_headerNAxisNameKey = "NAXIS";
1020 m_headerAxisNameKeys.emplace_back("NAXIS1");
1021 m_headerAxisNameKeys.emplace_back("NAXIS2");
1022
1023 m_mapFile = "";
1024
1025 // extensions
1026 m_sampleRotation = "HIERARCH Sample/Tomo_Angle";
1027 m_imageType = "HIERARCH Image/Type";
1028}
1029
1035 return;
1036
1037 // If a map file is selected, use that.
1039 std::ifstream fStream(name.c_str());
1040
1041 try {
1042 // Ensure valid file
1043 if (fStream.good()) {
1044 // Get lines, split words, verify and add to map.
1045 std::string line;
1046 std::vector<std::string> lineSplit;
1047 while (getline(fStream, line)) {
1048 boost::split(lineSplit, line, boost::is_any_of("="));
1049
1050 if (lineSplit[0] == g_ROTATION_NAME && !lineSplit[1].empty())
1051 m_headerRotationKey = lineSplit[1];
1052
1053 if (lineSplit[0] == g_BIT_DEPTH_NAME && !lineSplit[1].empty())
1054 m_headerBitDepthKey = lineSplit[1];
1055
1056 if (lineSplit[0] == g_AXIS_NAMES_NAME && !lineSplit[1].empty()) {
1057 m_headerAxisNameKeys.clear();
1058 boost::split(m_headerAxisNameKeys, lineSplit[1], boost::is_any_of(","));
1059 }
1060
1061 if (lineSplit[0] == g_IMAGE_KEY_NAME && !lineSplit[1].empty()) {
1062 m_headerImageKeyKey = lineSplit[1];
1063 }
1064 }
1065
1066 fStream.close();
1067 } else {
1068 throw std::runtime_error("Error while trying to read header keys mapping file: " + name);
1069 }
1070 } catch (...) {
1071 g_log.error("Cannot load specified map file, using property values "
1072 "and/or defaults.");
1073 }
1074}
1075
1085size_t LoadFITS::fetchNumber(const std::string &name) {
1086 std::string tmpStr;
1087 for (auto it = name.end() - 1; isdigit(*it); --it) {
1088 tmpStr.insert(0, 1, *it);
1089 }
1090 while (tmpStr.length() > 0 && tmpStr[0] == '0') {
1091 tmpStr.erase(tmpStr.begin());
1092 }
1093 return (tmpStr.length() > 0) ? boost::lexical_cast<size_t>(tmpStr) : 0;
1094}
1095
1107std::string LoadFITS::padZeros(const size_t number, const size_t totalDigitCount) {
1108 std::ostringstream ss;
1109 ss << std::setw(static_cast<int>(totalDigitCount)) << std::setfill('0') << static_cast<int>(number);
1110
1111 return ss.str();
1112}
1113} // namespace Mantid::DataHandling
gsl_vector * tmp
double value
The value of the point.
Definition: FitMW.cpp:51
double height
Definition: GetAllEi.cpp:155
#define PARALLEL_FOR_NO_WSP_CHECK()
#define DECLARE_FILELOADER_ALGORITHM(classname)
DECLARE_FILELOADER_ALGORITHM should be used in place of the standard DECLARE_ALGORITHM macro when wri...
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Definition: Algorithm.cpp:842
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
@ OptionalDirectory
to specify a directory that does not have to exist
Definition: FileProperty.h:55
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
Class to hold a set of workspaces.
std::vector< std::string > getNames() const
Returns the names of workspaces that make up this group.
A property class for workspaces.
LoadFITS: Load one or more of FITS files into a Workspace2D.
Definition: LoadFITS.h:36
static const size_t g_maxBitDepth
Definition: LoadFITS.h:162
void exec() override
Execution code.
Definition: LoadFITS.cpp:151
static const std::string g_AXIS_NAMES_NAME
Definition: LoadFITS.h:155
std::string padZeros(const size_t number, const size_t totalDigitCount)
Adds 0's to the front of a number to create a string of size totalDigitCount including number.
Definition: LoadFITS.cpp:1107
static const int g_BASE_HEADER_SIZE
size of a FITS header block (room for 36 entries, of 80 characters each), in bytes.
Definition: LoadFITS.h:145
std::string m_headerNAxisNameKey
Definition: LoadFITS.h:127
void doLoadFiles(const std::vector< std::string > &paths, const std::string &outWSName, bool loadAsRectImg, int binSize, double noiseThresh)
Loads files into workspace(s)
Definition: LoadFITS.cpp:370
int confidence(Kernel::FileDescriptor &descriptor) const override
Returns a confidence value that this algorithm can load a file.
Definition: LoadFITS.cpp:93
void doFilterNoise(double thresh, API::MantidImage &imageY, API::MantidImage &imageE)
filter noise pixel by pixel
Definition: LoadFITS.cpp:897
const std::string name() const override
Algorithm's name for identification overriding a virtual method.
Definition: LoadFITS.h:41
void init() override
Initialisation code.
Definition: LoadFITS.cpp:103
static const std::string g_XTENSION_KEYNAME
Definition: LoadFITS.h:153
void headerSanityCheck(const FITSInfo &hdr, const FITSInfo &hdrFirst)
Once loaded, check against standard and limitations of this algorithm.
Definition: LoadFITS.cpp:311
static const std::string g_COMMENT_KEYNAME
Definition: LoadFITS.h:152
static const size_t g_DIGIT_SIZE_APPEND
Definition: LoadFITS.h:141
void readDataToWorkspace(const FITSInfo &fileInfo, double cmpp, const DataObjects::Workspace2D_sptr &ws, std::vector< char > &buffer)
Reads the data (FITS matrix) from a single FITS file into a workspace (directly into the spectra,...
Definition: LoadFITS.cpp:753
static const size_t g_maxBytesPP
Definition: LoadFITS.h:164
std::vector< std::string > m_headerAxisNameKeys
Definition: LoadFITS.h:128
static const std::string g_BIT_DEPTH_NAME
Definition: LoadFITS.h:154
DataObjects::Workspace2D_sptr makeWorkspace(const FITSInfo &fileInfo, size_t &newFileNumber, std::vector< char > &buffer, API::MantidImage &imageY, API::MantidImage &imageE, const DataObjects::Workspace2D_sptr &parent, bool loadAsRectImg=false, int binSize=1, double noiseThresh=false)
Initialises a workspace with IDF and fills it with data.
Definition: LoadFITS.cpp:599
void readDataToImgs(const FITSInfo &fileInfo, API::MantidImage &imageY, API::MantidImage &imageE, std::vector< char > &buffer)
Reads the data (FITS matrix) from a single FITS file into image objects (Y and E).
Definition: LoadFITS.cpp:812
void setupDefaultKeywordNames()
Sets several keyword names with default (and standard) values.
Definition: LoadFITS.cpp:1010
void doRebin(size_t rebin, const API::MantidImage &imageY, const API::MantidImage &imageE, API::MantidImage &rebinnedY, API::MantidImage &rebinnedE)
rebin the matrix/image
Definition: LoadFITS.cpp:951
void readInBuffer(const FITSInfo &fileInfo, std::vector< char > &buffer, size_t len)
Reads the data (FITS matrix) from a single FITS file into a buffer.
Definition: LoadFITS.cpp:874
static const std::string g_HEADER_MAP_NAME
Definition: LoadFITS.h:158
void parseHeader(FITSInfo &headerInfo)
Parses the header values for the FITS file.
Definition: LoadFITS.cpp:499
static const std::string g_defaultImgType
Definition: LoadFITS.h:131
size_t fetchNumber(const std::string &name)
Returns the trailing number from a string minus leading 0's (so 25 from workspace_000025)
Definition: LoadFITS.cpp:1085
void mapHeaderKeys()
Maps the header keys to specified values.
Definition: LoadFITS.cpp:1033
static const std::string g_END_KEYNAME
Definition: LoadFITS.h:151
void loadHeader(const std::string &filePath, FITSInfo &header)
Load the FITS header(s) from one fits file into a struct.
Definition: LoadFITS.cpp:167
static const std::string g_IMAGE_KEY_NAME
Definition: LoadFITS.h:157
static const std::string g_ROTATION_NAME
Definition: LoadFITS.h:156
void addAxesInfoAndLogs(const DataObjects::Workspace2D_sptr &ws, bool loadAsRectImg, const FITSInfo &fileInfo, int binSize, double cmpp)
Add information to the workspace being loaded: labels, units, logs related to the image size,...
Definition: LoadFITS.cpp:677
bool isInstrOtherThanIMAT(const FITSInfo &hdr)
identifies fits coming from 'other' cameras by specific headers
Definition: LoadFITS.cpp:985
Defines a wrapper around an open file.
const std::string & extension() const
Access the file extension.
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:114
void error(const std::string &msg)
Logs at error level.
Definition: Logger.cpp:77
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
The concrete, templated class for properties.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< WorkspaceGroup > WorkspaceGroup_sptr
shared pointer to Mantid::API::WorkspaceGroup
std::vector< std::vector< double > > MantidImage
typedef for the image type
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< Workspace2D > Workspace2D_sptr
shared pointer to Mantid::DataObjects::Workspace2D
std::unique_ptr< T > create(const P &parent, const IndexArg &indexArg, const HistArg &histArg)
This is the create() method that all the other create() methods call.
std::string to_string(const wide_integer< Bits, Signed > &n)
std::vector< std::string > headerItems
Definition: LoadFITS.cpp:49
std::map< std::string, std::string > headerKeys
Definition: LoadFITS.cpp:50
std::vector< size_t > axisPixelLengths
Definition: LoadFITS.cpp:55
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54