diff --git a/doc/whats_new/changes.rst b/doc/whats_new/changes.rst index c1170ebe..f47edb35 100644 --- a/doc/whats_new/changes.rst +++ b/doc/whats_new/changes.rst @@ -7,6 +7,7 @@ Version 0.24 * Add support for sm files * Add support for CZ CF3 FCS files +* Improved type tttr file type inference to mitigate crashes Version 0.23 diff --git a/ext/python/FileCheck.i b/ext/python/FileCheck.i new file mode 100644 index 00000000..074e4db9 --- /dev/null +++ b/ext/python/FileCheck.i @@ -0,0 +1,6 @@ +%module tttrlib +%{ +#include "FileCheck.h" +%} + +%include "FileCheck.h" diff --git a/ext/python/TTTR.py b/ext/python/TTTR.py index 1f993987..69eafc48 100644 --- a/ext/python/TTTR.py +++ b/ext/python/TTTR.py @@ -48,19 +48,8 @@ def __init__(self, *args, **kwargs): import pathlib if isinstance(args[0], str) or isinstance(args[0], pathlib.Path): if len(args) == 1: - suffix = str(pathlib.Path(args[0]).suffix)[1:].upper() - obj = str(pathlib.Path(args[0]).absolute()) - if suffix == 'HT3' or suffix == 'PTU': - tttr_container_type = suffix - elif suffix == 'HDF' or suffix == 'H5' or suffix == 'HDF5': - tttr_container_type = 'HDF' - else: - raise ValueError( - "The file type '{}' does not allow to determine " - "the container format. Specify the 'tttr_container_type' " - "parameter.".format(suffix) - ) - this = _tttrlib.new_TTTR(obj, tttr_container_type) + obj = str(pathlib.Path(args[0]).absolute().as_posix()) + this = _tttrlib.new_TTTR(obj) else: this = _tttrlib.new_TTTR(*args, **kwargs) else: diff --git a/ext/python/tttrlib.i b/ext/python/tttrlib.i index 9a14d01e..c9391419 100644 --- a/ext/python/tttrlib.i +++ b/ext/python/tttrlib.i @@ -32,6 +32,7 @@ %include "info.h" %include "misc_types.i" +%include "FileCheck.i" %include "TTTRHeader.i" %include "TTTRRange.i" diff --git a/include/FileCheck.h b/include/FileCheck.h new file mode 100644 index 00000000..ba733ce0 --- /dev/null +++ b/include/FileCheck.h @@ -0,0 +1,93 @@ +#ifndef TTTRLIB_FILECHECK_H +#define TTTRLIB_FILECHECK_H + +#include +#include +#include + +#include "TTTRHeader.h" + +/** + * @brief Checks if the given file is an HDF5 file. + * + * This function opens the specified file and performs checks to determine if + * it conforms to the HDF5 file format. + * + * @param filename The name of the file to check. + * @return true if the file is an HDF5 file, false otherwise. + */ +bool isHDF5File(const std::string& filename); + +/** + * @brief Checks if the given file is an SM file. + * + * This function opens the specified file and reads the first 64 bits to determine + * if the file is an SM file, specifically checking if the value equals 2. + * + * @param filename The name of the file to check. + * @return true if the file is an SM file, false otherwise. + */ +bool isSMFile(const std::string& filename); + +/** + * @brief Checks if the given file is a PTU file. + * + * This function opens the specified file and reads the first 8 bytes (Magic) to + * verify if the file is a PTU file by checking if it starts with "PQTTTR". + * + * @param filename The name of the file to check. + * @return true if the file is a PTU file, false otherwise. + */ +bool isPTUFile(const std::string& filename); + +/** + * @brief Checks if the given file is an HT3 file. + * + * This function opens the specified file and reads the header to check if the + * file format version is "1.0", which is required for HT3 files. + * + * @param filename The name of the file to check. + * @return true if the file is an HT3 file, false otherwise. + */ +bool isHT3File(const std::string& filename); + +/** + * @brief Checks if the given file is a BH132 file. + * + * This function opens the specified file and reads the header to determine if + * it is a BH132 file by checking if the `macro_time_clock` is in the range (0, 10000) + * and if the `invalid` flag is set to true. + * + * @param filename The name of the file to check. + * @return true if the file is a BH132 file, false otherwise. + */ +bool isBH132File(const std::string& filename); + + +/** + * @brief Determines the type of the TTTR file based on its content. + * + * This function uses various type-checking functions to infer the type of the + * given TTTR file. It performs checks in a specific order to identify the file type. + * Returns a container identifier corresponding to the file type. + * + * @param fn The name of the file to check. + * @return An integer representing the type of the TTTR file, or -1 if the type cannot be determined. + */ +int inferTTTRFileType(const char* fn); + + +/** + * @brief Determines if the given file is a Carl Zeiss Confocor3 (CZ Confocor3) raw data file. + * + * This function reads the header of the file and checks for specific patterns + * to determine if the file is of type CZ Confocor3. It performs the necessary checks + * and returns true if the file type is identified, otherwise false. + * + * @param filename The name of the file to check. + * @return True if the file is a CZ Confocor3 file, false otherwise. + */ +bool isCZConfocor3File(const std::string& filename); + + +#endif //TTTRLIB_FILECHECK_H diff --git a/include/TTTR.h b/include/TTTR.h index a1f122e8..d783ee2c 100644 --- a/include/TTTR.h +++ b/include/TTTR.h @@ -27,6 +27,7 @@ #include "Histogram.h" #include "TTTRHeader.h" +#include "FileCheck.h" #include "TTTRMask.h" #include "TTTRRecordReader.h" #include "TTTRRecordTypes.h" @@ -683,6 +684,25 @@ class TTTR : public std::enable_shared_from_this{ */ TTTR(const char *filename, int container_type, bool read_input); + /** + * @brief Constructs a TTTR object based on the provided filename by inferring the file type. + * + * This constructor initializes a TTTR object by automatically determining the file type + * from the given filename. It utilizes the `inferTTTRFileType` function to identify + * the type of TTTR file and then calls the existing constructor with the inferred container type. + * The internal state related to the container type is initialized based on the file type inference. + * + * The constructor performs the following actions: + * - Infers the container type from the filename using `inferTTTRFileType`. + * - Initializes the TTTR object using the existing constructor `TTTR(const char *fn, int container_type, bool some_flag)`. + * - Sets the container type string based on the inferred container type. + * - Handles cases where the inferred container type is not supported by setting the container type string to `"Unknown"` + * and logging an error message. + * + * @param filename The path to the TTTR file to be analyzed. The file type is inferred based on this filename. + */ + TTTR(const char *filename); + /*! * Constructor for TTTR object that reads the content of the file. * diff --git a/src/FileCheck.cpp b/src/FileCheck.cpp new file mode 100644 index 00000000..c016c448 --- /dev/null +++ b/src/FileCheck.cpp @@ -0,0 +1,280 @@ +#include "FileCheck.h" + +// HDF5 file signature: "\x89HDF\r\n\x1A\n" +bool isHDF5File(const std::string& filename) { + // Open the file in binary mode + std::ifstream file(filename, std::ios::binary); + + if (!file.is_open()) { + std::cerr << "Error: Could not open the file.\n"; + return false; + } + + // The HDF5 file signature is 8 bytes long + const std::array hdf5_signature = {0x89, 'H', 'D', 'F', 0x0D, 0x0A, 0x1A, 0x0A}; + std::array file_signature = {0}; + + // Read the first 8 bytes of the file + file.read(reinterpret_cast(file_signature.data()), file_signature.size()); + + // Check if we read 8 bytes + if (!file) { + std::cerr << "Error: Could not read the file signature.\n"; + return false; + } + + // Compare the read bytes with the HDF5 signature + return file_signature == hdf5_signature; +} + + +// Function to check if the file is an SM file +bool isSMFile(const std::string& filename) { + uint64_t first_value; + FILE* file = fopen(filename.c_str(), "rb"); + + if (file == nullptr) { + std::cerr << "Error opening file: " << filename << std::endl; + return false; + } + + // Read the first 64-bit (8 bytes) from the file + size_t read_size = fread(&first_value, sizeof(first_value), 1, file); + if (read_size != 1) { + std::cerr << "Error reading the file" << std::endl; + fclose(file); + return false; + } + + // Close the file + fclose(file); + + // Compare the read value with 2 + if (first_value == 2) { + return true; + } + + return false; +} + + +// Function to check if the file is a PTU file +bool isPTUFile(const std::string& filename) { + char Magic[8]; + FILE* file = fopen(filename.c_str(), "rb"); + + if (file == nullptr) { + std::cerr << "Error opening file: " << filename << std::endl; + return false; + } + + // Always rewind the file pointer to the beginning + std::fseek(file, 0, SEEK_SET); + + // Read the first 8 bytes of the file (Magic) + size_t read_size = fread(Magic, 1, sizeof(Magic), file); + if (read_size != sizeof(Magic)) { + std::cerr << "Error reading the Magic header" << std::endl; + fclose(file); + return false; + } + + // Close the file + fclose(file); + + // Check if the Magic header starts with "PQTTTR" + if (strncmp(Magic, "PQTTTR", 6) == 0) { + return true; // This is a PTU file + } + + return false; // This is not a PTU file +} + + +// Function to check if the file is an HT3 file +bool isHT3File(const std::string& filename) { + pq_ht3_Header_t ht3_header_begin; + FILE* file = fopen(filename.c_str(), "rb"); + + if (file == nullptr) { + std::cerr << "Error opening file: " << filename << std::endl; + return false; + } + + // Always rewind the file pointer to the beginning + std::fseek(file, 0, SEEK_SET); + + // Read the header of the HT3 file + size_t read_size = fread(&ht3_header_begin, 1, sizeof(ht3_header_begin), file); + if (read_size != sizeof(ht3_header_begin)) { + std::cerr << "Error reading the HT3 header" << std::endl; + fclose(file); + return false; + } + + // Close the file + fclose(file); + + // Check if the FormatVersion is "1.0" + if (strncmp(ht3_header_begin.FormatVersion, "1.0", 3) == 0) { + return true; // This is a valid HT3 file + } + + return false; // This is not an HT3 file +} + +// Function to check if the file is a BH132 file +bool isBH132File(const std::string& filename) { + bh_spc132_header_t rec; + FILE* file = fopen(filename.c_str(), "rb"); + + if (file == nullptr) { + std::cerr << "Error opening file: " << filename << std::endl; + return false; + } + + // Always rewind the file pointer to the beginning + std::fseek(file, 0, SEEK_SET); + + // Read the header of the BH132 file + size_t read_size = fread(&rec, sizeof(rec), 1, file); + if (read_size != 1) { + std::cerr << "Error reading the BH132 header" << std::endl; + fclose(file); + return false; + } + + // Close the file + fclose(file); + + // Check if macro_time_clock is in the range and dataset is marked as invalid + if (rec.bits.macro_time_clock > 0 && rec.bits.macro_time_clock < 10000 && rec.bits.invalid) { + return true; // This is a valid BH132 file + } + + return false; // This is not a BH132 file +} + +/** + * @brief Determines if the given file is a Carl Zeiss Confocor3 (CZ Confocor3) raw data file. + * + * This function reads the header of the file and checks for specific patterns + * to determine if the file is of type CZ Confocor3. It performs the necessary checks + * and returns true if the file type is identified, otherwise false. + * + * @param filename The name of the file to check. + * @return True if the file is a CZ Confocor3 file, false otherwise. + */ +bool isCZConfocor3File(const std::string& filename) { + std::FILE* file = std::fopen(filename.c_str(), "rb"); + if (!file) { + std::cerr << "Error opening file: " << filename << std::endl; + return false; + } + + cz_confocor3_settings_t rec; + std::fseek(file, 0, SEEK_SET); + + size_t read_size = std::fread(&rec, sizeof(rec), 1, file); + std::fclose(file); + + if (read_size != 1) { + std::cerr << "Error reading file header" << std::endl; + return false; + } + + float frequency_float = rec.bits.frequency; + double mt_clk = 1.0 / frequency_float; + + // Validate frequency + if (frequency_float <= 0 || mt_clk >= 4000) { + return false; // Invalid or out-of-range frequency + } + + // Validate measure_id fields + for (int i = 0; i < 4; ++i) { + if (rec.bits.measure_id[i] >= 4000) { + return false; // measure_id is out of valid range + } + } + + // Validate measurement_position + if (rec.bits.measurement_position >= 4000) { + return false; // measurement_position is out of valid range + } + + // Validate kinetic_index + if (rec.bits.kinetic_index >= 4000) { + return false; // kinetic_index is out of valid range + } + + // Validate repetition_number + if (rec.bits.repetition_number >= 4000) { + return false; // repetition_number is out of valid range + } + + // Validate channel + if (rec.bits.channel >= 4000) { + return false; // channel value is out of valid range + } + + return true; // File is identified as a CZ Confocor3 file if all checks pass +} + + +/** + * @brief Infers the type of a TTTR file based on its content. + * + * This function uses a series of checks to determine the type of a TTTR file. + * It returns an integer representing the container identifier for the file type. + * + * @param filename The name of the file to infer the type. + * @return An integer identifier for the file type, or -1 if the file type cannot be determined. + */ +int inferTTTRFileType(const char* fn) { + // Convert const char* to std::string + std::string filename(fn); + + // Define the lambda function for converting a string to lowercase + auto to_lowercase = [](const std::string& str) -> std::string { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; + }; + + // Extract file extension + std::string::size_type idx = filename.rfind('.'); + if (idx != std::string::npos) { + std::string extension = to_lowercase(filename.substr(idx + 1)); + + // Check based on file extension + if (extension == "sm") { + return SM_CONTAINER; + } else if (extension == "spc") { + // Verify if it's a BH file + if (isBH132File(filename)) { + return BH_SPC130_CONTAINER; // Adjust if there are different types + } + } else if (extension == "ht3") { + if (isHT3File(filename)) { + return PQ_HT3_CONTAINER; + } + } else if (extension == "ptu") { + if (isPTUFile(filename)) { + return PQ_PTU_CONTAINER; + } + } else if (extension == "hdf5") { + if (isHDF5File(filename)) { + return PHOTON_HDF_CONTAINER; + } + } else if (extension == "raw") { + if (isCZConfocor3File(filename)) { + return CZ_CONFOCOR3_CONTAINER; + } + } + } + + // Fallback to content-based checking if necessary or unknown type + return -1; // Or another value representing "unknown" or unsupported type +} \ No newline at end of file diff --git a/src/TTTR.cpp b/src/TTTR.cpp index c42895ca..01463dd7 100644 --- a/src/TTTR.cpp +++ b/src/TTTR.cpp @@ -121,11 +121,15 @@ TTTR::TTTR(const TTTR &p2){ } TTTR::TTTR(const char *fn, int container_type, bool read_input) : TTTR(){ - filename.assign(fn); - tttr_container_type = container_type; - if(read_input){ - if(read_file()) - find_used_routing_channels(); + if(container_type >= 0){ + filename.assign(fn); + tttr_container_type = container_type; + if(read_input){ + if(read_file()) + find_used_routing_channels(); + } + } else{ + std::cerr << "File " << fn << " not supported." << std::endl; } } @@ -136,25 +140,26 @@ TTTR::TTTR(const char *fn, int container_type) : TTTR(fn, container_type, true) ); } catch(...) { - std::cerr << "Container type " << container_type - << " not supported." << std::endl; + std::cerr << "TTTR::TTTR(const char *fn, int container_type): Container type " << container_type << " not supported." << std::endl; } } TTTR::TTTR(const char *fn, const char *container_type) : TTTR() { -// try { + try { tttr_container_type_str.assign(container_type); tttr_container_type = container_names.left.at(std::string(container_type)); filename.assign(fn); if(read_file()) find_used_routing_channels(); -// } -// catch(...) { -// std::cerr << "Container type " << container_type -// << " not supported." << std::endl; -// } + } + catch(...) { + std::cerr << "TTTR::TTTR(const char *fn, const char *container_type): Container type " << container_type << " not supported." << std::endl; + } } +TTTR::TTTR(const char* filename) : TTTR(filename, inferTTTRFileType(filename), true) {} + + void TTTR::shift_macro_time(int shift) { for(size_t i=0; i