Skip to content

Commit afb466c

Browse files
committed
Add support for full-depth compression and decompression of int32 timestreams
Add a bit_depth attribute to G3Timestream objects, allowing the flac compression algorithm to enable compression of the full 32 bits per sample, rather than restricting to 24 bits. If 32-bit compression is selected, then int64 timestreams are truncated to int32, and double timestreams are truncated to float prior to compression, and the resulting data type is stored along with the compressed data.
1 parent 9ffbf19 commit afb466c

File tree

6 files changed

+181
-19
lines changed

6 files changed

+181
-19
lines changed

core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ add_spt3g_test(frame_types)
9494
add_spt3g_test(datatypes)
9595
if(FLAC_FOUND)
9696
add_spt3g_test(ts_nanencoding)
97+
add_spt3g_test(ts_flac_compression)
9798
endif()
9899
add_spt3g_test(cuts)
99100
add_spt3g_test(fileio)

core/include/core/G3Timestream.h

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ class G3Timestream : public G3FrameObject {
2727

2828
G3Timestream(const G3Timestream &r);
2929
G3Timestream(std::vector<double>::size_type s = 0, double val = 0) :
30-
units(None), use_flac_(0),
30+
units(None), use_flac_(0), flac_depth_(24),
3131
buffer_((s == 0) ? NULL : new std::vector<double>(s, val)),
3232
data_((s == 0) ? NULL : &(*buffer_)[0]), len_(s),
3333
data_type_(TS_DOUBLE) {}
3434
template <typename Iterator> G3Timestream(Iterator l, Iterator r) :
35-
units(None), use_flac_(0), buffer_(new std::vector<double>(l, r)),
35+
units(None), use_flac_(0), flac_depth_(24),
36+
buffer_(new std::vector<double>(l, r)),
3637
data_(&(*buffer_)[0]), len_(buffer_->size()), data_type_(TS_DOUBLE) {}
3738
virtual ~G3Timestream() {
3839
if (buffer_) delete buffer_;
3940
}
4041

4142
// FLAC compression levels range from 0-9. 0 means do not use FLAC.
4243
void SetFLACCompression(int compression_level);
44+
void SetFLACDepth(int bit_depth);
4345

4446
TimestreamUnits units;
4547
G3Time start, stop;
@@ -105,6 +107,7 @@ class G3Timestream : public G3FrameObject {
105107

106108
double GetSampleRate() const;
107109
uint8_t GetCompressionLevel() const{ return use_flac_; }
110+
uint8_t GetBitDepth() const { return flac_depth_; }
108111

109112
template <class A> void load(A &ar, unsigned v);
110113
template <class A> void save(A &ar, unsigned v) const;
@@ -135,17 +138,19 @@ class G3Timestream : public G3FrameObject {
135138
friend class G3TimestreamPythonHelpers;
136139

137140
uint8_t use_flac_;
141+
uint8_t flac_depth_;
138142

139143
std::vector<double> *buffer_;
140144
std::shared_ptr<void> root_data_ref_;
141145
void *data_;
142146
size_t len_;
143-
enum {
147+
enum DataType {
144148
TS_DOUBLE,
145149
TS_FLOAT,
146150
TS_INT32,
147151
TS_INT64
148-
} data_type_;
152+
};
153+
DataType data_type_;
149154

150155
template<typename T>
151156
struct TimeStreamTypeResolver{
@@ -206,7 +211,9 @@ class G3TimestreamMap : public G3FrameObject,
206211
void SetUnits(G3Timestream::TimestreamUnits units);
207212
/// FLAC compression levels range from 0-9. 0 means do not use FLAC.
208213
uint8_t GetCompressionLevel() const;
214+
uint8_t GetBitDepth() const;
209215
void SetFLACCompression(int compression_level);
216+
void SetFLACDepth(int bit_depth);
210217

211218
// Compact underlying data storage into a contiguous 2D block.
212219
// This invalidates any references to data inside any member
@@ -225,9 +232,10 @@ class G3TimestreamMap : public G3FrameObject,
225232
static G3TimestreamMap
226233
MakeCompact(const std::vector<std::string>& keys, std::size_t n_samples,
227234
G3Time start, G3Time stop, G3Timestream::TimestreamUnits units=G3Timestream::None,
228-
int compression_level=0) {
235+
int compression_level=0, int bit_depth=24) {
229236
std::shared_ptr<SampleType[]> data(new SampleType[n_samples*keys.size()]);
230-
return MakeCompact(keys, n_samples, data, start, stop, units, compression_level);
237+
return MakeCompact(keys, n_samples, data, start, stop, units,
238+
compression_level, bit_depth);
231239
}
232240

233241
/// Construct a map using an existing contiguous 2D block of data as the underlying storage.
@@ -243,16 +251,19 @@ class G3TimestreamMap : public G3FrameObject,
243251
static G3TimestreamMap
244252
MakeCompact(const std::vector<std::string>& keys, std::size_t n_samples,
245253
std::shared_ptr<SampleType[]> data, G3Time start, G3Time stop,
246-
G3Timestream::TimestreamUnits units=G3Timestream::None, int compression_level=0) {
254+
G3Timestream::TimestreamUnits units=G3Timestream::None,
255+
int compression_level=0, int bit_depth=24) {
247256
G3TimestreamMap map;
248-
map.FromBuffer(keys, n_samples, data, start, stop, units, compression_level);
257+
map.FromBuffer(keys, n_samples, data, start, stop, units,
258+
compression_level, bit_depth);
249259
return map;
250260
}
251261

252262
template<typename SampleType>
253263
void FromBuffer(const std::vector<std::string>& keys, std::size_t n_samples,
254264
std::shared_ptr<SampleType[]> data, G3Time start, G3Time stop,
255-
G3Timestream::TimestreamUnits units=G3Timestream::None, int compression_level=0) {
265+
G3Timestream::TimestreamUnits units=G3Timestream::None,
266+
int compression_level=0, int bit_depth=24) {
256267
if(!std::is_sorted(keys.begin(), keys.end()))
257268
throw std::runtime_error("G3TimestreamMap::MakeCompact: keys must be sorted");
258269
const auto data_type=G3Timestream::TimeStreamTypeResolver<SampleType>::type_tag;
@@ -263,6 +274,7 @@ class G3TimestreamMap : public G3FrameObject,
263274
ts->stop = stop;
264275
ts->units = units;
265276
ts->use_flac_ = compression_level;
277+
ts->flac_depth_ = bit_depth;
266278
ts->root_data_ref_ = data;
267279
ts->data_ = data.get() + offset;
268280
ts->data_type_ = data_type;
@@ -283,7 +295,7 @@ namespace cereal {
283295
template <class A> struct specialize<A, G3TimestreamMap, cereal::specialization::member_serialize> {};
284296
}
285297

286-
G3_SERIALIZABLE(G3Timestream, 3);
298+
G3_SERIALIZABLE(G3Timestream, 4);
287299
G3_SERIALIZABLE(G3TimestreamMap, 3);
288300

289301
#endif

core/src/G3Timestream.cxx

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,20 @@ template <class A> void G3Timestream::save(A &ar, unsigned v) const
122122
if (units != Counts && units != None)
123123
log_fatal("Cannot use FLAC on non-counts timestreams");
124124

125+
DataType data_type_out = TS_FLOAT;
126+
if (flac_depth_ == 32) {
127+
if (data_type_ == TS_INT64 || data_type_ == TS_INT32)
128+
data_type_out = TS_INT32;
129+
}
130+
131+
ar & cereal::make_nvp("flac_depth", flac_depth_);
132+
ar & cereal::make_nvp("data_type", data_type_out);
133+
125134
// Copy to 24-bit integers
126135
inbuf.resize(size());
127-
switch (data_type_) {
136+
switch (flac_depth_) {
137+
case 24:
138+
switch (data_type_) {
128139
case TS_DOUBLE:
129140
for (size_t i = 0; i < size(); i++)
130141
inbuf[i] = ((int32_t(((double *)data_)[i]) & 0x00ffffff) << 8) >> 8;
@@ -149,6 +160,31 @@ template <class A> void G3Timestream::save(A &ar, unsigned v) const
149160
break;
150161
default:
151162
log_fatal("Unknown timestream datatype %d", data_type_);
163+
}
164+
break;
165+
case 32:
166+
switch (data_type_) {
167+
case TS_DOUBLE:
168+
for (size_t i = 0; i < size(); i++)
169+
inbuf[i] = int32_t(float(((double *)data_)[i]));
170+
break;
171+
case TS_FLOAT:
172+
for (size_t i = 0; i < size(); i++)
173+
inbuf[i] = int32_t(((float *)data_)[i]);
174+
break;
175+
case TS_INT32:
176+
memcpy(inbuf.data(), data_, size() * sizeof(int32_t));
177+
break;
178+
case TS_INT64:
179+
for (size_t i = 0; i < size(); i++)
180+
inbuf[i] = int32_t(((int64_t *)data_)[i]);
181+
break;
182+
default:
183+
log_fatal("Unknown timestream datatype %d", data_type_);
184+
}
185+
break;
186+
default:
187+
log_fatal("Invalid FLAC bit depth %d", flac_depth_);
152188
}
153189
chanmap[0] = &inbuf[0];
154190

@@ -182,7 +218,7 @@ template <class A> void G3Timestream::save(A &ar, unsigned v) const
182218
FLAC__StreamEncoder *encoder = FLAC__stream_encoder_new();
183219
FLAC__stream_encoder_set_channels(encoder, 1);
184220
// XXX: should assert if high-order 8 bits are not clear
185-
FLAC__stream_encoder_set_bits_per_sample(encoder, 24);
221+
FLAC__stream_encoder_set_bits_per_sample(encoder, flac_depth_);
186222
FLAC__stream_encoder_set_compression_level(encoder, use_flac_);
187223
FLAC__stream_encoder_set_do_md5(encoder, false);
188224
FLAC__stream_encoder_init_stream(encoder,
@@ -262,6 +298,13 @@ template <class A> void G3Timestream::load(A &ar, unsigned v)
262298
if (units != Counts && units != None)
263299
log_fatal("Cannot use FLAC on non-counts timestreams");
264300

301+
if (v >= 4) {
302+
ar & cereal::make_nvp("flac_depth", flac_depth_);
303+
ar & cereal::make_nvp("data_type", data_type_);
304+
} else {
305+
data_type_ = TS_FLOAT;
306+
}
307+
265308
ar & cereal::make_nvp("nanflag", nanflag);
266309
if (nanflag == SomeNan)
267310
ar & cereal::make_nvp("nanmask", nanbuf);
@@ -281,14 +324,22 @@ template <class A> void G3Timestream::load(A &ar, unsigned v)
281324
FLAC__stream_decoder_finish(decoder);
282325
FLAC__stream_decoder_delete(decoder);
283326

327+
// Short-circuit for full-depth INT32.
328+
if (data_type_ == TS_INT32 && flac_depth_ == 32) {
329+
root_data_ref_ = std::shared_ptr<std::vector<int32_t> >(
330+
callback.outbuf);
331+
len_ = callback.outbuf->size();
332+
data_ = &(*callback.outbuf)[0];
333+
return;
334+
}
335+
284336
// Represent data as floats internally. These have the same
285337
// significand depth (24 bits) as the max. bit depth of the
286338
// reference FLAC encoder we use, so no data are lost, and
287339
// allow NaNs, unlike int32_ts, which we try to pull through
288340
// the process to signal missing data.
289341
float *data = new float[callback.outbuf->size()];
290342
root_data_ref_ = std::shared_ptr<float[]>(data);
291-
data_type_ = TS_FLOAT;
292343
len_ = callback.outbuf->size();
293344
data_ = data;
294345

@@ -361,7 +412,7 @@ template <class A> void G3Timestream::load(A &ar, unsigned v)
361412

362413
G3Timestream::G3Timestream(const G3Timestream &r) :
363414
units(r.units), start(r.start), stop(r.stop), use_flac_(r.use_flac_),
364-
len_(r.len_), data_type_(r.data_type_)
415+
flac_depth_(r.flac_depth_), len_(r.len_), data_type_(r.data_type_)
365416
{
366417
// Copy constructor needs to copy data, which always involves
367418
// allocating the internal buffer.
@@ -431,6 +482,14 @@ void G3Timestream::SetFLACCompression(int use_flac)
431482
#endif
432483
}
433484

485+
void G3Timestream::SetFLACDepth(int bit_depth)
486+
{
487+
if (bit_depth != 24 && bit_depth != 32)
488+
log_fatal("Invalid flac bit depth %d", bit_depth);
489+
490+
flac_depth_ = bit_depth;
491+
}
492+
434493
G3Timestream G3Timestream::operator +(const G3Timestream &r) const
435494
{
436495
G3Timestream ret(*this);
@@ -760,12 +819,32 @@ uint8_t G3TimestreamMap::GetCompressionLevel() const
760819
return begin()->second->use_flac_;
761820
}
762821

822+
uint8_t G3TimestreamMap::GetBitDepth() const
823+
{
824+
if (begin() == end())
825+
return 0;
826+
827+
return begin()->second->flac_depth_;
828+
}
829+
763830
void G3TimestreamMap::SetFLACCompression(int compression_level)
764831
{
832+
// Check for errors
833+
begin()->second->SetFLACCompression(compression_level);
834+
765835
for (auto& ts : *this)
766836
ts.second->use_flac_ = compression_level;
767837
}
768838

839+
void G3TimestreamMap::SetFLACDepth(int bit_depth)
840+
{
841+
// Check for errors
842+
begin()->second->SetFLACDepth(bit_depth);
843+
844+
for (auto& ts : *this)
845+
ts.second->flac_depth_ = bit_depth;
846+
}
847+
769848
void G3TimestreamMap::Compactify()
770849
{
771850
// Check if already compacted
@@ -1065,7 +1144,8 @@ struct PyBufferOwner {
10651144
static G3TimestreamMapPtr
10661145
G3TimestreamMap_from_numpy(std::vector<std::string> keys,
10671146
boost::python::object data, G3Time start, G3Time stop,
1068-
G3Timestream::TimestreamUnits units, int compression_level, bool copy_data)
1147+
G3Timestream::TimestreamUnits units, int compression_level,
1148+
bool copy_data, int bit_depth)
10691149
{
10701150
G3TimestreamMapPtr x(new G3TimestreamMap);
10711151

@@ -1112,6 +1192,7 @@ G3TimestreamMap_from_numpy(std::vector<std::string> keys,
11121192
templ.start = start;
11131193
templ.stop = stop;
11141194
templ.SetFLACCompression(compression_level);
1195+
templ.SetFLACDepth(bit_depth);
11151196
if (strcmp(v->v.format, "d") == 0) {
11161197
templ.data_type_ = G3Timestream::TS_DOUBLE;
11171198
} else if (strcmp(v->v.format, "f") == 0) {
@@ -1343,6 +1424,10 @@ PYBINDINGS("core") {
13431424
"Pass True to turn on FLAC compression when serialized. "
13441425
"FLAC compression only works if the timestream is in units of "
13451426
"counts.")
1427+
.def("SetFLACDepth", &G3Timestream::SetFLACDepth,
1428+
"Change the bit depth for FLAC compression. "
1429+
"FLAC compression only works if the timestream is in units of "
1430+
"counts.")
13461431
.def_readwrite("units", &G3Timestream::units,
13471432
"Units of the data in the timestream, stored as one of the "
13481433
"members of core.G3TimestreamUnits.")
@@ -1357,6 +1442,8 @@ PYBINDINGS("core") {
13571442
.add_property("compression_level", &G3Timestream::GetCompressionLevel,
13581443
&G3Timestream::SetFLACCompression, "Level of FLAC compression used for this timestream. "
13591444
"This can only be non-zero if the timestream is in units of counts.")
1445+
.add_property("bit_depth", &G3Timestream::GetBitDepth,
1446+
&G3Timestream::SetFLACDepth, "Bit depth of FLAC compression used for this timestream.")
13601447
.def("_assert_congruence", &G3Timestream::G3TimestreamPythonHelpers::G3Timestream_assert_congruence,
13611448
"log_fatal() if units, length, start, or stop times do not match")
13621449
.def("_cxxslice", &G3Timestream::G3TimestreamPythonHelpers::G3Timestream_getslice, "Slice-only __getitem__")
@@ -1378,7 +1465,7 @@ PYBINDINGS("core") {
13781465
bp::default_call_policies(),
13791466
(bp::arg("keys"), bp::arg("data"), bp::arg("start")=G3Time(0),
13801467
bp::arg("stop")=G3Time(0), bp::arg("units") = G3Timestream::TimestreamUnits::None,
1381-
bp::arg("compression_level") = 0, bp::arg("copy_data") = true)),
1468+
bp::arg("compression_level") = 0, bp::arg("copy_data") = true, bp::arg("bit_depth") = 24)),
13821469
"Create a timestream map from a numpy array or other numeric python iterable. "
13831470
"Each row of the 2D input array will correspond to a single timestream, with "
13841471
"the key set to the correspondingly-indexed entry of <keys>. If <copy_data> "
@@ -1394,6 +1481,10 @@ PYBINDINGS("core") {
13941481
"Pass True to turn on FLAC compression when serialized. "
13951482
"FLAC compression only works if the timestreams are in units of "
13961483
"counts.")
1484+
.def("SetFLACDepth", &G3TimestreamMap::SetFLACDepth,
1485+
"Change the bit depth for FLAC compression. "
1486+
"FLAC compression only works if the timestreams are in units of "
1487+
"counts.")
13971488
.add_property("start", &G3TimestreamMap::GetStartTime,
13981489
&G3TimestreamMap::SetStartTime,
13991490
"Time of the first sample in the time stream")
@@ -1413,6 +1504,9 @@ PYBINDINGS("core") {
14131504
&G3TimestreamMap::SetFLACCompression,
14141505
"Level of FLAC compression used for this timestream map. "
14151506
"This can only be non-zero if the timestream is in units of counts.")
1507+
.add_property("bit_depth", &G3TimestreamMap::GetBitDepth,
1508+
&G3TimestreamMap::SetFLACDepth,
1509+
"Bit depth of FLAC compression used for this timestream map.")
14161510
;
14171511
register_pointer_conversions<G3TimestreamMap>();
14181512

core/tests/G3TimestreamMapTest.cxx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ TEST(MakeCompactCompressed){
5555
std::vector<std::string> keys={"a","b","c","d"};
5656
G3Time t1(0), t2(3);
5757
const std::size_t nSamples=4;
58-
G3TimestreamMap tsm=G3TimestreamMap::MakeCompact<int32_t>(keys, nSamples, t1, t2, G3Timestream::Counts, 6);
58+
G3TimestreamMap tsm=G3TimestreamMap::MakeCompact<int32_t>(keys, nSamples, t1, t2, G3Timestream::Counts, 6, 32);
5959

60-
for(const auto& item : tsm)
60+
for(const auto& item : tsm) {
6161
ENSURE_EQUAL(item.second->GetCompressionLevel(), 6, "All timestreams should be set to use compression");
62+
ENSURE_EQUAL(item.second->GetBitDepth(), 32, "All timestreams should be set to use 32-bit depth");
63+
}
6264
}
6365

6466
template<typename SampleType>
@@ -144,8 +146,11 @@ TEST(SetCompression){
144146
G3TimestreamMap tsm=G3TimestreamMap::MakeCompact<double>(keys, nSamples, t1, t2);
145147

146148
unsigned int compressionLevel=5;
149+
unsigned int bitDepth=32;
147150
tsm.SetFLACCompression(5);
151+
tsm.SetFLACDepth(bitDepth);
148152
for(const auto& key : keys){
149153
ENSURE_EQUAL(tsm[key]->GetCompressionLevel(), compressionLevel, "SetFLACCompression should set the compression level for each timestream");
154+
ENSURE_EQUAL(tsm[key]->GetBitDepth(), bitDepth, "SetFLACDepth should set the bit depth for each timestream");
150155
}
151156
}

core/tests/timestream_2d.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@
6464
# Test setting secondary properties at the time of direct construction
6565
tsm2 = core.G3TimestreamMap(tsm.keys(), buffer1d, start=start, stop=stop,
6666
units=core.G3TimestreamUnits.Counts,
67-
compression_level=3)
67+
compression_level=3, bit_depth=32)
6868
for ts in tsm2.values():
6969
assert ts.start == start
7070
assert ts.stop == stop
7171
assert ts.units == core.G3TimestreamUnits.Counts
7272
assert ts.compression_level == 3
73+
assert ts.bit_depth == 32

0 commit comments

Comments
 (0)