Skip to content

Commit 5196a88

Browse files
authored
Add detrending option (#150)
* Add option to detrend channels * Optimize
1 parent e27c8c9 commit 5196a88

23 files changed

Lines changed: 1347 additions & 125 deletions

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ qt_add_library(sigviewer_objects OBJECT
144144
src/file_handling/biosig_reader.h
145145
src/file_handling/biosig_writer.cpp
146146
src/file_handling/biosig_writer.h
147+
src/file_handling/channel_manager_proxy.cpp
148+
src/file_handling/channel_manager_proxy.h
149+
src/file_handling/detrend_channel_manager.cpp
150+
src/file_handling/detrend_channel_manager.h
147151
src/file_handling/file_channel_manager.cpp
148152
src/file_handling/file_channel_manager.h
149153
src/file_handling/event_manager.cpp
@@ -197,6 +201,8 @@ qt_add_library(sigviewer_objects OBJECT
197201
src/gui/commands/adapt_event_view_gui_command.h
198202
src/gui/commands/close_file_gui_command.cpp
199203
src/gui/commands/close_file_gui_command.h
204+
src/gui/commands/detrend_gui_command.cpp
205+
src/gui/commands/detrend_gui_command.h
200206
src/gui/commands/event_editing_gui_command.cpp
201207
src/gui/commands/event_editing_gui_command.h
202208
src/gui/commands/help_gui_command.cpp

src/base/fixed_data_block.cpp

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,22 @@ float32 const& FixedDataBlock::operator[] (size_t index) const
4949
//! Get the minimal value in a data block, excluding any NANs
5050
float32 FixedDataBlock::getMin () const
5151
{
52+
size_t len = size ();
5253
float32 min = 0;
53-
for (auto const elem : *data_)
54+
for (size_t i = 0; i < len; ++i)
5455
{
55-
if (!std::isnan(elem))
56+
float32 v = (*data_)[start_index_ + i];
57+
if (!std::isnan (v))
5658
{
57-
min = elem;
59+
min = v;
5860
break;
5961
}
6062
}
61-
62-
for (auto const elem : *data_)
63+
for (size_t i = 0; i < len; ++i)
6364
{
64-
if (!std::isnan(elem))
65-
{
66-
if (elem < min)
67-
min = elem;
68-
}
65+
float32 v = (*data_)[start_index_ + i];
66+
if (!std::isnan (v) && v < min)
67+
min = v;
6968
}
7069
return min;
7170
}
@@ -74,23 +73,22 @@ float32 FixedDataBlock::getMin () const
7473
//! Get the maximal value in a data block, excluding any NANs
7574
float32 FixedDataBlock::getMax () const
7675
{
76+
size_t len = size ();
7777
float32 max = 0;
78-
for (auto const elem : *data_)
78+
for (size_t i = 0; i < len; ++i)
7979
{
80-
if (!std::isnan(elem))
80+
float32 v = (*data_)[start_index_ + i];
81+
if (!std::isnan (v))
8182
{
82-
max = elem;
83+
max = v;
8384
break;
8485
}
8586
}
86-
87-
for (auto const elem : *data_)
87+
for (size_t i = 0; i < len; ++i)
8888
{
89-
if (!std::isnan(elem))
90-
{
91-
if (elem > max)
92-
max = elem;
93-
}
89+
float32 v = (*data_)[start_index_ + i];
90+
if (!std::isnan (v) && v > max)
91+
max = v;
9492
}
9593
return max;
9694
}

src/file_context.cpp

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ FileContext::FileContext (QString const& file_path_and_name,
2121
file_path_and_name_ (file_path_and_name),
2222
event_manager_ (event_manager),
2323
channel_manager_ (channel_manager),
24+
detrend_manager_ (nullptr),
25+
proxy_manager_ (new ChannelManagerProxy (channel_manager)),
26+
detrend_enabled_ (false),
27+
detrend_cutoff_hz_ (0.0),
2428
basic_header_ (header)
2529
{
2630
connect (event_manager_.data(), SIGNAL(changed()), SLOT(setAsChanged()));
@@ -30,6 +34,8 @@ FileContext::FileContext (QString const& file_path_and_name,
3034
FileContext::~FileContext ()
3135
{
3236
qDebug () << "deleting FileContext";
37+
delete proxy_manager_;
38+
delete detrend_manager_;
3339
delete channel_manager_;
3440
}
3541

@@ -87,13 +93,73 @@ QSharedPointer<EventManager> FileContext::getEventManager ()
8793
//-----------------------------------------------------------------------------
8894
ChannelManager const& FileContext::getChannelManager () const
8995
{
90-
return *channel_manager_;
96+
return *proxy_manager_;
9197
}
9298

9399
//-----------------------------------------------------------------------------
94100
ChannelManager& FileContext::getChannelManager ()
95101
{
96-
return *channel_manager_;
102+
return *proxy_manager_;
103+
}
104+
105+
//-------------------------------------------------------------------------
106+
void FileContext::precomputeDetrendChannel (double cutoff_hz, ChannelID id)
107+
{
108+
if (!detrend_manager_ || detrend_manager_->hpCutoff () != cutoff_hz)
109+
{
110+
delete detrend_manager_;
111+
detrend_manager_ = new DetrendChannelManager (channel_manager_, cutoff_hz);
112+
detrend_cutoff_hz_ = cutoff_hz;
113+
}
114+
// A getData(id, 0, 1) call is enough to trigger and cache processedChannel()
115+
// which also populates the per-channel min/max cache.
116+
detrend_manager_->getData (id, 0, 1);
117+
}
118+
119+
//-------------------------------------------------------------------------
120+
void FileContext::ensureDetrendManager (double cutoff_hz)
121+
{
122+
if (!detrend_manager_ || detrend_manager_->hpCutoff () != cutoff_hz)
123+
{
124+
delete detrend_manager_;
125+
detrend_manager_ = new DetrendChannelManager (channel_manager_, cutoff_hz);
126+
detrend_cutoff_hz_ = cutoff_hz;
127+
}
128+
}
129+
130+
//-------------------------------------------------------------------------
131+
void FileContext::precomputeDetrendChannelFromRaw (ChannelID id,
132+
QSharedPointer<DataBlock const> raw)
133+
{
134+
// detrend_manager_ must already exist (call ensureDetrendManager first).
135+
if (detrend_manager_)
136+
detrend_manager_->precomputeFromRawData (id, raw);
137+
}
138+
139+
//-------------------------------------------------------------------------
140+
bool FileContext::setDetrendEnabled (bool enabled, double hp_cutoff_hz)
141+
{
142+
detrend_enabled_ = enabled;
143+
detrend_cutoff_hz_ = hp_cutoff_hz;
144+
145+
bool rebuilt = false;
146+
if (enabled)
147+
{
148+
// Only rebuild if parameters changed or no manager exists yet.
149+
if (!detrend_manager_ || detrend_manager_->hpCutoff () != hp_cutoff_hz)
150+
{
151+
delete detrend_manager_;
152+
detrend_manager_ = new DetrendChannelManager (channel_manager_, hp_cutoff_hz);
153+
rebuilt = true;
154+
}
155+
proxy_manager_->setTarget (detrend_manager_);
156+
}
157+
else
158+
{
159+
proxy_manager_->setTarget (channel_manager_);
160+
// Keep detrend_manager_ alive so toggling back on reuses the cache.
161+
}
162+
return rebuilt;
97163
}
98164

99165
//-------------------------------------------------------------------------

src/file_context.h

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include "file_handling/event_manager.h"
1010
#include "file_handling/basic_header.h"
1111
#include "file_handling/channel_manager.h"
12+
#include "file_handling/channel_manager_proxy.h"
13+
#include "file_handling/detrend_channel_manager.h"
14+
#include "base/data_block.h"
1215
#include "gui/signal_visualisation_model.h"
1316
#include "base/file_states.h"
1417

@@ -74,6 +77,38 @@ class FileContext : public QObject
7477
//-------------------------------------------------------------------------
7578
ChannelManager& getChannelManager ();
7679

80+
//-------------------------------------------------------------------------
81+
/// Enable or disable offset removal (mean subtraction + optional FIR
82+
/// high-pass filter). A high-pass cutoff of 0.0 means mean-only.
83+
/// Returns true if the filter was (re)built, false if the existing cache
84+
/// was reused (e.g. toggling off then back on with the same parameters).
85+
bool setDetrendEnabled (bool enabled, double hp_cutoff_hz = 0.0);
86+
87+
//-------------------------------------------------------------------------
88+
/// Pre-warms the detrend manager for a single channel during file load.
89+
/// Creates the DetrendChannelManager if it does not yet exist or if the
90+
/// cutoff changed. Does NOT enable detrend — user still toggles it on.
91+
void precomputeDetrendChannel (double cutoff_hz, ChannelID id);
92+
93+
//-------------------------------------------------------------------------
94+
/// Ensures the DetrendChannelManager exists with the given cutoff.
95+
/// Must be called on the main thread before any parallel
96+
/// precomputeDetrendChannelFromRaw() calls.
97+
void ensureDetrendManager (double cutoff_hz);
98+
99+
//-------------------------------------------------------------------------
100+
/// Pre-compute a single channel from already-read raw data.
101+
/// Thread-safe — multiple channels can be processed concurrently once
102+
/// ensureDetrendManager() has been called on the main thread.
103+
void precomputeDetrendChannelFromRaw (ChannelID id,
104+
QSharedPointer<DataBlock const> raw);
105+
106+
//-------------------------------------------------------------------------
107+
bool isDetrendEnabled () const { return detrend_enabled_; }
108+
109+
//-------------------------------------------------------------------------
110+
double getDetrendCutoffHz () const { return detrend_cutoff_hz_; }
111+
77112
//-------------------------------------------------------------------------
78113
QSharedPointer<BasicHeader> getHeader () {return basic_header_;}
79114

@@ -103,7 +138,11 @@ public slots:
103138
FileState state_;
104139
QString file_path_and_name_;
105140
QSharedPointer<EventManager> event_manager_;
106-
ChannelManager* channel_manager_;
141+
ChannelManager* channel_manager_; ///< raw source (owned)
142+
DetrendChannelManager* detrend_manager_; ///< detrend wrapper (owned, may be null)
143+
ChannelManagerProxy* proxy_manager_; ///< stable pointer given to views (owned)
144+
bool detrend_enabled_;
145+
double detrend_cutoff_hz_;
107146
QSharedPointer<BasicHeader> basic_header_;
108147
QSharedPointer<SignalVisualisationModel> main_signal_vis_model_;
109148
};

src/file_handling/channel_manager.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,25 @@ float64 ChannelManager::getMaxValue (ChannelID channel_id) const
107107
}
108108

109109
//-------------------------------------------------------------------------
110+
void ChannelManager::invalidateMinMaxCache ()
111+
{
112+
min_max_initialized_ = false;
113+
min_values_.clear ();
114+
max_values_.clear ();
115+
offsets_.clear ();
116+
}
117+
118+
void ChannelManager::setChannelMinMax (ChannelID id, float64 min_val, float64 max_val)
119+
{
120+
min_values_[id] = min_val;
121+
max_values_[id] = max_val;
122+
}
123+
124+
void ChannelManager::markMinMaxInitialized ()
125+
{
126+
min_max_initialized_ = true;
127+
}
128+
110129
void ChannelManager::initMinMax () const
111130
{
112131
if (min_max_initialized_)

src/file_handling/channel_manager.h

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,46 @@ class ChannelManager
5757
virtual float64 getSampleRate () const = 0;
5858

5959
//-------------------------------------------------------------------------
60-
void addDownsampledMinMaxVersion (ChannelID id, QSharedPointer<DataBlock const> min,
60+
virtual void addDownsampledMinMaxVersion (ChannelID id, QSharedPointer<DataBlock const> min,
6161
QSharedPointer<DataBlock const> max, unsigned factor);
6262

6363
//-------------------------------------------------------------------------
64-
unsigned getNearestDownsamplingFactor (ChannelID id, unsigned factor) const;
64+
virtual unsigned getNearestDownsamplingFactor (ChannelID id, unsigned factor) const;
6565

6666
//-------------------------------------------------------------------------
67-
QSharedPointer<DataBlock const> getDownsampledMin (ChannelID id, unsigned factor) const;
67+
virtual QSharedPointer<DataBlock const> getDownsampledMin (ChannelID id, unsigned factor) const;
6868

6969
//-------------------------------------------------------------------------
70-
QSharedPointer<DataBlock const> getDownsampledMax (ChannelID id, unsigned factor) const;
70+
virtual QSharedPointer<DataBlock const> getDownsampledMax (ChannelID id, unsigned factor) const;
71+
72+
//-------------------------------------------------------------------------
73+
/// Clears the cached min/max values so they are recomputed on the next
74+
/// access. Call this when the underlying data source changes.
75+
void invalidateMinMaxCache ();
76+
77+
//-------------------------------------------------------------------------
78+
/// Pre-populate one channel's min/max entry. Call markMinMaxInitialized()
79+
/// once all channels have been set to skip the lazy per-channel file scan.
80+
void setChannelMinMax (ChannelID id, float64 min_val, float64 max_val);
81+
82+
//-------------------------------------------------------------------------
83+
/// Mark the min/max cache as fully built so initMinMax() is never called.
84+
void markMinMaxInitialized ();
7185

7286
//-------------------------------------------------------------------------
7387
float64 getValueRange (std::set<ChannelID> const& channels) const;
7488

7589
//-------------------------------------------------------------------------
76-
float64 getMinValue (std::set<ChannelID> const& channels) const;
90+
virtual float64 getMinValue (std::set<ChannelID> const& channels) const;
7791

7892
//-------------------------------------------------------------------------
79-
float64 getMaxValue (std::set<ChannelID> const& channels) const;
93+
virtual float64 getMaxValue (std::set<ChannelID> const& channels) const;
8094

8195
//-------------------------------------------------------------------------
82-
float64 getMinValue (ChannelID channel_id) const;
96+
virtual float64 getMinValue (ChannelID channel_id) const;
8397

8498
//-------------------------------------------------------------------------
85-
float64 getMaxValue (ChannelID channel_id) const;
99+
virtual float64 getMaxValue (ChannelID channel_id) const;
86100

87101
//-------------------------------------------------------------------------
88102
void setXAxisUnitLabel (QString const& label) {x_axis_unit_label_ = label;}

0 commit comments

Comments
 (0)