20#include <boost/algorithm/string.hpp>
21#include <boost/scoped_array.hpp>
23#include <Poco/BinaryReader.h>
36template <
typename InterpretType>
double toDouble(uint8_t *src) {
38 return static_cast<double>(*
reinterpret_cast<InterpretType *
>(src));
81 : m_headerScaleKey(), m_headerOffsetKey(), m_headerBitDepthKey(), m_headerRotationKey(), m_headerImageKeyKey(),
82 m_headerAxisNameKeys(), m_mapFile(), m_pixelCount(0) {
95 return (descriptor.
extension() ==
".fits" || descriptor.
extension() ==
".fit") ? 80 : 0;
104 std::vector<std::string> exts, exts2;
109 exts.emplace_back(
".fits");
110 exts.emplace_back(
".fit");
112 exts2.emplace_back(
".*");
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).");
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.");
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.");
130 auto posInt = std::make_shared<BoundedValidator<int>>();
133 "Rebunch n*n on both axes, generating pixels with sums of "
134 "blocks of n by n original pixels.");
136 auto posDbl = std::make_shared<BoundedValidator<double>>();
137 posDbl->setLower(std::numeric_limits<double>::epsilon());
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.");
155 std::vector<std::string> paths;
156 boost::split(paths, fName, boost::is_any_of(
","));
159 double noiseThresh =
getProperty(
"FilterNoiseLevel");
163 doLoadFiles(paths, outWSName, loadAsRectImg, binSize, noiseThresh);
167 header.extension =
"";
168 header.filePath = filePath;
175 }
catch (std::exception &e) {
177 throw std::runtime_error(
"Severe problem found while parsing the header of "
179 filePath +
"). This file may not be standard FITS. Error description: " + e.what());
187 if (boost::contains(tmpBitPix,
"-")) {
188 boost::erase_all(tmpBitPix,
"-");
189 header.isFloat =
true;
191 header.isFloat =
false;
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());
202 if (header.bitsPerPixel != 8 && header.bitsPerPixel != 16 && header.bitsPerPixel != 32 && header.bitsPerPixel != 64)
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 "
208 filePath +
"' says that its bit depth is: " +
std::to_string(header.bitsPerPixel));
209 }
catch (std::exception &e) {
211 "' entry (bits per pixel) in the header of this file: " + filePath +
212 ". Error description: " + e.what());
219 if (header.headerKeys.end() != it) {
220 header.imageKey = it->second;
224 }
catch (std::exception &e) {
226 "' entry (type of image: sample, dark, open) in "
227 "the header of this file: " +
228 filePath +
". Error description: " + e.what());
234 for (
int j = 0; j < header.numberOfAxis; ++j) {
235 header.axisPixelLengths.emplace_back(boost::lexical_cast<size_t>(header.headerKeys[
m_headerAxisNameKeys[j]]));
238 << header.axisPixelLengths.back() <<
'\n';
245 }
catch (std::exception &e) {
247 "' entries (dimensions) in the header of this file: " + filePath +
248 ". Error description: " + e.what());
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 +
260 ") as a floating point number (double). Error: " + e.what());
270 }
catch (std::exception & ) {
277 if (0 != modf(doff, &intPart)) {
279 g_log.
warning() <<
"The value given in the FITS header entry for the data "
282 ") has a fractional part, and it will be ignored!\n";
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 +
" = " +
288 ") as an integer number nor a floating point "
289 "number (double). Error: " +
325 <<
": the number of pixels in the first dimension differs "
326 "from the first file loaded ("
333 <<
": the number of pixels in the second dimension differs"
334 "from the first file loaded ("
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 "
370 int binSize,
double noiseThresh) {
371 std::vector<FITSInfo> headers;
372 headers.resize(paths.size());
377 if (headers[0].numberOfAxis > 0)
381 for (
int i = 1; i < headers[0].numberOfAxis; ++i) {
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 " +
391 ") is not a multiple of the bin size.");
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]));
398 size_t bytes = (headers[0].bitsPerPixel / 8) *
m_pixelCount;
399 std::vector<char> buffer;
401 buffer.resize(bytes);
402 }
catch (std::exception &) {
403 throw std::runtime_error(
"Could not allocate enough memory to run when trying to allocate " +
409 size_t fileNumberInGroup = 0;
412 if (
auto &ads = AnalysisDataService::Instance(); ads.doesExist(outWSName)) {
415 std::string latestName = wsGroup->
getNames().back();
419 wsGroup = std::make_shared<WorkspaceGroup>();
420 wsGroup->setTitle(outWSName);
423 const size_t totalWS = headers.size();
426 progress.report(0,
"Loading file(s) into workspace(s)");
432 makeWorkspace(headers[0], fileNumberInGroup, buffer, imageY, imageE, imgWS, loadAsRectImg, binSize, noiseThresh);
433 progress.report(1,
"First file loaded.");
435 wsGroup->addWorkspace(imgWS);
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));
450 }
catch (std::exception &ex) {
451 g_log.
information(
"Cannot load the instrument definition. " + std::string(ex.what()));
457 for (int64_t i = 1; i < static_cast<int64_t>(totalWS); ++i) {
463 imgWS =
makeWorkspace(headers[i], fileNumberInGroup, buffer, imageY, imageE, imgWS, loadAsRectImg, binSize,
466 wsGroup->addWorkspace(imgWS);
500 std::ifstream istr(headerInfo.
filePath.c_str(), std::ios::binary);
501 istr.seekg(0, istr.end);
502 const std::streampos fileSize = istr.tellg();
504 throw std::runtime_error(
"Found a file that is readable but empty (0 bytes size): " + headerInfo.
filePath);
506 istr.seekg(0, istr.beg);
508 Poco::BinaryReader reader(istr);
514 bool endFound =
false;
518 const int entriesPerHDU = 36;
520 for (
int i = 0; i < entriesPerHDU; ++i) {
524 reader.readRaw(80, part);
530 if (boost::iequals(commentKW, part.substr(0, commentKW.size()))) {
537 auto eqPos = part.find(
'=');
539 std::string key = part.substr(0, eqPos);
540 std::string
value = part.substr(eqPos + 1);
544 if (
auto pos =
value.find(
'/'); pos != std::string::npos && pos > 0)
560 throw std::runtime_error(
"Could not find any valid END entry in the headers of this file after "
561 "scanning the file (" +
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.");
599 bool loadAsRectImg,
int binSize,
double noiseThresh) {
604 if (!loadAsRectImg) {
605 size_t finalPixelCount =
m_pixelCount / binSize * binSize;
606 ws = std::dynamic_pointer_cast<Workspace2D>(
609 ws = std::dynamic_pointer_cast<Workspace2D>(
625 cmpp *=
static_cast<double>(binSize);
627 if (loadAsRectImg && 1 == binSize) {
636 ws->setImageYAndE(imageY, imageE, 0, loadAsRectImg, cmpp,
false );
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));
642 doRebin(binSize, imageY, imageE, rebinnedY, rebinnedE);
643 ws->setImageYAndE(rebinnedY, rebinnedE, 0, loadAsRectImg, cmpp,
false );
648 ws->setTitle(std::filesystem::path(fileInfo.
filePath).filename().string());
649 }
catch (std::runtime_error &) {
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);
687 ws->replaceAxis(0, std::move(axw));
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;
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);
700 ws->replaceAxis(1, std::move(axh));
702 unitLbl = std::dynamic_pointer_cast<Kernel::Units::Label>(UnitFactory::Instance().
create(
"Label"));
703 unitLbl->setLabel(
"height",
"cm");
704 ws->getAxis(1)->unit() = unitLbl;
706 ws->setDistribution(
true);
710 ws->setYUnitLabel(
"brightness");
713 for (
const auto &headerKey : fileInfo.
headerKeys) {
714 ws->mutableRun().removeLogData(headerKey.first,
true);
720 ws->mutableRun().removeLogData(
"Rotation",
true);
722 auto rot = boost::lexical_cast<double>(it->second);
729 ws->mutableRun().removeLogData(
"Axis1",
true);
731 ws->mutableRun().removeLogData(
"Axis2",
true);
735 ws->mutableRun().removeLogData(
"ImageKey",
true);
752 std::vector<char> &buffer) {
759 auto *buffer8 =
reinterpret_cast<uint8_t *
>(buffer.data());
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;
768 for (
size_t j = 0; j <
ncols; ++j) {
770 const size_t start = ((i * (bytespp)) *
nrows) + (j * (bytespp));
771 uint8_t
const *
const buffer8Start = buffer8 + start;
775 std::reverse_copy(buffer8Start, buffer8Start + bytespp, byteValue);
779 val = toDouble<uint8_t>(byteValue);
781 val = toDouble<uint16_t>(byteValue);
783 val = toDouble<uint32_t>(byteValue);
785 val = toDouble<uint32_t>(byteValue);
787 val = toDouble<float>(byteValue);
789 val = toDouble<double>(byteValue);
794 eVals[j] = sqrt(val);
811 std::vector<char> &buffer) {
818 auto *buffer8 =
reinterpret_cast<uint8_t *
>(&buffer[0]);
819 std::vector<char> buf(bytespp);
820 char *
tmp = buf.data();
834 std::reverse_copy(buffer8 + start, buffer8 + start + bytespp,
tmp);
837 val =
static_cast<double>(*
reinterpret_cast<uint8_t *
>(
tmp));
839 val =
static_cast<double>(*
reinterpret_cast<uint16_t *
>(
tmp));
841 val =
static_cast<double>(*
reinterpret_cast<uint32_t *
>(
tmp));
843 val =
static_cast<double>(*
reinterpret_cast<uint64_t *
>(
tmp));
847 val =
static_cast<double>(*
reinterpret_cast<float *
>(
tmp));
851 val = *
reinterpret_cast<double *
>(
tmp);
855 imageE[i][j] = sqrt(val);
873 std::string filename = fileInfo.
filePath;
874 std::ifstream file(filename, std::ios::in | std::ios::binary);
876 file.read(&buffer[0], len);
878 throw std::runtime_error(
"Error while reading file: " + filename +
". Tried to read " +
std::to_string(len) +
880 " bytes. The file and/or its headers may be wrong.");
904 for (
size_t j = 1; j < (imageY.size() - 1); ++j) {
905 for (
size_t i = 1; i < (imageY[0].size() - 1); ++i) {
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))
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))
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];
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];
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) {
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];
966 rebinnedY[j][i] = accumY;
967 rebinnedE[j][i] = accumE;
989 if (hdr.
headerKeys.end() != it && boost::contains(it->second,
"Starlight")) {
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";
1037 std::ifstream fStream(headerMapFileName.c_str());
1041 if (fStream.good()) {
1044 std::vector<std::string> lineSplit;
1045 while (getline(fStream, line)) {
1046 boost::split(lineSplit, line, boost::is_any_of(
"="));
1066 throw std::runtime_error(
"Error while trying to read header keys mapping file: " + headerMapFileName);
1069 g_log.
error(
"Cannot load specified map file, using property values "
1070 "and/or defaults.");
1085 for (
auto it =
name.end() - 1; isdigit(*it); --it) {
1086 tmpStr.insert(0, 1, *it);
1088 while (tmpStr.length() > 0 && tmpStr[0] ==
'0') {
1089 tmpStr.erase(tmpStr.begin());
1091 return (tmpStr.length() > 0) ? boost::lexical_cast<size_t>(tmpStr) : 0;
1106 std::ostringstream ss;
1107 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
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.