20#include <boost/algorithm/string.hpp>
21#include <boost/scoped_array.hpp>
23#include <Poco/BinaryReader.h>
37template <
typename InterpretType>
double toDouble(uint8_t *src) {
39 return static_cast<double>(*
reinterpret_cast<InterpretType *
>(src));
82 : m_headerScaleKey(), m_headerOffsetKey(), m_headerBitDepthKey(), m_headerRotationKey(), m_headerImageKeyKey(),
83 m_headerAxisNameKeys(), m_mapFile(), m_pixelCount(0) {
96 return (descriptor.
extension() ==
".fits" || descriptor.
extension() ==
".fit") ? 80 : 0;
105 std::vector<std::string> exts, exts2;
110 exts.emplace_back(
".fits");
111 exts.emplace_back(
".fit");
113 exts2.emplace_back(
".*");
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).");
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.");
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.");
131 auto posInt = std::make_shared<BoundedValidator<int>>();
134 "Rebunch n*n on both axes, generating pixels with sums of "
135 "blocks of n by n original pixels.");
137 auto posDbl = std::make_shared<BoundedValidator<double>>();
138 posDbl->setLower(std::numeric_limits<double>::epsilon());
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.");
156 std::vector<std::string> paths;
157 boost::split(paths, fName, boost::is_any_of(
","));
160 double noiseThresh =
getProperty(
"FilterNoiseLevel");
164 doLoadFiles(paths, outWSName, loadAsRectImg, binSize, noiseThresh);
168 header.extension =
"";
169 header.filePath = filePath;
176 }
catch (std::exception &e) {
178 throw std::runtime_error(
"Severe problem found while parsing the header of "
180 filePath +
"). This file may not be standard FITS. Error description: " + e.what());
188 if (boost::contains(tmpBitPix,
"-")) {
189 boost::erase_all(tmpBitPix,
"-");
190 header.isFloat =
true;
192 header.isFloat =
false;
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());
203 if (header.bitsPerPixel != 8 && header.bitsPerPixel != 16 && header.bitsPerPixel != 32 && header.bitsPerPixel != 64)
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 "
209 filePath +
"' says that its bit depth is: " +
std::to_string(header.bitsPerPixel));
210 }
catch (std::exception &e) {
212 "' entry (bits per pixel) in the header of this file: " + filePath +
213 ". Error description: " + e.what());
220 if (header.headerKeys.end() != it) {
221 header.imageKey = it->second;
225 }
catch (std::exception &e) {
227 "' entry (type of image: sample, dark, open) in "
228 "the header of this file: " +
229 filePath +
". Error description: " + e.what());
235 for (
int j = 0; j < header.numberOfAxis; ++j) {
236 header.axisPixelLengths.emplace_back(boost::lexical_cast<size_t>(header.headerKeys[
m_headerAxisNameKeys[j]]));
239 << header.axisPixelLengths.back() <<
'\n';
246 }
catch (std::exception &e) {
248 "' entries (dimensions) in the header of this file: " + filePath +
249 ". Error description: " + e.what());
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 +
261 ") as a floating point number (double). Error: " + e.what());
271 }
catch (std::exception & ) {
278 if (0 != modf(doff, &intPart)) {
280 g_log.
warning() <<
"The value given in the FITS header entry for the data "
283 ") has a fractional part, and it will be ignored!\n";
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 +
" = " +
289 ") as an integer number nor a floating point "
290 "number (double). Error: " +
326 <<
": the number of pixels in the first dimension differs "
327 "from the first file loaded ("
334 <<
": the number of pixels in the second dimension differs"
335 "from the first file loaded ("
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 "
371 int binSize,
double noiseThresh) {
372 std::vector<FITSInfo> headers;
373 headers.resize(paths.size());
378 if (headers[0].numberOfAxis > 0)
382 for (
int i = 1; i < headers[0].numberOfAxis; ++i) {
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 " +
392 ") is not a multiple of the bin size.");
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]));
399 size_t bytes = (headers[0].bitsPerPixel / 8) *
m_pixelCount;
400 std::vector<char> buffer;
402 buffer.resize(bytes);
403 }
catch (std::exception &) {
404 throw std::runtime_error(
"Could not allocate enough memory to run when trying to allocate " +
410 size_t fileNumberInGroup = 0;
416 std::string latestName = wsGroup->
getNames().back();
420 wsGroup = std::make_shared<WorkspaceGroup>();
421 wsGroup->setTitle(outWSName);
424 const size_t totalWS = headers.size();
427 progress.report(0,
"Loading file(s) into workspace(s)");
433 makeWorkspace(headers[0], fileNumberInGroup, buffer, imageY, imageE, imgWS, loadAsRectImg, binSize, noiseThresh);
434 progress.report(1,
"First file loaded.");
436 wsGroup->addWorkspace(imgWS);
447 directoryName = directoryName +
"/IMAT_Definition.xml";
448 loadInst->setPropertyValue(
"Filename", directoryName);
449 loadInst->setProperty<
MatrixWorkspace_sptr>(
"Workspace", std::dynamic_pointer_cast<MatrixWorkspace>(imgWS));
451 }
catch (std::exception &ex) {
452 g_log.
information(
"Cannot load the instrument definition. " + std::string(ex.what()));
458 for (int64_t i = 1; i < static_cast<int64_t>(totalWS); ++i) {
464 imgWS =
makeWorkspace(headers[i], fileNumberInGroup, buffer, imageY, imageE, imgWS, loadAsRectImg, binSize,
467 wsGroup->addWorkspace(imgWS);
501 std::ifstream istr(headerInfo.
filePath.c_str(), std::ios::binary);
502 istr.seekg(0, istr.end);
503 const std::streampos fileSize = istr.tellg();
505 throw std::runtime_error(
"Found a file that is readable but empty (0 bytes size): " + headerInfo.
filePath);
507 istr.seekg(0, istr.beg);
509 Poco::BinaryReader reader(istr);
515 bool endFound =
false;
519 const int entriesPerHDU = 36;
521 for (
int i = 0; i < entriesPerHDU; ++i) {
525 reader.readRaw(80, part);
531 if (boost::iequals(commentKW, part.substr(0, commentKW.size()))) {
538 auto eqPos = part.find(
'=');
540 std::string key = part.substr(0, eqPos);
541 std::string
value = part.substr(eqPos + 1);
545 auto slashPos =
value.find(
'/');
562 throw std::runtime_error(
"Could not find any valid END entry in the headers of this file after "
563 "scanning the file (" +
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.");
601 bool loadAsRectImg,
int binSize,
double noiseThresh) {
606 if (!loadAsRectImg) {
607 size_t finalPixelCount =
m_pixelCount / binSize * binSize;
608 ws = std::dynamic_pointer_cast<Workspace2D>(
611 ws = std::dynamic_pointer_cast<Workspace2D>(
627 cmpp *=
static_cast<double>(binSize);
629 if (loadAsRectImg && 1 == binSize) {
638 ws->setImageYAndE(imageY, imageE, 0, loadAsRectImg, cmpp,
false );
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));
644 doRebin(binSize, imageY, imageE, rebinnedY, rebinnedE);
645 ws->setImageYAndE(rebinnedY, rebinnedE, 0, loadAsRectImg, cmpp,
false );
650 ws->setTitle(Poco::Path(fileInfo.
filePath).getFileName());
651 }
catch (std::runtime_error &) {
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);
689 ws->replaceAxis(0, std::move(axw));
691 std::shared_ptr<Kernel::Units::Label> unitLbl =
693 unitLbl->setLabel(
"width",
"cm");
694 ws->getAxis(0)->unit() = unitLbl;
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);
702 ws->replaceAxis(1, std::move(axh));
705 unitLbl->setLabel(
"height",
"cm");
706 ws->getAxis(1)->unit() = unitLbl;
708 ws->setDistribution(
true);
712 ws->setYUnitLabel(
"brightness");
715 for (
const auto &headerKey : fileInfo.
headerKeys) {
716 ws->mutableRun().removeLogData(headerKey.first,
true);
722 ws->mutableRun().removeLogData(
"Rotation",
true);
724 auto rot = boost::lexical_cast<double>(it->second);
731 ws->mutableRun().removeLogData(
"Axis1",
true);
733 ws->mutableRun().removeLogData(
"Axis2",
true);
737 ws->mutableRun().removeLogData(
"ImageKey",
true);
754 std::vector<char> &buffer) {
761 auto *buffer8 =
reinterpret_cast<uint8_t *
>(buffer.data());
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;
770 for (
size_t j = 0; j <
ncols; ++j) {
772 const size_t start = ((i * (bytespp)) *
nrows) + (j * (bytespp));
773 uint8_t
const *
const buffer8Start = buffer8 + start;
777 std::reverse_copy(buffer8Start, buffer8Start + bytespp, byteValue);
781 val = toDouble<uint8_t>(byteValue);
783 val = toDouble<uint16_t>(byteValue);
785 val = toDouble<uint32_t>(byteValue);
787 val = toDouble<uint32_t>(byteValue);
789 val = toDouble<float>(byteValue);
791 val = toDouble<double>(byteValue);
796 eVals[j] = sqrt(val);
813 std::vector<char> &buffer) {
820 auto *buffer8 =
reinterpret_cast<uint8_t *
>(&buffer[0]);
821 std::vector<char> buf(bytespp);
822 char *
tmp = buf.data();
836 std::reverse_copy(buffer8 + start, buffer8 + start + bytespp,
tmp);
839 val =
static_cast<double>(*
reinterpret_cast<uint8_t *
>(
tmp));
841 val =
static_cast<double>(*
reinterpret_cast<uint16_t *
>(
tmp));
843 val =
static_cast<double>(*
reinterpret_cast<uint32_t *
>(
tmp));
845 val =
static_cast<double>(*
reinterpret_cast<uint64_t *
>(
tmp));
849 val =
static_cast<double>(*
reinterpret_cast<float *
>(
tmp));
853 val = *
reinterpret_cast<double *
>(
tmp);
857 imageE[i][j] = sqrt(val);
875 std::string filename = fileInfo.
filePath;
876 std::ifstream file(filename, std::ios::in | std::ios::binary);
878 file.read(&buffer[0], len);
880 throw std::runtime_error(
"Error while reading file: " + filename +
". Tried to read " +
std::to_string(len) +
882 " bytes. The file and/or its headers may be wrong.");
906 for (
size_t j = 1; j < (imageY.size() - 1); ++j) {
907 for (
size_t i = 1; i < (imageY[0].size() - 1); ++i) {
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))
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))
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];
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];
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) {
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];
968 rebinnedY[j][i] = accumY;
969 rebinnedE[j][i] = accumE;
991 if (hdr.
headerKeys.end() != it && boost::contains(it->second,
"Starlight")) {
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";
1039 std::ifstream fStream(
name.c_str());
1043 if (fStream.good()) {
1046 std::vector<std::string> lineSplit;
1047 while (getline(fStream, line)) {
1048 boost::split(lineSplit, line, boost::is_any_of(
"="));
1068 throw std::runtime_error(
"Error while trying to read header keys mapping file: " +
name);
1071 g_log.
error(
"Cannot load specified map file, using property values "
1072 "and/or defaults.");
1087 for (
auto it =
name.end() - 1; isdigit(*it); --it) {
1088 tmpStr.insert(0, 1, *it);
1090 while (tmpStr.length() > 0 && tmpStr[0] ==
'0') {
1091 tmpStr.erase(tmpStr.begin());
1093 return (tmpStr.length() > 0) ? boost::lexical_cast<size_t>(tmpStr) : 0;
1108 std::ostringstream ss;
1109 ss << std::setw(static_cast<int>(totalDigitCount)) << std::setfill(
'0') <<
static_cast<int>(number);
double value
The value of the point.
#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.
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.
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.
std::string m_headerRotationKey
static const size_t g_maxBitDepth
void exec() override
Execution code.
std::string m_headerBitDepthKey
static const std::string g_AXIS_NAMES_NAME
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.
std::string m_headerNAxisNameKey
void doLoadFiles(const std::vector< std::string > &paths, const std::string &outWSName, bool loadAsRectImg, int binSize, double noiseThresh)
Loads files into workspace(s)
int confidence(Kernel::FileDescriptor &descriptor) const override
Returns a confidence value that this algorithm can load a file.
void doFilterNoise(double thresh, API::MantidImage &imageY, API::MantidImage &imageE)
filter noise pixel by pixel
const std::string name() const override
Algorithm's name for identification overriding a virtual method.
void init() override
Initialisation code.
static const std::string g_XTENSION_KEYNAME
void headerSanityCheck(const FITSInfo &hdr, const FITSInfo &hdrFirst)
Once loaded, check against standard and limitations of this algorithm.
static const std::string g_COMMENT_KEYNAME
std::string m_headerImageKeyKey
static const size_t g_DIGIT_SIZE_APPEND
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,...
static const size_t g_maxBytesPP
std::vector< std::string > m_headerAxisNameKeys
static const std::string g_BIT_DEPTH_NAME
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.
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).
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
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.
static const std::string g_HEADER_MAP_NAME
void parseHeader(FITSInfo &headerInfo)
Parses the header values for the FITS file.
static const std::string g_defaultImgType
std::string m_headerScaleKey
std::string m_headerOffsetKey
size_t fetchNumber(const std::string &name)
Returns the trailing number from a string minus leading 0's (so 25 from workspace_000025)
std::string m_sampleRotation
void mapHeaderKeys()
Maps the header keys to specified values.
static const std::string g_END_KEYNAME
void loadHeader(const std::string &filePath, FITSInfo &header)
Load the FITS header(s) from one fits file into a struct.
static const std::string g_IMAGE_KEY_NAME
static const std::string g_ROTATION_NAME
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,...
bool isInstrOtherThanIMAT(const FITSInfo &hdr)
identifies fits coming from 'other' cameras by specific headers
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.
void error(const std::string &msg)
Logs at error level.
void warning(const std::string &msg)
Logs at warning level.
void information(const std::string &msg)
Logs at information level.
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
std::map< std::string, std::string > headerKeys
std::vector< size_t > axisPixelLengths
@ Input
An input workspace.
@ Output
An output workspace.