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