Skip to content

Commit 86594f7

Browse files
authored
Merge pull request #42 from Fluorescence-Tools/development
Add support for CZ and sm files
2 parents 4ee5734 + 777a522 commit 86594f7

File tree

9 files changed

+274
-5
lines changed

9 files changed

+274
-5
lines changed

doc/whats_new/changes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
Version 0.24
66
============
77

8-
**January, 2024**
8+
* Add support for sm files
9+
* Add support for CZ CF3 FCS files
910

1011

1112
Version 0.23

include/TTTR.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,16 @@ class TTTR : public std::enable_shared_from_this<TTTR>{
343343
*/
344344
int read_hdf_file(const char *fn);
345345

346+
/*!
347+
* \brief Reads the essential content from a SM file.
348+
*
349+
* Reads the macro time, and routing channel number from the specified Photon SM file.
350+
*
351+
* \param fn Filename pointing to the Photon HDF file.
352+
* \return Returns an integer indicating the success or failure of the file reading operation.
353+
*/
354+
int read_sm_file(const char *fn);
355+
346356
/*!
347357
* \brief Reads a specified number of records from the file.
348358
*

include/TTTRHeader.h

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <sstream> // std::stringstream
1919
#include <iomanip> /* std::setfill */
2020
#include <fstream> /* ifstream */
21+
#include <cstring> /* std::memcpy */
2122

2223
#include <any>
2324
// #include <boost/any.hpp>
@@ -44,6 +45,37 @@ const std::string TTTRTagBits = "TTResultFormat_BitsPerRecord"; // Bits per T
4445
const std::string FileTagEnd = "Header_End"; // Always appended as last tag (BLOCKEND)
4546

4647

48+
/**
49+
* Swaps the endianness of a given value.
50+
*
51+
* This function takes a reference to a value of any type `T` and swaps its byte order
52+
* between little-endian and big-endian formats. It uses a union to access the raw bytes
53+
* of the value and reverses the byte order using `std::reverse_copy`.
54+
*
55+
* @tparam T The type of the value whose endianness is to be swapped. Must be trivially
56+
* copyable and have a defined byte size.
57+
* @param val A reference to the value whose endianness is to be swapped. The value is
58+
* modified in-place.
59+
*
60+
* Example:
61+
*
62+
* int32_t original = 0x12345678;
63+
* SwapEndian(original);
64+
* // original now contains 0x78563412
65+
*/
66+
template <typename T>
67+
void SwapEndian(T &val) {
68+
union U {
69+
T val;
70+
std::array<std::uint8_t, sizeof(T)> raw;
71+
} src, dst;
72+
73+
src.val = val;
74+
std::reverse_copy(src.raw.begin(), src.raw.end(), dst.raw.begin());
75+
val = dst.val;
76+
}
77+
78+
4779
class TTTRHeader {
4880

4981
friend class TTTR;
@@ -275,8 +307,22 @@ class TTTRHeader {
275307
bool rewind = true
276308
);
277309

310+
/**
311+
* Reads and parses the header of an SM (Single molecule) record from the given file.
312+
* The parsed information is stored in the provided JSON object `j`.
313+
*
314+
* This function performs the following tasks:
315+
* 1. Adds a tag to the JSON object `j` for the record type.
316+
* 2. Reads and processes the header information from the file.
317+
*
318+
* @param file A pointer to the file from which the header is read.
319+
* @param j A reference to a nlohmann::json object where parsed information will be stored.
320+
* @return The file position after reading the header.
321+
*/
322+
static size_t read_sm_header(FILE* file, nlohmann::json &j);
323+
278324
/*!
279-
* @brief Reads the header of a Becker & Hickel SPC132 file and sets the reading routing.
325+
* @brief Reads the header of a Becker & Hickl SPC132 file and sets the reading routing.
280326
*
281327
* @param fpin File pointer to the SPC132 file.
282328
* @param data Output parameter for JSON data.

include/TTTRHeaderTypes.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#define BH_SPC600_4096_CONTAINER 4
3939
#define PHOTON_HDF_CONTAINER 5
4040
#define CZ_CONFOCOR3_CONTAINER 6
41+
#define SM_CONTAINER 7
4142

4243
// tttrlib record type identifier definitions
4344
#define PQ_RECORD_TYPE_HHT2v2 1
@@ -50,6 +51,7 @@
5051
#define BH_RECORD_TYPE_SPC600_256 8
5152
#define BH_RECORD_TYPE_SPC600_4096 9
5253
#define CZ_RECORD_TYPE_CONFOCOR3 10
54+
#define SM_RECORD_TYPE 11
5355

5456

5557
/*
@@ -139,6 +141,30 @@ typedef struct {
139141
} pq_ht3_TTModeHeader_t;
140142

141143

144+
145+
// Header information structure
146+
typedef struct {
147+
uint32_t version;
148+
std::string comment;
149+
std::string simple_str;
150+
uint32_t pointer1;
151+
std::string file_section_type;
152+
uint32_t magic1;
153+
uint32_t magic2;
154+
std::string col1_name;
155+
double col1_resolution;
156+
double col1_offset;
157+
uint32_t col1_bho;
158+
std::string col2_name;
159+
double col2_resolution;
160+
double col2_offset;
161+
uint32_t col2_bho;
162+
std::string col3_name;
163+
double col3_resolution;
164+
double col3_offset;
165+
std::vector<std::string> channel_labels;
166+
} sm_header_t;
167+
142168
/// Carl Zeiss Confocor3 raw data
143169
typedef union cz_confocor3_settings{
144170
uint32_t allbits;

include/TTTRRecordTypes.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
#include <cstdint>
55

6+
// SM files
7+
typedef union sm_record {
8+
uint64_t time;
9+
uint32_t channel;
10+
} sm_record_t;
11+
612

713
// HydraHarp/TimeHarp260 T2 record
814
typedef union pq_hh_t2_record {

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[project]
2-
version = "0.24.3"
2+
version = "0.24.4"
33
name = "tttrlib"
44
requires-python = ">=3.8"
55
description = "Read, write & process time-tagged time-resolved (TTTR) data."

src/TTTR.cpp

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ TTTR::TTTR() :
2727
container_names.insert({std::string("SPC-600_4096"), BH_SPC600_4096_CONTAINER});
2828
container_names.insert({std::string("PHOTON-HDF5"), PHOTON_HDF_CONTAINER});
2929
container_names.insert({std::string("CZ-RAW"), CZ_CONFOCOR3_CONTAINER});
30+
container_names.insert({std::string("SM"), SM_CONTAINER});
3031
header = new TTTRHeader(tttr_container_type);
3132
allocate_memory_for_records(0);
3233
}
@@ -242,6 +243,96 @@ int TTTR::read_hdf_file(const char *fn){
242243
return 0;
243244
}
244245

246+
int TTTR::read_sm_file(const char *filename){
247+
// Function to read a 64-bit big-endian value
248+
249+
// Open the file using fopen
250+
FILE* fp = fopen(filename, "rb");
251+
if (fp == nullptr) {
252+
std::cerr << "Error opening file: " << filename << std::endl;
253+
return 1;
254+
}
255+
256+
if (!fp) {
257+
std::cerr << "Error opening file: " << filename << std::endl;
258+
return 1;
259+
}
260+
261+
// Decode header
262+
header = new TTTRHeader(fp, SM_CONTAINER);
263+
264+
// Skip the header (165 bytes)
265+
size_t HEADER_SIZE = header->header_end;
266+
if (fseek(fp, HEADER_SIZE, SEEK_SET) != 0) {
267+
std::cerr << "Error seeking past the header." << std::endl;
268+
fclose(fp);
269+
return 1;
270+
}
271+
272+
// Determine file size to calculate remaining data size
273+
fseek(fp, 0, SEEK_END);
274+
long fileSize = ftell(fp);
275+
fseek(fp, HEADER_SIZE, SEEK_SET); // Return to start of data after header
276+
277+
if (fileSize < HEADER_SIZE + 26) {
278+
std::cerr << "Error: File is too short to contain expected data and trailing bytes." << std::endl;
279+
fclose(fp);
280+
return 1;
281+
}
282+
283+
size_t dataSize = fileSize - HEADER_SIZE - 26;
284+
285+
// Allocate buffer to hold the data
286+
std::vector<uint8_t> buffer(dataSize);
287+
if (fread(buffer.data(), 1, dataSize, fp) != dataSize) {
288+
std::cerr << "Error reading data from file." << std::endl;
289+
fclose(fp);
290+
return 1;
291+
}
292+
293+
fclose(fp); // Close the file after reading
294+
295+
// Define inline lambda functions for endian conversion
296+
auto readBigEndian64 = [](const uint8_t* data) -> uint64_t {
297+
return (static_cast<uint64_t>(data[0]) << 56) |
298+
(static_cast<uint64_t>(data[1]) << 48) |
299+
(static_cast<uint64_t>(data[2]) << 40) |
300+
(static_cast<uint64_t>(data[3]) << 32) |
301+
(static_cast<uint64_t>(data[4]) << 24) |
302+
(static_cast<uint64_t>(data[5]) << 16) |
303+
(static_cast<uint64_t>(data[6]) << 8) |
304+
(static_cast<uint64_t>(data[7]));
305+
};
306+
307+
auto readBigEndian16 = [](const uint8_t* data) -> uint16_t {
308+
return (static_cast<uint16_t>(data[0]) << 8) | static_cast<uint16_t>(data[1]);
309+
};
310+
311+
// Process the data in 12-byte records
312+
const size_t RECORD_SIZE = 12;
313+
if (buffer.size() % RECORD_SIZE != 0) {
314+
std::cerr << "Error: Data size is not a multiple of record size." << std::endl;
315+
return 1;
316+
}
317+
318+
size_t numRecords = buffer.size() / RECORD_SIZE;
319+
n_valid_events = numRecords;
320+
n_records_in_file = n_valid_events;
321+
allocate_memory_for_records(numRecords);
322+
for (size_t i = 0; i < numRecords; ++i) {
323+
const uint8_t* record = buffer.data() + i * RECORD_SIZE;
324+
325+
// Read and interpret the 64-bit PH time
326+
macro_times[i] = readBigEndian64(record);
327+
328+
// Read and interpret the 16-bit detector
329+
routing_channels[i] = readBigEndian16(record + 8);
330+
}
331+
332+
return 0;
333+
334+
}
335+
245336
int TTTR::read_file(const char *fn, int container_type) {
246337
#ifdef VERBOSE_TTTRLIB
247338
std::clog << "READING TTTR FILE" << std::endl;
@@ -263,7 +354,10 @@ int TTTR::read_file(const char *fn, int container_type) {
263354
fn = filename.c_str();
264355
if (container_type == PHOTON_HDF_CONTAINER) {
265356
read_hdf_file(fn);
266-
} else{
357+
} else if (container_type == SM_CONTAINER){
358+
read_sm_file(fn);
359+
}
360+
else{
267361
fp = fopen(fn, "rb");
268362
header = new TTTRHeader(fp, container_type);
269363
fp_records_begin = header->end();

src/TTTRHeader.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ TTTRHeader::TTTRHeader(
7474
} else if(tttr_container_type == CZ_CONFOCOR3_CONTAINER) {
7575
header_end = read_cz_confocor3_header(fpin, json_data);
7676
tttr_record_type = get_tag(json_data, TTTRRecordType)["value"];
77+
} else if(tttr_container_type == SM_CONTAINER){
78+
header_end = read_sm_header(fpin, json_data);
79+
tttr_record_type = get_tag(json_data, TTTRRecordType)["value"];
7780
}
7881
else if(tttr_container_type == PQ_HT3_CONTAINER){
7982
header_end = read_ht3_header(fpin, json_data);
@@ -144,6 +147,88 @@ size_t TTTRHeader::read_bh132_header(
144147
}
145148

146149

150+
size_t TTTRHeader::read_sm_header(FILE* file, nlohmann::json &j) {
151+
152+
add_tag(j, TTTRRecordType, (int) SM_RECORD_TYPE, tyInt8);
153+
154+
// Helper lambda to read and swap endianness
155+
auto read_and_swap = [&](auto& value) {
156+
fread(&value, sizeof(value), 1, file);
157+
SwapEndian(value);
158+
};
159+
160+
// Helper lambda to read a string with its size
161+
auto read_string = [&](const std::string& tag_name) {
162+
uint32_t size;
163+
read_and_swap(size);
164+
char* buffer = new char[size];
165+
fread(buffer, sizeof(char), size, file);
166+
add_tag(j, tag_name, buffer, tyAnsiString);
167+
delete[] buffer;
168+
};
169+
sm_header_t header; // Use only the 'header' structure
170+
171+
// Read and swap the version
172+
read_and_swap(header.version);
173+
add_tag(j, "version", (int) header.version, tyInt8);
174+
175+
read_string("comment");
176+
read_string("simple");
177+
178+
read_and_swap(header.pointer1);
179+
add_tag(j, "pointer1", (int) header.pointer1, tyInt8);
180+
181+
read_string("file_section_type");
182+
183+
read_and_swap(header.magic1);
184+
add_tag(j, "magic1", (int) header.magic1, tyInt8);
185+
read_and_swap(header.magic2);
186+
add_tag(j, "magic2", (int) header.magic2, tyInt8);
187+
188+
read_string("col1_name");
189+
read_and_swap(header.col1_resolution);
190+
add_tag(j, "col1_resolution", (double) header.col1_resolution, tyFloat8);
191+
read_and_swap(header.col1_offset);
192+
add_tag(j, "col1_offset", (double) header.col1_offset, tyFloat8);
193+
read_and_swap(header.col1_bho);
194+
add_tag(j, "col1_bho", (int) header.col1_bho, tyInt8);
195+
196+
// Read the column 2 information
197+
read_string("col2_name");
198+
read_and_swap(header.col2_resolution);
199+
add_tag(j, "col2_resolution", (double) header.col2_resolution, tyFloat8);
200+
read_and_swap(header.col2_offset);
201+
add_tag(j, "col2_offset", (double) header.col2_offset, tyFloat8);
202+
read_and_swap(header.col2_bho);
203+
add_tag(j, "col2_bho", (int) header.col2_bho, tyInt8);
204+
205+
read_string("col3_name");
206+
read_and_swap(header.col3_resolution);
207+
add_tag(j, "col3_resolution", (double) header.col3_resolution, tyFloat8);
208+
read_and_swap(header.col3_offset);
209+
add_tag(j, "col3_offset", (double) header.col3_offset, tyFloat8);
210+
211+
// Read the number of channels
212+
int32_t num_channels;
213+
read_and_swap(num_channels);
214+
add_tag(j, "num_channels", (int) num_channels, tyInt8);
215+
216+
header.channel_labels.resize(num_channels);
217+
for (uint32_t i = 0; i < num_channels; ++i) {
218+
uint32_t size;
219+
fread(&size, sizeof(size), 1, file);
220+
SwapEndian(size);
221+
fseek(file, size, SEEK_CUR);
222+
}
223+
224+
add_tag(j, TTTRTagGlobRes, (double) header.col2_resolution, tyFloat8);
225+
226+
// Return the current file position, which is the cursor
227+
return ftell(file);
228+
}
229+
230+
231+
147232
size_t TTTRHeader::read_cz_confocor3_header(
148233
std::FILE *fpin,
149234
nlohmann::json &data,
@@ -484,6 +569,7 @@ void TTTRHeader::write_spc132_header(
484569
fclose(fp);
485570
}
486571

572+
487573
void TTTRHeader::write_ptu_header(std::string fn, TTTRHeader* header, std::string modes){
488574
#ifdef VERBOSE_TTTRLIB
489575
std::clog << "TTTRHeader::write_ptu_header" << std::endl;

tools/conda_build_config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
CONDA_BUILD_SYSROOT:
2-
- /opt/MacOSX10.13.sdk # [osx]
2+
- /opt/MacOSX10.9.sdk # [osx]
33
c_compiler:
44
- gcc # [linux]
55
- clang # [osx]

0 commit comments

Comments
 (0)