diff --git a/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/LoadFITS.h b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/LoadFITS.h index f22452fabd9ea914788f816653977a21060090dc..474446b003ca01542ab1f1f8a38aba38bf33f20e 100644 --- a/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/LoadFITS.h +++ b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/LoadFITS.h @@ -15,7 +15,7 @@ using namespace std; struct FITSInfo { vector<string> headerItems; - map<string, string> headerKeys; + std::map<string, string> headerKeys; int bitsPerPixel; int numberOfAxis; int offset; @@ -24,54 +24,44 @@ struct FITSInfo { double tof; double timeBin; double scale; - int imageKey; + std::string imageKey; long int countsInImage; long int numberOfTriggers; - string extension; - string filePath; + std::string extension; + std::string filePath; bool isFloat; }; namespace Mantid { namespace DataHandling { /** - LoadFITS : Load a number of FITS files into a histogram Workspace +LoadFITS: Load one or more of FITS files into a Workspace2D. The FITS +format, normally used for images, is described for example here: +http://www.fileformat.info/format/fits/egff.htm - File format is described here: http://www.fileformat.info/format/fits/egff.htm - This loader doesn't support the full specification, caveats are: - Support for unsigned 8, 16, 32 bit values only - Support only for 2 data axis - No support for format extensions +At the moment this algorithm only supports 2 data axis and the +following data types: unsigned 8, 16, 32 bits per pixel. - Loader is designed to work with multiple files, loading into a single - workspace. - At points there are assumptions that all files in a batch use the same number - of bits per pixel, - and that the number of spectra in each file are the same. +Copyright © 2014,2015 ISIS Rutherford Appleton Laboratory, NScD +Oak Ridge National Laboratory & European Spallation Source - @author John R Hill, RAL - @date 29/08/2014 +This file is part of Mantid. - Copyright © 2014 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge - National Laboratory & European Spallation Source +Mantid is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. - This file is part of Mantid. +Mantid is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. - Mantid is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. - Mantid is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - - File change history is stored at: <https://github.com/mantidproject/mantid> - Code Documentation is available at: <http://doxygen.mantidproject.org> +File change history is stored at: <https://github.com/mantidproject/mantid> +Code Documentation is available at: <http://doxygen.mantidproject.org> */ class DLLExport LoadFITS : public API::IFileLoader<Kernel::FileDescriptor> { @@ -84,7 +74,7 @@ public: /// Summary of algorithms purpose virtual const std::string summary() const { - return "Load data from FITS files."; + return "Load FITS files into workspaces of type Workspace2D."; } /// Algorithm's version for identification overriding a virtual method @@ -105,51 +95,80 @@ private: void init(); /// Execution code void exec(); - /// Parses the header values for the FITS file - bool parseHeader(FITSInfo &headerInfo); - /// Creates a vector of all rotations from a file - std::vector<double> readRotations(std::string rotFilePath, size_t fileCount); + /// Loads files into workspace(s) + void doLoadFiles(const std::vector<std::string> &paths); + + /// Loads the FITS header(s) into a struct + void doLoadHeaders(const std::vector<std::string> &paths, + std::vector<FITSInfo> &headers); + + /// Parses the header values for the FITS file + void parseHeader(FITSInfo &headerInfo); /// Initialises a workspace with IDF and fills it with data DataObjects::Workspace2D_sptr - addWorkspace(const FITSInfo &fileInfo, size_t &newFileNumber, - void *&bufferAny, API::MantidImage &imageY, - API::MantidImage &imageE, double rotation, - const DataObjects::Workspace2D_sptr parent); + makeWorkspace(const FITSInfo &fileInfo, size_t &newFileNumber, + std::vector<char> &buffer, API::MantidImage &imageY, + API::MantidImage &imageE, + const DataObjects::Workspace2D_sptr parent); + + // Reads the data from a single FITS file into a workspace + void readDataToWorkspace2D(DataObjects::Workspace2D_sptr ws, + const FITSInfo &fileInfo, API::MantidImage &imageY, + API::MantidImage &imageE, + std::vector<char> &buffer); + + /// Once loaded, check against standard and limitations of this algorithm + void headerSanityCheck(const FITSInfo &hdr, const FITSInfo &hdrFirst); + + void setupDefaultKeywordNames(); /// Returns the trailing number from a string minus leading 0's (so 25 from /// workspace_00025) - size_t fetchNumber(std::string name); + size_t fetchNumber(const std::string &name); // Adds a number of leading 0's to another number up to the totalDigitCount. - std::string padZeros(size_t number, size_t totalDigitCount); - - // Reads the data from a single FITS file into a workspace - void readFileToWorkspace(DataObjects::Workspace2D_sptr ws, - const FITSInfo &fileInfo, API::MantidImage &imageY, - API::MantidImage &imageE, void *&bufferAny); + std::string padZeros(const size_t number, const size_t totalDigitCount); // Maps the header keys to specified values void mapHeaderKeys(); // Strings used to map header keys - string m_headerScaleKey; - string m_headerOffsetKey; - string m_headerBitDepthKey; - string m_headerRotationKey; - string m_headerImageKeyKey; - string m_mapFile; + std::string m_headerScaleKey; + std::string m_headerOffsetKey; + std::string m_headerBitDepthKey; + std::string m_headerRotationKey; + std::string m_headerImageKeyKey; + std::string m_headerNAxisNameKey; std::vector<std::string> m_headerAxisNameKeys; + std::string m_mapFile; - string m_baseName; + static const std::string m_defaultImgType; + + // names of extension headers + std::string m_sampleRotation; + std::string m_imageType; + + std::string m_baseName; size_t m_spectraCount; API::Progress *m_progress; - // Number of digits which will be appended to a workspace name, i.e. 4 = - // workspace_0001 - static const size_t DIGIT_SIZE_APPEND = 4; + // Number of digits for the fixed width appendix number added to + // workspace names, i.e. 3=> workspace_001; 5 => workspace_00001 + static const size_t DIGIT_SIZE_APPEND = 5; + /// size of a FITS header block (room for 36 entries, of 80 + /// characters each), in bytes. A FITS header always comes in + /// multiples of this block. static const int BASE_HEADER_SIZE = 2880; + + // names for several options that can be given in a "FITS" header + // setup file + static const std::string m_BIT_DEPTH_NAME; + static const std::string m_AXIS_NAMES_NAME; + static const std::string m_ROTATION_NAME; + static const std::string m_IMAGE_KEY_NAME; + static const std::string m_HEADER_MAP_NAME; }; } // namespace DataHandling diff --git a/Code/Mantid/Framework/DataHandling/src/LoadFITS.cpp b/Code/Mantid/Framework/DataHandling/src/LoadFITS.cpp index c147984dc309b96d04e74125cca22c1dd1bf87b3..f8c6571370091b98e9640f6f84fe2905b468c9b8 100644 --- a/Code/Mantid/Framework/DataHandling/src/LoadFITS.cpp +++ b/Code/Mantid/Framework/DataHandling/src/LoadFITS.cpp @@ -1,12 +1,14 @@ -#include "MantidDataHandling/LoadFITS.h" #include "MantidAPI/MultipleFileProperty.h" #include "MantidAPI/FileProperty.h" #include "MantidAPI/RegisterFileLoader.h" +#include "MantidDataHandling/LoadFITS.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidKernel/UnitFactory.h" + #include <boost/algorithm/string.hpp> #include <Poco/BinaryReader.h> -#include <fstream> +#include <Poco/FileStream.h> + using namespace Mantid::DataHandling; using namespace Mantid::DataObjects; @@ -14,40 +16,30 @@ using namespace Mantid::API; using namespace Mantid::Kernel; using namespace std; using namespace boost; -using Poco::BinaryReader; -namespace { -static const std::string BIT_DEPTH_NAME = "BitDepthName"; -static const std::string ROTATION_NAME = "RotationName"; -static const std::string AXIS_NAMES_NAME = "AxisNames"; -static const std::string IMAGE_KEY_NAME = "ImageKeyName"; -static const std::string HEADER_MAP_NAME = "HeaderMapFile"; - -/** -* Used with find_if to check a string isn't a fits file (by checking extension) -* @param s string to check for extension -* @returns bool Value indicating if the string ends with .fits or not -*/ -bool IsNotFits(std::string s) { - std::string tmp = s; - to_lower(tmp); - return !ends_with(tmp, ".fits"); -} -} +namespace {} namespace Mantid { namespace DataHandling { // Register the algorithm into the AlgorithmFactory DECLARE_FILELOADER_ALGORITHM(LoadFITS) +const std::string LoadFITS::m_BIT_DEPTH_NAME = "BitDepthName"; +const std::string LoadFITS::m_ROTATION_NAME = "RotationName"; +const std::string LoadFITS::m_AXIS_NAMES_NAME = "AxisNames"; +const std::string LoadFITS::m_IMAGE_KEY_NAME = "ImageKeyName"; +const std::string LoadFITS::m_HEADER_MAP_NAME = "HeaderMapFile"; + +const std::string LoadFITS::m_defaultImgType = "SAMPLE"; + /** * Constructor. Just initialize everything to prevent issues. */ -LoadFITS::LoadFITS(): m_headerScaleKey(), m_headerOffsetKey(), - m_headerBitDepthKey(), m_headerRotationKey(), - m_headerImageKeyKey(), m_mapFile(), - m_headerAxisNameKeys(), m_baseName(), - m_spectraCount(0), m_progress(NULL) { +LoadFITS::LoadFITS() + : m_headerScaleKey(), m_headerOffsetKey(), m_headerBitDepthKey(), + m_headerRotationKey(), m_headerImageKeyKey(), m_headerAxisNameKeys(), + m_mapFile(), m_baseName(), m_spectraCount(0), m_progress(NULL) { + setupDefaultKeywordNames(); } /** @@ -64,6 +56,31 @@ int LoadFITS::confidence(Kernel::FileDescriptor &descriptor) const { : 0; } +/** + * Sets several keyword names with default (and standard) values. You + * don't want to change these unless you want to break compatibility + * with the FITS standard. + */ +void LoadFITS::setupDefaultKeywordNames() { + // Inits all the absolutely necessary keywords + // standard headers (If SIMPLE=T) + m_headerScaleKey = "BSCALE"; + m_headerOffsetKey = "BZERO"; + m_headerBitDepthKey = "BITPIX"; + m_headerImageKeyKey = "IMAGE_TYPE"; // This is a "HIERARCH Image/Type= " + m_headerRotationKey = "ROTATION"; + + m_headerNAxisNameKey = "NAXIS"; + m_headerAxisNameKeys.push_back("NAXIS1"); + m_headerAxisNameKeys.push_back("NAXIS2"); + + m_mapFile = ""; + + // extensions + m_sampleRotation = "HIERARCH Sample/Tomo_Angle"; + m_imageType = "HIERARCH Image/Type"; +} + /** * Initialise the algorithm. Declare properties which can be set before execution * (input) or @@ -82,308 +99,325 @@ void LoadFITS::init() { exts2.push_back(".*"); declareProperty(new MultipleFileProperty("Filename", exts), - "The input filename of the stored data"); + "The name of the input file (you can give " + "multiple file names separated by commas)."); declareProperty(new API::WorkspaceProperty<API::Workspace>( "OutputWorkspace", "", Kernel::Direction::Output)); declareProperty( - new PropertyWithValue<int>("ImageKey", 0, Kernel::Direction::Input), - "Image type to set these files as. 0=data image, 1=flat field, 2=open " - "field, -1=use the value from FITS header. At present, if this is not " - "specified and an IMAGEKEY entry is not found in the FITS header, the " - "loader will show an error message and stop."); - - declareProperty(new PropertyWithValue<string>(BIT_DEPTH_NAME, "BITPIX", - Kernel::Direction::Input), - "Name for the pixel bit depth header key."); - declareProperty(new PropertyWithValue<string>(ROTATION_NAME, "ROTATION", - Kernel::Direction::Input), - "Name for the rotation header key."); - declareProperty( - new PropertyWithValue<string>(AXIS_NAMES_NAME, "NAXIS1,NAXIS2", - Kernel::Direction::Input), - "Names for the axis header keys, comma separated string of all axis."); - declareProperty(new PropertyWithValue<string>(IMAGE_KEY_NAME, "IMAGEKEY", - Kernel::Direction::Input), - "Names for the image type, key."); - - declareProperty( - new FileProperty(HEADER_MAP_NAME, "", FileProperty::OptionalDirectory, "", - Kernel::Direction::Input), - "A file mapping header keys to the ones used by ISIS [line separated " - "values in the format KEY=VALUE, e.g. BitDepthName=BITPIX "); + new FileProperty(m_HEADER_MAP_NAME, "", FileProperty::OptionalDirectory, + "", Kernel::Direction::Input), + "A file mapping header key names to non-standard names [line separated " + "values in the format KEY=VALUE, e.g. BitDepthName=BITPIX] - do not use " + "this if you want to keep compatibility with standard FITS files."); } /** -* Execute the algorithm. -*/ + * Execute the algorithm. + */ void LoadFITS::exec() { - // Init header info - setup some defaults just in case - m_headerScaleKey = "BSCALE"; - m_headerOffsetKey = "BZERO"; - m_headerBitDepthKey = "BITPIX"; - m_headerImageKeyKey = "IMAGEKEY"; - m_headerRotationKey = "ROTATION"; - m_mapFile = ""; - m_headerAxisNameKeys.push_back("NAXIS1"); - m_headerAxisNameKeys.push_back("NAXIS2"); - + // for non-standard headers, by default won't do anything mapHeaderKeys(); - // Create FITS file information for each file selected - std::vector<std::string> paths; string fName = getPropertyValue("Filename"); - boost::split(paths, fName, boost::is_any_of(",")); - // If paths contains a non fits file, assume (for now) that it contains - // information about the rotations - std::string rotFilePath = ""; - std::vector<std::string>::iterator it = - std::find_if(paths.begin(), paths.end(), IsNotFits); - if (it != paths.end()) { - rotFilePath = *it; - paths.erase(it); - } - vector<FITSInfo> allHeaderInfo; - allHeaderInfo.resize(paths.size()); - - // Check each header is valid for this loader, - standard (no extension to - // FITS), and has two axis - bool headerValid = true; + std::vector<std::string> paths; + boost::split(paths, fName, boost::is_any_of(",")); + doLoadFiles(paths); +} - for (size_t i = 0; i < paths.size(); ++i) { - allHeaderInfo[i].extension = ""; - allHeaderInfo[i].filePath = paths[i]; - // Get various pieces of information from the file header which are used to - // create the workspace - if (parseHeader(allHeaderInfo[i])) { - // Get and convert specific standard header values which will help when - // parsing the data - // BITPIX, NAXIS, NAXISi (where i = 1..NAXIS, e.g. NAXIS2 for two axis), - // TOF, TIMEBIN, N_COUNTS, N_TRIGS - try { - string tmpBitPix = allHeaderInfo[i].headerKeys[m_headerBitDepthKey]; - if (boost::contains(tmpBitPix, "-")) { - boost::erase_all(tmpBitPix, "-"); - allHeaderInfo[i].isFloat = true; - } else { - allHeaderInfo[i].isFloat = false; - } +/** + * Create FITS file information for each file selected. Loads headers + * and data from the files and fills the output workspace(s). + * + * @param paths File names as given in the algorithm input property + */ +void LoadFITS::doLoadFiles(const std::vector<std::string> &paths) { + std::vector<FITSInfo> headers; - // Add the image key, use the property if it's not -1, otherwise use the - // header value - allHeaderInfo[i].imageKey = - boost::lexical_cast<int>(getPropertyValue("ImageKey")); - if (allHeaderInfo[i].imageKey == -1) { - allHeaderInfo[i].imageKey = boost::lexical_cast<int>( - allHeaderInfo[i].headerKeys[m_headerImageKeyKey]); - } + doLoadHeaders(paths, headers); - allHeaderInfo[i].bitsPerPixel = lexical_cast<int>(tmpBitPix); - allHeaderInfo[i].numberOfAxis = - static_cast<int>(m_headerAxisNameKeys.size()); + // No extension is set -> it's the standard format which we can parse. + if (headers[0].numberOfAxis > 0) + m_spectraCount += headers[0].axisPixelLengths[0]; - for (int j = 0; - allHeaderInfo.size() > i && j < allHeaderInfo[i].numberOfAxis; - ++j) { - allHeaderInfo[i].axisPixelLengths.push_back(lexical_cast<size_t>( - allHeaderInfo[i].headerKeys[m_headerAxisNameKeys[j]])); - } + // Presumably 2 axis, but futureproofing. + for (int i = 1; i < headers[0].numberOfAxis; ++i) { + m_spectraCount *= headers[0].axisPixelLengths[i]; + } - // m_allHeaderInfo[i].tof = - // lexical_cast<double>(m_allHeaderInfo[i].headerKeys["TOF"]); - // m_allHeaderInfo[i].timeBin = - // lexical_cast<double>(m_allHeaderInfo[i].headerKeys["TIMEBIN"]); - // m_allHeaderInfo[i].countsInImage = lexical_cast<long - // int>(m_allHeaderInfo[i].headerKeys["N_COUNTS"]); - // m_allHeaderInfo[i].numberOfTriggers = lexical_cast<long - // int>(m_allHeaderInfo[i].headerKeys["N_TRIGS"]); - allHeaderInfo[i].extension = - allHeaderInfo[i].headerKeys["XTENSION"]; // Various extensions are - // available to the FITS - // format, and must be - // parsed differently if - // this is present. Loader - // doesn't support this. - - } catch (std::exception &) { - // todo write error and fail this load with invalid data in file. - throw std::runtime_error("Unable to locate one or more valid BITPIX, " - "NAXIS or IMAGEKEY values in the FITS file " - "header."); - } + MantidImage imageY(headers[0].axisPixelLengths[0], + vector<double>(headers[0].axisPixelLengths[1])); + MantidImage imageE(headers[0].axisPixelLengths[0], + vector<double>(headers[0].axisPixelLengths[1])); - allHeaderInfo[i].scale = - (allHeaderInfo[i].headerKeys[m_headerScaleKey] == "") - ? 1 - : lexical_cast<double>( - allHeaderInfo[i].headerKeys[m_headerScaleKey]); - allHeaderInfo[i].offset = - (allHeaderInfo[i].headerKeys[m_headerOffsetKey] == "") - ? 0 - : lexical_cast<int>( - allHeaderInfo[i].headerKeys[m_headerOffsetKey]); - - if (allHeaderInfo[i].extension != "") - headerValid = false; - if (allHeaderInfo[i].numberOfAxis != 2) - headerValid = false; - - // Test current item has same axis values as first item. - if (allHeaderInfo[0].axisPixelLengths[0] != - allHeaderInfo[i].axisPixelLengths[0]) - headerValid = false; - if (allHeaderInfo[0].axisPixelLengths[1] != - allHeaderInfo[i].axisPixelLengths[1]) - headerValid = false; - } else { - // Unable to parse the header, throw. - throw std::runtime_error("Unable to open the FITS file."); - } + size_t bytes = (headers[0].bitsPerPixel / 8) * m_spectraCount; + std::vector<char> buffer; + try { + buffer.resize(bytes); + } catch (std::exception &) { + throw std::runtime_error( + "Could not allocate enough memory to run when trying to allocate " + + boost::lexical_cast<std::string>(bytes) + " bytes."); } - // Check that the files use bit depths of either 8, 16 or 32 - if (allHeaderInfo[0].bitsPerPixel != 8 && - allHeaderInfo[0].bitsPerPixel != 16 && - allHeaderInfo[0].bitsPerPixel != 32 && - allHeaderInfo[0].bitsPerPixel != 64) - throw std::runtime_error( - "FITS loader only supports 8, 16, 32 or 64 bits per pixel."); + // Create a group for these new workspaces, if the group already exists, add + // to it. + string groupName = getPropertyValue("OutputWorkspace"); - // Check the format is correct and create the Workspace - if (headerValid) { - // No extension is set, therefore it's the standard format which we can - // parse. + // This forms the name of the group + m_baseName = getPropertyValue("OutputWorkspace") + "_"; - if (allHeaderInfo[0].numberOfAxis > 0) - m_spectraCount += allHeaderInfo[0].axisPixelLengths[0]; + size_t fileNumberInGroup = 0; + WorkspaceGroup_sptr wsGroup; - // Presumably 2 axis, but futureproofing. - for (int i = 1; i < allHeaderInfo[0].numberOfAxis; ++i) { - m_spectraCount *= allHeaderInfo[0].axisPixelLengths[i]; - } + if (!AnalysisDataService::Instance().doesExist(groupName)) { + wsGroup = WorkspaceGroup_sptr(new WorkspaceGroup); + wsGroup->setTitle(groupName); + } else { + // Get the name of the latest file in group to start numbering from + if (AnalysisDataService::Instance().doesExist(groupName)) + wsGroup = + AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(groupName); + + std::string latestName = wsGroup->getNames().back(); + // Set next file number + fileNumberInGroup = fetchNumber(latestName) + 1; + } - MantidImage imageY(allHeaderInfo[0].axisPixelLengths[0], - vector<double>(allHeaderInfo[0].axisPixelLengths[1])); - MantidImage imageE(allHeaderInfo[0].axisPixelLengths[0], - vector<double>(allHeaderInfo[0].axisPixelLengths[1])); - ; + // Create a progress reporting object + m_progress = new Progress(this, 0, 1, headers.size() + 1); - void *bufferAny = NULL; - bufferAny = malloc((allHeaderInfo[0].bitsPerPixel / 8) * m_spectraCount); - if (bufferAny == NULL) { - throw std::runtime_error( - "FITS loader couldn't allocate enough memory to run."); - } + // Create first workspace (with instrument definition). This is also used as + // a template for creating others + Workspace2D_sptr latestWS; + latestWS = makeWorkspace(headers[0], fileNumberInGroup, buffer, imageY, + imageE, latestWS); - // Set info in WS log to hold rotational information - vector<double> rotations; - if (rotFilePath != "") - rotations = readRotations(rotFilePath, paths.size()); + map<size_t, Workspace2D_sptr> wsOrdered; + wsOrdered[0] = latestWS; + try { + IAlgorithm_sptr loadInst = createChildAlgorithm("LoadInstrument"); + std::string directoryName = + Kernel::ConfigService::Instance().getInstrumentDirectory(); + directoryName = directoryName + "/IMAT_Definition.xml"; + loadInst->setPropertyValue("Filename", directoryName); + loadInst->setProperty<MatrixWorkspace_sptr>( + "Workspace", dynamic_pointer_cast<MatrixWorkspace>(latestWS)); + loadInst->execute(); + } catch (std::exception &ex) { + g_log.information("Cannot load the instrument definition. " + + string(ex.what())); + } - // Create a group for these new workspaces, if the group already exists, add - // to it. - string groupName = getPropertyValue("OutputWorkspace"); + PARALLEL_FOR_NO_WSP_CHECK() + for (int64_t i = 1; i < static_cast<int64_t>(headers.size()); ++i) { + latestWS = makeWorkspace(headers[i], fileNumberInGroup, buffer, imageY, + imageE, latestWS); + wsOrdered[i] = latestWS; + } - // This forms the name of the group - m_baseName = getPropertyValue("OutputWorkspace") + "_"; + // Add to group - done here to maintain sequence + for (auto it = wsOrdered.begin(); it != wsOrdered.end(); ++it) { + wsGroup->addWorkspace(it->second); + } - size_t fileNumberInGroup = 0; - WorkspaceGroup_sptr wsGroup; + setProperty("OutputWorkspace", wsGroup); +} - if (!AnalysisDataService::Instance().doesExist(groupName)) { - wsGroup = WorkspaceGroup_sptr(new WorkspaceGroup); - wsGroup->setTitle(groupName); - } else { - // Get the name of the latest file in group to start numbering from - if (AnalysisDataService::Instance().doesExist(groupName)) - wsGroup = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( - groupName); - - std::string latestName = wsGroup->getNames().back(); - // Set next file number - fileNumberInGroup = fetchNumber(latestName) + 1; - } +/** + * Load header(s) from FITS file(s) into FITSInfo header + * struct(s). This is usually the first step when loading FITS files + * into workspaces or anything else. In the simplest case, paths has + * only one string and only one header struct is added in headers. + * + * @param paths File name(s) + * @param headers Vector where to store the header struct(s) + * + * @throws std::runtime_error if issues are found in the headers + */ +void LoadFITS::doLoadHeaders(const std::vector<std::string> &paths, + std::vector<FITSInfo> &headers) { + headers.resize(paths.size()); - // Create a progress reporting object - m_progress = new Progress(this, 0, 1, allHeaderInfo.size() + 1); + for (size_t i = 0; i < paths.size(); ++i) { + headers[i].extension = ""; + headers[i].filePath = paths[i]; + // Get various pieces of information from the file header which are used to + // create the workspace + try { + parseHeader(headers[i]); + } catch (std::exception &e) { + // Unable to parse the header, throw. + throw std::runtime_error( + "Severe problem found while parsing the header of " + "this FITS file (" + + paths[i] + + "). This file may not be standard FITS. Error description: " + + e.what()); + } - // Create First workspace with instrument definition, also used as a - // template for creating others - Workspace2D_sptr latestWS; - double rot = (rotations.size() > 0) ? rotations[0] : -1; - map<size_t, Workspace2D_sptr> wsOrdered; + // Get and convert specific standard header values which will are + // needed to know how to load the data: BITPIX, NAXIS, NAXISi (where i = + // 1..NAXIS, e.g. NAXIS2 for two axis). + try { + string tmpBitPix = headers[i].headerKeys[m_headerBitDepthKey]; + if (boost::contains(tmpBitPix, "-")) { + boost::erase_all(tmpBitPix, "-"); + headers[i].isFloat = true; + } else { + headers[i].isFloat = false; + } - latestWS = addWorkspace(allHeaderInfo[0], fileNumberInGroup, bufferAny, - imageY, imageE, rot, latestWS); - wsOrdered[0] = latestWS; + headers[i].bitsPerPixel = lexical_cast<int>(tmpBitPix); + // Check that the files use bit depths of either 8, 16 or 32 + if (headers[i].bitsPerPixel != 8 && headers[i].bitsPerPixel != 16 && + headers[i].bitsPerPixel != 32 && headers[i].bitsPerPixel != 64) + throw std::runtime_error( + "This algorithm only supports 8, 16, 32 or 64 " + "bits per pixel. The header of file '" + + paths[i] + "' says that its bit depth is: " + + boost::lexical_cast<std::string>(headers[i].bitsPerPixel)); + } catch (std::exception &e) { + throw std::runtime_error( + "Failed to process the '" + m_headerBitDepthKey + + "' entry (bits per pixel) in the header of this file: " + paths[i] + + ". Error description: " + e.what()); + } try { - IAlgorithm_sptr loadInst = createChildAlgorithm("LoadInstrument"); - std::string directoryName = - Kernel::ConfigService::Instance().getInstrumentDirectory(); - directoryName = directoryName + "/IMAT_Definition.xml"; - loadInst->setPropertyValue("Filename", directoryName); - loadInst->setProperty<MatrixWorkspace_sptr>( - "Workspace", dynamic_pointer_cast<MatrixWorkspace>(latestWS)); - loadInst->execute(); - } catch (std::exception &ex) { - g_log.information("Cannot load the instrument definition. " + - string(ex.what())); + // Add the image key, use the value in the FITS header if found, + // otherwise default (to SAMPLE). + auto it = headers[i].headerKeys.find(m_headerImageKeyKey); + if (headers[i].headerKeys.end() != it) { + headers[i].imageKey = it->second; + } else { + headers[i].imageKey = m_defaultImgType; + } + } catch (std::exception &e) { + throw std::runtime_error("Failed to process the '" + m_headerImageKeyKey + + "' entry (type of image: sample, dark, open) in " + "the header of this file: " + + paths[i] + ". Error description: " + e.what()); } - PARALLEL_FOR_NO_WSP_CHECK() - for (int64_t i = 1; i < static_cast<int64_t>(allHeaderInfo.size()); ++i) { - double rot = - (static_cast<int64_t>(rotations.size()) > i) ? rotations[i] : -1; - latestWS = addWorkspace(allHeaderInfo[i], fileNumberInGroup, bufferAny, - imageY, imageE, rot, latestWS); - wsOrdered[i] = latestWS; - } + try { + headers[i].numberOfAxis = static_cast<int>(m_headerAxisNameKeys.size()); + + for (int j = 0; headers.size() > i && j < headers[i].numberOfAxis; ++j) { + headers[i].axisPixelLengths.push_back(lexical_cast<size_t>( + headers[i].headerKeys[m_headerAxisNameKeys[j]])); + } - // Add to group - Done here to maintain order - for (auto it = wsOrdered.begin(); it != wsOrdered.end(); ++it) { - wsGroup->addWorkspace(it->second); + // Various extensions to the FITS format are used elsewhere, and + // must be parsed differently if used. This loader Loader + // doesn't support this. + headers[i].extension = headers[i].headerKeys["XTENSION"]; + } catch (std::exception &e) { + throw std::runtime_error( + "Failed to process the '" + m_headerNAxisNameKey + + "' entries (dimensions) in the header of this file: " + paths[i] + + ". Error description: " + e.what()); } - free(bufferAny); + headers[i].scale = + (headers[i].headerKeys[m_headerScaleKey] == "") + ? 1 + : lexical_cast<double>(headers[i].headerKeys[m_headerScaleKey]); + headers[i].offset = + (headers[i].headerKeys[m_headerOffsetKey] == "") + ? 0 + : lexical_cast<int>(headers[i].headerKeys[m_headerOffsetKey]); + + // Check each header is valid/supported: standard (no extension to + // FITS), and has two axis + headerSanityCheck(headers[i], headers[0]); + } +} - setProperty("OutputWorkspace", wsGroup); - } else { - // Invalid files, record error - throw std::runtime_error("Loader currently doesn't support FITS files with " - "non-standard extensions, greater than two axis " - "of data, or has detected that all the files are " - "not similar."); +/** + * Read a single files header and populate an object with the information. + * + * @param headerInfo A FITSInfo file object to parse header + * information into. This object must have its field filePath set to + * the input file + * + * @throws various std::runtime_error etc. on read failure +*/ +void LoadFITS::parseHeader(FITSInfo &headerInfo) { + headerInfo.headerSizeMultiplier = 0; + ifstream istr(headerInfo.filePath.c_str(), ios::binary); + Poco::BinaryReader reader(istr); + + // Iterate 80 bytes at a time until header is parsed | 2880 bytes is the + // fixed header length of FITS + // 2880/80 = 36 iterations required + bool endFound = false; + while (!endFound) { + headerInfo.headerSizeMultiplier++; + for (int i = 0; i < 36; ++i) { + // Keep vect of each header item, including comments, and also keep a + // map of individual keys. + string part; + reader.readRaw(80, part); + headerInfo.headerItems.push_back(part); + + // Add key/values - these are separated by the = symbol. + // If it doesn't have an = it's a comment to ignore. All keys should be + // unique + auto eqPos = part.find('='); + if (eqPos > 0) { + string key = part.substr(0, eqPos); + string value = part.substr(eqPos + 1); + + // Comments are added after the value separated by a / symbol. Remove. + auto slashPos = value.find('/'); + if (slashPos > 0) + value = value.substr(0, slashPos); + + boost::trim(key); + boost::trim(value); + + if (key == "END") + endFound = true; + + if (key != "") + headerInfo.headerKeys[key] = value; + } + } } + + istr.close(); } /** - * Initialises a workspace with IDF and fills it with data + * Creates and initialises a workspace with instrument definition and fills it + * with data + * * @param fileInfo information for the current file * @param newFileNumber number for the new file when added into ws group - * @param bufferAny Presized buffer to contain data values + * @param buffer pre-allocated buffer to contain data values * @param imageY Object to set the Y data values in * @param imageE Object to set the E data values in - * @param rotation Value for the rotation of the current file * @param parent A workspace which can be used to copy initialisation * information from (size/instrument def etc) - * @returns A pointer to the workspace created + * + * @returns A newly created Workspace2D, as a shared pointer */ -Workspace2D_sptr LoadFITS::addWorkspace(const FITSInfo &fileInfo, - size_t &newFileNumber, void *&bufferAny, - MantidImage &imageY, - MantidImage &imageE, double rotation, - const Workspace2D_sptr parent) { +Workspace2D_sptr +LoadFITS::makeWorkspace(const FITSInfo &fileInfo, size_t &newFileNumber, + std::vector<char> &buffer, MantidImage &imageY, + MantidImage &imageE, const Workspace2D_sptr parent) { // Create ws Workspace2D_sptr ws; - if (!parent) + if (!parent) { ws = dynamic_pointer_cast<Workspace2D>(WorkspaceFactory::Instance().create( "Workspace2D", m_spectraCount, 2, 1)); - else + } else { ws = dynamic_pointer_cast<Workspace2D>( WorkspaceFactory::Instance().create(parent)); + } string currNumberS = padZeros(newFileNumber, DIGIT_SIZE_APPEND); ++newFileNumber; @@ -393,21 +427,26 @@ Workspace2D_sptr LoadFITS::addWorkspace(const FITSInfo &fileInfo, ws->setTitle(baseName); // set data - readFileToWorkspace(ws, fileInfo, imageY, imageE, bufferAny); + readDataToWorkspace2D(ws, fileInfo, imageY, imageE, buffer); // Add all header info to log. for (auto it = fileInfo.headerKeys.begin(); it != fileInfo.headerKeys.end(); ++it) { - ws->mutableRun().removeLogData("_" + it->first, true); + ws->mutableRun().removeLogData(it->first, true); ws->mutableRun().addLogData( - new PropertyWithValue<string>("_" + it->first, it->second)); + new PropertyWithValue<string>(it->first, it->second)); } // Add rotational data to log. Clear first from copied WS + auto it = fileInfo.headerKeys.find(m_sampleRotation); ws->mutableRun().removeLogData("Rotation", true); - if (rotation != -1) - ws->mutableRun().addLogData( - new PropertyWithValue<double>("Rotation", rotation)); + if (fileInfo.headerKeys.end() != it) { + double rot = boost::lexical_cast<double>(it->second); + if (rot >= 0) { + ws->mutableRun().addLogData( + new PropertyWithValue<double>("Rotation", rot)); + } + } // Add axis information to log. Clear first from copied WS ws->mutableRun().removeLogData("Axis1", true); @@ -419,8 +458,8 @@ Workspace2D_sptr LoadFITS::addWorkspace(const FITSInfo &fileInfo, // Add image key data to log. Clear first from copied WS ws->mutableRun().removeLogData("ImageKey", true); - ws->mutableRun().addLogData(new PropertyWithValue<int>( - "ImageKey", static_cast<int>(fileInfo.imageKey))); + ws->mutableRun().addLogData( + new PropertyWithValue<std::string>("ImageKey", fileInfo.imageKey)); m_progress->report(); @@ -428,84 +467,58 @@ Workspace2D_sptr LoadFITS::addWorkspace(const FITSInfo &fileInfo, } /** - * Returns the trailing number from a string minus leading 0's (so 25 from - * workspace_00025)the confidence with with this algorithm can load the file - * @param name string with a numerical suffix - * @returns A numerical representation of the string minus leading characters - * and leading 0's - */ -size_t LoadFITS::fetchNumber(std::string name) { - string tmpStr = ""; - for (auto it = name.end() - 1; isdigit(*it); --it) { - tmpStr.insert(0, 1, *it); - } - while (tmpStr.length() > 0 && tmpStr[0] == '0') { - tmpStr.erase(tmpStr.begin()); - } - return (tmpStr.length() > 0) ? lexical_cast<size_t>(tmpStr) : 0; -} - -// Adds 0's to the front of a number to create a string of size totalDigitCount -// including number -std::string LoadFITS::padZeros(size_t number, size_t totalDigitCount) { - std::ostringstream ss; - ss << std::setw(static_cast<int>(totalDigitCount)) << std::setfill('0') - << static_cast<int>(number); - - return ss.str(); -} - -/** - * Reads the data from a single FITS file into a workspace + * Reads the data (matrix) from a single FITS file into a workspace + * * @param ws Workspace to populate with the data * @param fileInfo information pertaining to the FITS file to load * @param imageY Object to set the Y data values in * @param imageE Object to set the E data values in - * @param bufferAny Presized buffer to contain data values + * @param buffer pre-allocated buffer to contain data values + * + * @throws std::runtime_error if there are file input issues */ -void LoadFITS::readFileToWorkspace(Workspace2D_sptr ws, - const FITSInfo &fileInfo, - MantidImage &imageY, MantidImage &imageE, - void *&bufferAny) { - uint8_t *buffer8 = NULL; - - // create pointer of correct data type to void pointer of the buffer: - buffer8 = static_cast<uint8_t *>(bufferAny); - - // Read Data - bool fileErr = false; - FILE *currFile = fopen(fileInfo.filePath.c_str(), "rb"); - if (currFile == NULL) - fileErr = true; - - size_t result = 0; - if (!fileErr) { - if (fseek(currFile, BASE_HEADER_SIZE * fileInfo.headerSizeMultiplier, - SEEK_CUR) == 0) - result = fread(buffer8, 1, m_spectraCount * (fileInfo.bitsPerPixel / 8), - currFile); +void LoadFITS::readDataToWorkspace2D(Workspace2D_sptr ws, + const FITSInfo &fileInfo, + MantidImage &imageY, MantidImage &imageE, + std::vector<char> &buffer) { + std::string filename = fileInfo.filePath; + Poco::FileStream file(filename, std::ios::in); + + size_t bytespp = (fileInfo.bitsPerPixel / 8); + size_t len = m_spectraCount * bytespp; + file.seekg(BASE_HEADER_SIZE * fileInfo.headerSizeMultiplier); + file.read(&buffer[0], len); + if (!file) { + throw std::runtime_error( + "Error while reading file: " + filename + ". Tried to read " + + boost::lexical_cast<std::string>(len) + " bytes but got " + + boost::lexical_cast<std::string>(file.gcount()) + + " bytes. The file and/or its headers may be wrong."); } + // all is loaded + file.close(); - if (result != m_spectraCount * (fileInfo.bitsPerPixel / 8)) - fileErr = true; - - if (fileErr) - throw std::runtime_error("Error reading file; possibly invalid data."); - - std::vector<char> buf(fileInfo.bitsPerPixel / 8); - char* tmp = &buf.front(); + // create pointer of correct data type to void pointer of the buffer: + uint8_t *buffer8 = reinterpret_cast<uint8_t *>(&buffer[0]); + std::vector<char> buf(bytespp); + char *tmp = &buf.front(); + size_t start = 0; for (size_t i = 0; i < fileInfo.axisPixelLengths[0]; ++i) { for (size_t j = 0; j < fileInfo.axisPixelLengths[1]; ++j) { - double val = 0; - size_t start = - ((i * (fileInfo.bitsPerPixel / 8)) * fileInfo.axisPixelLengths[1]) + - (j * (fileInfo.bitsPerPixel / 8)); + // If you wanted to PARALLEL_...ize these loops (which doesn't + // seem to provide any speed up, you cannot use the + // start+=bytespp at the end of this loop. You'd need something + // like this: + // + // size_t start = + // ((i * (bytespp)) * fileInfo.axisPixelLengths[1]) + + // (j * (bytespp)); // Reverse byte order of current value - std::reverse_copy(buffer8 + start, - buffer8 + start + (fileInfo.bitsPerPixel / 8), tmp); + std::reverse_copy(buffer8 + start, buffer8 + start + bytespp, tmp); + double val = 0; if (fileInfo.bitsPerPixel == 8) val = static_cast<double>(*reinterpret_cast<uint8_t *>(tmp)); if (fileInfo.bitsPerPixel == 16) @@ -529,187 +542,160 @@ void LoadFITS::readFileToWorkspace(Workspace2D_sptr ws, imageY[i][j] = val; imageE[i][j] = sqrt(val); + + start += bytespp; } } // Set in WS ws->setImageYAndE(imageY, imageE, 0, false); - - // Clear memory associated with the file load - fclose(currFile); } /** -* Read a single files header and populate an object with the information -* @param headerInfo A FITSInfo file object to parse header information into -* @returns A bool specifying succes of the operation -*/ -bool LoadFITS::parseHeader(FITSInfo &headerInfo) { - bool ranSuccessfully = true; - headerInfo.headerSizeMultiplier = 0; - try { - ifstream istr(headerInfo.filePath.c_str(), ios::binary); - Poco::BinaryReader reader(istr); - - // Iterate 80 bytes at a time until header is parsed | 2880 bytes is the - // fixed header length of FITS - // 2880/80 = 36 iterations required - bool endFound = false; - while (!endFound) { - headerInfo.headerSizeMultiplier++; - for (int i = 0; i < 36; ++i) { - // Keep vect of each header item, including comments, and also keep a - // map of individual keys. - string part; - reader.readRaw(80, part); - headerInfo.headerItems.push_back(part); - - // Add key/values - these are separated by the = symbol. - // If it doesn't have an = it's a comment to ignore. All keys should be - // unique - auto eqPos = part.find('='); - if (eqPos > 0) { - string key = part.substr(0, eqPos); - string value = part.substr(eqPos + 1); - - // Comments are added after the value separated by a / symbol. Remove. - auto slashPos = value.find('/'); - if (slashPos > 0) - value = value.substr(0, slashPos); - - boost::trim(key); - boost::trim(value); - - if (key == "END") - endFound = true; - - if (key != "") - headerInfo.headerKeys[key] = value; - } - } - } + * Checks that a FITS header (once loaded) is valid/supported: + * standard (no extension to FITS), and has two axis with the expected + * dimensions. + * + * @param hdr FITS header struct loaded from a file - to check + * + * @param hdr FITS header struct loaded from a (first) reference file - to + * compare against + * + * @throws std::exception if there's any issue or unsupported entry in the + * header + */ +void LoadFITS::headerSanityCheck(const FITSInfo &hdr, + const FITSInfo &hdrFirst) { + bool valid = true; + if (hdr.extension != "") { + valid = false; + g_log.error() << "File " << hdr.filePath + << ": extensions found in the header." << std::endl; + } + if (hdr.numberOfAxis != 2) { + valid = false; + g_log.error() << "File " << hdr.filePath + << ": the number of axes is not 2 but: " << hdr.numberOfAxis + << std::endl; + } - istr.close(); - } catch (...) { - // Unable to read the file - ranSuccessfully = false; + // Test current item has same axis values as first item. + if (hdr.axisPixelLengths[0] != hdrFirst.axisPixelLengths[0]) { + valid = false; + g_log.error() << "File " << hdr.filePath + << ": the number of pixels in the first dimension differs " + "from the first file loaded (" << hdrFirst.filePath + << "): " << hdr.axisPixelLengths[0] + << " != " << hdrFirst.axisPixelLengths[0] << std::endl; + } + if (hdr.axisPixelLengths[1] != hdrFirst.axisPixelLengths[1]) { + valid = false; + g_log.error() << "File " << hdr.filePath + << ": the number of pixels in the second dimension differs" + "from the first file loaded (" << hdrFirst.filePath + << "): " << hdr.axisPixelLengths[0] + << " != " << hdrFirst.axisPixelLengths[0] << std::endl; } - return ranSuccessfully; + // Check the format is correct and create the Workspace + if (!valid) { + // Invalid files, record error + throw std::runtime_error( + "An issue has been found in the header of this FITS file: " + + hdr.filePath + + ". This algorithm currently doesn't support FITS files with " + "non-standard extensions, more than two axis " + "of data, or has detected that all the files are " + "not similar."); + } } /** - * Reads a file containing rotation values for each image into a vector of - *doubles - * @param rotFilePath The path to a file containing rotation values - * @param fileCount number of images which should have corresponding rotation - *values in the file + * Returns the trailing number from a string minus leading 0's (so 25 from + * workspace_00025)the confidence with with this algorithm can load the file + * + * @param name string with a numerical suffix * - * @returns vector<double> A vector of all the rotation values + * @returns A numerical representation of the string minus leading characters + * and leading 0's */ -std::vector<double> LoadFITS::readRotations(std::string rotFilePath, - size_t fileCount) { - std::vector<double> allRotations; - ifstream fStream(rotFilePath.c_str()); - - try { - // Ensure valid file - if (fStream.good()) { - // Get lines, split words, verify and add to map. - string line; - vector<string> lineSplit; - size_t ind = -1; - while (getline(fStream, line)) { - ind++; - boost::split(lineSplit, line, boost::is_any_of("\t")); - - if (ind == 0 || lineSplit[0] == "") - continue; // Skip first iteration or where rotation value is empty - - allRotations.push_back(lexical_cast<double>(lineSplit[1])); - } - - // Check the number of rotations in file matches number of files - if (ind != fileCount) - throw std::runtime_error("File error, throw higher up."); - - fStream.close(); - } else { - throw std::runtime_error("File error, throw higher up."); - } - } catch (...) { - throw std::runtime_error("Invalid file path or file format: Expected a " - "file with a line separated list of rotations " - "with the same number of entries as other files."); +size_t LoadFITS::fetchNumber(const std::string &name) { + string tmpStr = ""; + for (auto it = name.end() - 1; isdigit(*it); --it) { + tmpStr.insert(0, 1, *it); + } + while (tmpStr.length() > 0 && tmpStr[0] == '0') { + tmpStr.erase(tmpStr.begin()); } + return (tmpStr.length() > 0) ? lexical_cast<size_t>(tmpStr) : 0; +} - return allRotations; +/** + * Adds 0's to the front of a number to create a string of size totalDigitCount + * including number + * + * @param number input number to add padding to + * @parm totalDigitCount width of the resulting string with 0s followed by + * number + * + * @return A string with the 0-padded number + */ +std::string LoadFITS::padZeros(const size_t number, + const size_t totalDigitCount) { + std::ostringstream ss; + ss << std::setw(static_cast<int>(totalDigitCount)) << std::setfill('0') + << static_cast<int>(number); + + return ss.str(); } /** * Maps the header keys to specified values */ void LoadFITS::mapHeaderKeys() { - bool useProperties = true; + if ("" == getPropertyValue(m_HEADER_MAP_NAME)) + return; // If a map file is selected, use that. - if (getPropertyValue(HEADER_MAP_NAME) != "") { - // std::vector<double> allRotations; - ifstream fStream(getPropertyValue(HEADER_MAP_NAME).c_str()); + std::string name = getPropertyValue(m_HEADER_MAP_NAME); + ifstream fStream(name.c_str()); - try { - // Ensure valid file - if (fStream.good()) { - // Get lines, split words, verify and add to map. - string line; - vector<string> lineSplit; - while (getline(fStream, line)) { - boost::split(lineSplit, line, boost::is_any_of("=")); - - if (lineSplit[0] == ROTATION_NAME && lineSplit[1] != "") - m_headerRotationKey = lineSplit[1]; - - if (lineSplit[0] == BIT_DEPTH_NAME && lineSplit[1] != "") - m_headerBitDepthKey = lineSplit[1]; - - if (lineSplit[0] == AXIS_NAMES_NAME && lineSplit[1] != "") { - m_headerAxisNameKeys.clear(); - std::string propVal = getProperty(AXIS_NAMES_NAME); - boost::split(m_headerAxisNameKeys, propVal, boost::is_any_of(",")); - } - - if (lineSplit[0] == IMAGE_KEY_NAME && lineSplit[1] != "") { - m_headerImageKeyKey = lineSplit[1]; - } + try { + // Ensure valid file + if (fStream.good()) { + // Get lines, split words, verify and add to map. + std::string line; + vector<std::string> lineSplit; + while (getline(fStream, line)) { + boost::split(lineSplit, line, boost::is_any_of("=")); + + if (lineSplit[0] == m_ROTATION_NAME && lineSplit[1] != "") + m_headerRotationKey = lineSplit[1]; + + if (lineSplit[0] == m_BIT_DEPTH_NAME && lineSplit[1] != "") + m_headerBitDepthKey = lineSplit[1]; + + if (lineSplit[0] == m_AXIS_NAMES_NAME && lineSplit[1] != "") { + m_headerAxisNameKeys.clear(); + boost::split(m_headerAxisNameKeys, lineSplit[1], + boost::is_any_of(",")); } - fStream.close(); - useProperties = false; - } else { - throw std::runtime_error("File error, throw higher up."); + if (lineSplit[0] == m_IMAGE_KEY_NAME && lineSplit[1] != "") { + m_headerImageKeyKey = lineSplit[1]; + } } - } catch (...) { - g_log.information("Cannot load specified map file, using property values " - "and/or defaults."); - useProperties = true; - } - } - if (useProperties) { - // Try and set from the loader properties if present and didn't load map - // file - if (getPropertyValue(BIT_DEPTH_NAME) != "") - m_headerBitDepthKey = getPropertyValue(BIT_DEPTH_NAME); - if (getPropertyValue(ROTATION_NAME) != "") - m_headerRotationKey = getPropertyValue(ROTATION_NAME); - if (getPropertyValue(AXIS_NAMES_NAME) != "") { - m_headerAxisNameKeys.clear(); - std::string propVal = getProperty(AXIS_NAMES_NAME); - boost::split(m_headerAxisNameKeys, propVal, boost::is_any_of(",")); + fStream.close(); + } else { + throw std::runtime_error( + "Error while trying to read header keys mapping file: " + name); } - if (getPropertyValue(IMAGE_KEY_NAME) != "") - m_headerImageKeyKey = getPropertyValue(IMAGE_KEY_NAME); + } catch (...) { + g_log.error("Cannot load specified map file, using property values " + "and/or defaults."); } } -} -} + +} // namespace DataHandling +} // namespace Mantid diff --git a/Code/Mantid/Framework/DataHandling/test/LoadFITSTest.h b/Code/Mantid/Framework/DataHandling/test/LoadFITSTest.h index 01cd6d92e88d84d328fad2818251bcfbaa42d8f0..95ff18df83794367f217b95328e8b6de93e03882 100644 --- a/Code/Mantid/Framework/DataHandling/test/LoadFITSTest.h +++ b/Code/Mantid/Framework/DataHandling/test/LoadFITSTest.h @@ -1,61 +1,178 @@ -#ifndef LOADFITSTEST_H_ -#define LOADFITSTEST_H_ +#ifndef MANTID_DATAHANDLING_LOADFITSTEST_H_ +#define MANTID_DATAHANDLING_LOADFITSTEST_H_ #include <cxxtest/TestSuite.h> + +#include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/AnalysisDataService.h" #include "MantidDataHandling/LoadFITS.h" using namespace Mantid::API; using namespace Mantid::DataHandling; -using namespace Mantid::Kernel; -class LoadFITSTest : public CxxTest::TestSuite -{ -public: - void testInit() - { +class LoadFITSTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static LoadFITSTest *createSuite() { return new LoadFITSTest(); } + static void destroySuite(LoadFITSTest *suite) { delete suite; } + + void test_algorithm() { + std::string name = "LoadFITS"; + int version = 1; + testAlg = + Mantid::API::AlgorithmManager::Instance().create(name /*, version*/); + TS_ASSERT(testAlg); + TS_ASSERT_EQUALS(testAlg->name(), name); + TS_ASSERT_EQUALS(testAlg->version(), version); + } + + void test_castAlgorithm() { + // can create + boost::shared_ptr<LoadFITS> a; + TS_ASSERT(a = boost::make_shared<LoadFITS>()); + // can cast to inherited interfaces and base classes + + TS_ASSERT(dynamic_cast<Mantid::DataHandling::LoadFITS *>(a.get())); + TS_ASSERT(dynamic_cast<Mantid::API::Algorithm *>(a.get())); + TS_ASSERT(dynamic_cast<Mantid::Kernel::PropertyManagerOwner *>(a.get())); + TS_ASSERT(dynamic_cast<Mantid::API::IAlgorithm *>(a.get())); + TS_ASSERT(dynamic_cast<Mantid::Kernel::IPropertyManager *>(a.get())); + } + + void test_initAlgorithm() { + LoadFITS lf; + TS_ASSERT_THROWS_NOTHING(lf.initialize()); + } + + void test_propertiesMissing() { + LoadFITS lf; + TS_ASSERT_THROWS_NOTHING(lf.initialize()); + TS_ASSERT_THROWS_NOTHING(lf.setPropertyValue("Filename", smallFname1)); + TS_ASSERT_THROWS(lf.execute(), std::runtime_error); + TS_ASSERT(!lf.isExecuted()); + + LoadFITS lf2; + TS_ASSERT_THROWS_NOTHING(lf2.initialize()); + TS_ASSERT_THROWS_NOTHING( + lf2.setPropertyValue("OutputWorkspace", "out_ws_name")); + TS_ASSERT_THROWS(lf2.execute(), std::runtime_error); + TS_ASSERT(!lf2.isExecuted()); + } + + void test_wrongProp() { + LoadFITS lf; + TS_ASSERT_THROWS_NOTHING(lf.initialize()); + TS_ASSERT_THROWS(lf.setPropertyValue("file", "anything"), + std::runtime_error); + TS_ASSERT_THROWS(lf.setPropertyValue("output", "anything"), + std::runtime_error); + TS_ASSERT_THROWS(lf.setPropertyValue("FITS", "anything"), + std::runtime_error); + + TS_ASSERT_THROWS(lf.setPropertyValue("ImageKey", "anything"), + Mantid::Kernel::Exception::NotFoundError); + TS_ASSERT_THROWS(lf.setPropertyValue("BITPIX", "anything"), + std::runtime_error); + TS_ASSERT_THROWS(lf.setPropertyValue("NAXIS", "anything"), + std::runtime_error); + TS_ASSERT_THROWS(lf.setPropertyValue("NAXIS1", "anything"), + std::runtime_error); + } + + void test_init() { TS_ASSERT_THROWS_NOTHING(algToBeTested.initialize()); - TS_ASSERT( algToBeTested.isInitialized() ); - - if ( !algToBeTested.isInitialized() ) algToBeTested.initialize(); - - outputSpace="LoadFITSTest"; - algToBeTested.setPropertyValue("OutputWorkspace", outputSpace); - + TS_ASSERT(algToBeTested.isInitialized()); + + if (!algToBeTested.isInitialized()) + algToBeTested.initialize(); + + outputSpace = "LoadFITSTest"; + algToBeTested.setPropertyValue("OutputWorkspace", outputSpace); + // Should fail because mandatory parameter has not been set - TS_ASSERT_THROWS(algToBeTested.execute(),std::runtime_error); - - inputFile = "FITS_small_01.fits,FITS_small_02.fits"; - algToBeTested.setPropertyValue("Filename", inputFile); - - // Set the ImageKey to be 0 (as it is missing from the test file and is required); - algToBeTested.setProperty<int>("ImageKey", 0); - } - - void testPerformAssertions() - { - TS_ASSERT_THROWS_NOTHING(algToBeTested.execute()); - TS_ASSERT( algToBeTested.isExecuted() ); + TS_ASSERT_THROWS(algToBeTested.execute(), std::runtime_error); + + inputFile = smallFname1 + ", " + smallFname2; + algToBeTested.setPropertyValue("Filename", inputFile); + + // Set the ImageKey to be 0 (this used to be required, but the key + // should not be there any longer); + TS_ASSERT_THROWS( algToBeTested.setProperty<int>("ImageKey", 0), + Mantid::Kernel::Exception::NotFoundError); + } + + void test_performAssertions() { + TS_ASSERT_THROWS_NOTHING(algToBeTested.execute()); + TS_ASSERT(algToBeTested.isExecuted()); // get workspace generated - WorkspaceGroup_sptr output = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(outputSpace); - TS_ASSERT_EQUALS( output->getNumberOfEntries(), 2); // Number of time bins should equal number of files - MatrixWorkspace_sptr ws1 = boost::dynamic_pointer_cast<MatrixWorkspace>(output->getItem(0)); - MatrixWorkspace_sptr ws2 = boost::dynamic_pointer_cast<MatrixWorkspace>(output->getItem(1)); - - TS_ASSERT_EQUALS(ws1->getNumberHistograms(), SPECTRA_COUNT); // Number of spectra + WorkspaceGroup_sptr out; + TS_ASSERT(AnalysisDataService::Instance().doesExist(outputSpace)); + TS_ASSERT_THROWS_NOTHING( + out = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( + outputSpace)); + TS_ASSERT_EQUALS(out->getNumberOfEntries(), + 2); // Number of time bins should equal number of files + MatrixWorkspace_sptr ws1; + TS_ASSERT_THROWS_NOTHING( + ws1 = boost::dynamic_pointer_cast<MatrixWorkspace>(out->getItem(0))); + MatrixWorkspace_sptr ws2; + TS_ASSERT_THROWS_NOTHING( + ws2 = boost::dynamic_pointer_cast<MatrixWorkspace>(out->getItem(1))); + + // basic FITS headers + const auto run = ws1->run(); + TS_ASSERT_EQUALS(run.getLogData("SIMPLE")->value(), hdrSIMPLE); + TS_ASSERT_EQUALS(run.getLogData("BITPIX")->value(), hdrBITPIX); + TS_ASSERT_EQUALS(run.getLogData("NAXIS")->value(), hdrNAXIS); + TS_ASSERT_EQUALS(run.getLogData("NAXIS1")->value(), hdrNAXIS1); + TS_ASSERT_EQUALS(run.getLogData("NAXIS2")->value(), hdrNAXIS2); + + // Number of spectra + TS_ASSERT_EQUALS(ws1->getNumberHistograms(), SPECTRA_COUNT); + TS_ASSERT_EQUALS(ws2->getNumberHistograms(), SPECTRA_COUNT); + // Sum the two bins from the last spectra - should be 70400 - double sumY = ws1->readY(SPECTRA_COUNT-1)[0] + ws2->readY(SPECTRA_COUNT-1)[0]; - TS_ASSERT_EQUALS(sumY, 275); - // Check the sum of the error values for the last spectra in each file - should be 375.183 - double sumE = ws1->readE(SPECTRA_COUNT-1)[0] + ws2->readE(SPECTRA_COUNT-1)[0]; - TS_ASSERT_LESS_THAN(abs(sumE-23.4489), 0.0001); // Include a small tolerance check with the assert - not exactly 375.183 + double sumY = + ws1->readY(SPECTRA_COUNT - 1)[0] + ws2->readY(SPECTRA_COUNT - 1)[0]; + TS_ASSERT_EQUALS(sumY, 275); + // Check the sum of the error values for the last spectra in each file - + // should be 375.183 + double sumE = + ws1->readE(SPECTRA_COUNT - 1)[0] + ws2->readE(SPECTRA_COUNT - 1)[0]; + TS_ASSERT_LESS_THAN(abs(sumE - 23.4489), 0.0001); // Include a small + // tolerance check with + // the assert - not + // exactly 375.183 } private: + Mantid::API::IAlgorithm_sptr testAlg; LoadFITS algToBeTested; + std::string inputFile; std::string outputSpace; - const static size_t SPECTRA_COUNT = 262144; // Based on the 512*512 test image + static const std::string smallFname1; + static const std::string smallFname2; + + const static size_t xdim = 512; + const static size_t ydim = 512; + const static size_t SPECTRA_COUNT = xdim * ydim; + // FITS headers + const static std::string hdrSIMPLE; + const static std::string hdrBITPIX; + const static std::string hdrNAXIS; + const static std::string hdrNAXIS1; + const static std::string hdrNAXIS2; }; +const std::string LoadFITSTest::smallFname1 = "FITS_small_01.fits"; +const std::string LoadFITSTest::smallFname2 = "FITS_small_02.fits"; + +const std::string LoadFITSTest::hdrSIMPLE = "T"; +const std::string LoadFITSTest::hdrBITPIX = "16"; +const std::string LoadFITSTest::hdrNAXIS = "2"; +const std::string LoadFITSTest::hdrNAXIS1 = "512"; +const std::string LoadFITSTest::hdrNAXIS2 = "512"; -#endif \ No newline at end of file +#endif // MANTID_DATAHANDLING_LOADFITSTEST_H_ diff --git a/Code/Mantid/docs/source/algorithms/LoadFITS-v1.rst b/Code/Mantid/docs/source/algorithms/LoadFITS-v1.rst index 1184957082ea3bb9cc5fcd8427abafca0877988f..dfeae5b13f7f708ab961b2d2f3ac96cdc33508b7 100644 --- a/Code/Mantid/docs/source/algorithms/LoadFITS-v1.rst +++ b/Code/Mantid/docs/source/algorithms/LoadFITS-v1.rst @@ -62,15 +62,15 @@ Usage ws = wsg.getItem(0) # A couple of standard FITS header entries - bpp_log = '_BITPIX' + bpp_log = 'BITPIX' try: log = ws.getRun().getLogData(bpp_log).value print "Bits per pixel: %s" % int(log) except RuntimeError: print "Could not find the keyword '%s' in this FITS file" % bpp_log - axis1_log = '_NAXIS1' - axis2_log = '_NAXIS2' + axis1_log = 'NAXIS1' + axis2_log = 'NAXIS2' try: log1 = ws.getRun().getLogData(axis1_log).value log2 = ws.getRun().getLogData(axis2_log).value