[med-svn] [SCM] aghermann branch, master, updated. 99b1d5a023eee9df74b0e0d6f894516fc79435ad

Andrei Zavada johnhommer at gmail.com
Sun Jul 7 23:04:02 UTC 2013


The following commit has been merged in the master branch:
commit 22a2a688c605d0e96da0e75a84331697c223da5b
Author: Andrei Zavada <johnhommer at gmail.com>
Date:   Mon Jun 24 02:41:09 2013 +0300

    WIP (sigfile::CTSVFile)

diff --git a/src/libsigfile/Makefile.am b/src/libsigfile/Makefile.am
index ac733a1..9fad844 100644
--- a/src/libsigfile/Makefile.am
+++ b/src/libsigfile/Makefile.am
@@ -16,6 +16,8 @@ libsigfile_la_SOURCES := \
 	edf.cc \
 	edf-io.cc \
 	edf.hh \
+	tsv.cc \
+	tsv.hh \
 	page.cc \
 	page.hh
 
@@ -41,6 +43,7 @@ BUILT_SOURCES := \
 	source-base.hh.gch \
 	source.hh.gch \
 	edf.hh.gch \
+	tsv.hh.gch \
 	page.hh.gch
 %.hh.gch: %.hh
 	$(CXXCOMPILE) -c $<
diff --git a/src/libsigfile/edf-io.cc b/src/libsigfile/edf-io.cc
index 8aa3295..4253f87 100644
--- a/src/libsigfile/edf-io.cc
+++ b/src/libsigfile/edf-io.cc
@@ -146,6 +146,7 @@ get_region_filtered_smpl( const int h,
 					   58, 62, 1, true);
 	    break;
 	case SFilterPack::TNotchFilter::none:
+	default:
 	    break;
 	}
 
diff --git a/src/libsigfile/edf.cc b/src/libsigfile/edf.cc
index 5a54285..5aee4ee 100644
--- a/src/libsigfile/edf.cc
+++ b/src/libsigfile/edf.cc
@@ -35,11 +35,14 @@ using agh::str::tokens_trimmed;
 
 using sigfile::CEDFFile;
 
+
+// every setter is special
 int
 CEDFFile::
 set_patient_id( const string& s)
 {
 	memcpy( header.patient_id, pad( s, 80).c_str(), 80);
+	_patient_id = s;
 	return s.size() > 80;
 }
 
@@ -48,6 +51,8 @@ CEDFFile::
 set_recording_id( const string& s)
 {
 	memcpy( header.recording_id, pad( s, 80).c_str(), 80);
+	_recording_id = s;
+	// maybe let _session and _episode be assigned, too?
 	return s.size() > 80;
 }
 
@@ -56,6 +61,7 @@ CEDFFile::
 set_episode( const string& s)
 {
 	_episode.assign( s);
+	// aha
 	return set_recording_id( (_session + '/' + _episode).c_str());
 }
 
@@ -69,8 +75,10 @@ set_session( const string& s)
 
 int
 CEDFFile::
-set_reserved( const string&s)
+set_reserved( const string& s)
 {
+	fprintf( stderr, "You just voided your warranty: Writing to \"reserved\" field in EDF header:\n%s\n", s.c_str());
+	_recording_id = s;
 	memcpy( header.reserved, pad( s, 44).c_str(), 44);
 	return s.size() > 44;
 }
@@ -115,17 +123,13 @@ CEDFFile (const string& fname_, const int flags_)
 	{
 		struct stat stat0;
 		int stst = stat( fname_.c_str(), &stat0);
-		if ( stst == -1 ) {
-			_status |= TStatus::sysfail;
-			throw invalid_argument (explain_edf_status(_status));
-		}
+		if ( stst == -1 )
+			throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 		_fsize = stat0.st_size;
 	}
 	_fd = open( fname_.c_str(), O_RDWR);
-	if ( _fd == -1 ) {
-		_status |= TStatus::sysfail;
-		throw invalid_argument (explain_edf_status(_status));
-	}
+	if ( _fd == -1 )
+		throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 
       // mmap
 	_mmapping =
@@ -143,7 +147,7 @@ CEDFFile (const string& fname_, const int flags_)
 		if ( not (flags_ & no_field_consistency_check) ) {
 			close( _fd);
 			munmap( _mmapping, _fsize);
-			throw invalid_argument (explain_edf_status(_status));
+			throw invalid_argument (explain_status(_status));
 		} else
 			fprintf( stderr, "CEDFFile::CEDFFile(\"%s\") Warning: parse header failed, but proceeding anyway\n", fname_.c_str());
 	}
@@ -162,7 +166,7 @@ CEDFFile (const string& fname_, const int flags_)
 			close( _fd);
 			munmap( _mmapping, _fsize);
 			_status |= file_truncated;
-			throw invalid_argument (explain_edf_status(_status));
+			throw invalid_argument (explain_status(_status));
 		} else if ( _fsize > expected_fsize ) {
 			_status |= trailing_junk;
 			fprintf( stderr, "CEDFFile::CEDFFile(\"%s\") Warning: %zu bytes of trailing junk\n",
@@ -173,71 +177,10 @@ CEDFFile (const string& fname_, const int flags_)
 	_extract_embedded_annotations();
 
       // ancillary files:
-	if ( flags_ & sigfile::CTypedSource::no_ancillary_files )
+	if ( flags_ & sigfile::CSource::no_ancillary_files )
 		;
-	else {
-	      // 1. artifacts, per signal
-		for ( auto &H : channels ) {
-			ifstream thomas (make_fname_artifacts( H.ucd));
-			if ( not thomas.good() )
-				continue;
-
-			while ( !thomas.eof() ) {
-				double aa = NAN, az = NAN;
-				thomas >> aa >> az;
-				if ( not isfinite(aa) || not isfinite(az) )
-					break;
-				H.artifacts.mark_artifact( aa, az);
-			}
-		}
-
-	      // 2. annotations, per signal
-		for ( auto &H : channels ) {
-			ifstream fd (make_fname_annotations( H.ucd));
-			if ( not fd.good() )
-				continue;
-			while ( fd.good() and not fd.eof() ) {
-				int type = -1;
-				double aa = NAN, az = NAN;
-				string an;
-				fd >> type >> aa >> az;
-				getline( fd, an, EOA);
-				if ( isfinite(aa) and isfinite(az) and
-				     aa < az and az < n_data_records * data_record_size
-				     and type < SAnnotation::TType_total and type >= 0 )
-					H.annotations.emplace_back(
-						aa, az,
-						trim(an),
-						(SAnnotation::TType)type);
-				else {
-					fprintf( stderr, "Bad annotation: (%d %g %g %50s)\n", type, aa, az, an.c_str());
-					break;
-				}
-			}
-			H.annotations.sort();
-		}
-
-	      // 3. filters
-		{
-			ifstream thomas (make_fname_filters(fname_));
-			if ( !thomas.fail() )
-				for ( auto &I : channels ) {
-					int ol = -1, oh = -1, nf = -1;
-					float fl = 0., fh = 0.;
-					thomas >> fl >> ol
-					       >> fh >> oh >> nf;
-					if ( ol > 0 && oh > 0 && ol < 5 && oh < 5
-					     && fl >= 0. && fh >= 0.
-					     && nf >= 0 && nf <= 2 ) {
-						I.filters.low_pass_cutoff = fl;
-						I.filters.low_pass_order  = ol;
-						I.filters.high_pass_cutoff = fh;
-						I.filters.high_pass_order  = oh;
-						I.filters.notch_filter = (SFilterPack::TNotchFilter)nf;
-					}
-				}
-		}
-	}
+	else
+		load_ancillary_files();
 }
 
 
@@ -254,10 +197,8 @@ CEDFFile (const string& fname_, const TSubtype subtype_, const int flags_,
 	_subtype (subtype_)
 {
 	_fd = open( fname_.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP);
-	if ( _fd == -1 ) {
-		_status |= TStatus::sysfail;
-		throw invalid_argument ("CEDFFile::CEDFFile(): file open error");
-	}
+	if ( _fd == -1 )
+		throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 
 	header_length = 256 + (channels_.size() * 256);
 	size_t total_samplerate = 0;
@@ -266,10 +207,8 @@ CEDFFile (const string& fname_, const TSubtype subtype_, const int flags_,
 
 	_fsize = header_length + 2 * total_samplerate * data_record_size * n_data_records;
 	// extend
-	if ( lseek( _fd, _fsize-1, SEEK_SET) == -1 || write( _fd, "\0", 1) != 1 ) {
-		_status |= TStatus::sysfail;
-		throw invalid_argument ("CEDFFile::CEDFFile(): file write error");
-	}
+	if ( lseek( _fd, _fsize-1, SEEK_SET) == -1 || write( _fd, "\0", 1) != 1 )
+		throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 
 //	size_t sys_page_size = (size_t) sysconf( _SC_PAGESIZE);
 	_mmapping =
@@ -280,7 +219,7 @@ CEDFFile (const string& fname_, const TSubtype subtype_, const int flags_,
 		      0);
 	if ( _mmapping == (void*)-1 ) {
 		close( _fd);
-		throw length_error ("CEDFFile::CEDFFile(): mmap error");
+		throw invalid_argument (explain_status(_status |= TStatus::mmap_error));
 	}
 
       // fill out some essential header fields
@@ -354,7 +293,7 @@ set_digital_range( const int16_t m, const int16_t M)
 /*
 size_t
 CEDFFile::
-resize( const size_t new_records)
+resize_records( const size_t new_records)
 {
 	size_t total_samples_per_record = 0;
 	for ( auto& H : channels )
@@ -405,7 +344,10 @@ CEDFFile (CEDFFile&& rv)
 	_start_time = rv._start_time;
 	_end_time   = rv._end_time;
 
-	swap( _patient_id, rv._patient_id);
+	swap( _patient_id,   rv._patient_id);
+	swap( _recording_id, rv._recording_id);
+	swap( _reserved,     rv._reserved);
+
 	swap( _episode,    rv._episode);
 	swap( _session,    rv._session);
 
@@ -430,9 +372,6 @@ CEDFFile::
 	if ( _mmapping != (void*)-1 ) {
 		munmap( _mmapping, _fsize);
 		close( _fd);
-
-		if ( not (flags() & sigfile::CTypedSource::no_ancillary_files) )
-			write_ancillary_files();
 	}
 }
 
@@ -441,38 +380,6 @@ CEDFFile::
 
 
 
-void
-CEDFFile::
-write_ancillary_files()
-{
-	for ( auto &I : channels ) {
-		if ( not I.artifacts().empty() ) {
-			ofstream thomas (make_fname_artifacts( I.ucd), ios_base::trunc);
-			if ( thomas.good() )
-				for ( auto &A : I.artifacts() )
-					thomas << A.a << ' ' << A.z << endl;
-		} else
-			if ( unlink( make_fname_artifacts( I.ucd).c_str()) ) {}
-
-		if ( not I.annotations.empty() ) {
-			ofstream thomas (make_fname_annotations( I.ucd), ios_base::trunc);
-			for ( auto &A : I.annotations )
-				thomas << (int)A.type << ' ' << A.span.a << ' ' << A.span.z << ' ' << A.label << EOA << endl;
-		} else
-			if ( unlink( make_fname_annotations( I.ucd).c_str()) ) {}
-	}
-	ofstream thomas (make_fname_filters( filename()), ios_base::trunc);
-	if ( thomas.good() )
-		for ( auto &I : channels )
-			thomas << I.filters.low_pass_cutoff << ' ' << I.filters.low_pass_order << ' '
-			       << I.filters.high_pass_cutoff << ' ' << I.filters.high_pass_order << ' '
-			       << (int)I.filters.notch_filter << endl;
-}
-
-
-
-
-
 
 
 
@@ -531,8 +438,6 @@ _get_next_field( char *&field, const size_t fld_size) throw (TStatus)
 	return field;
 }
 
-size_t	CEDFFile::max_channels = 256;
-
 int
 CEDFFile::
 _parse_header()
@@ -573,7 +478,7 @@ _parse_header()
 
 		if ( !header_length || !n_data_records || !data_record_size || !n_channels ) {
 			_status |= bad_numfld;
-			if ( not (flags() & no_field_consistency_check) )
+			if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 				return -2;
 		}
 		if ( n_channels == 0 )  {
@@ -582,6 +487,7 @@ _parse_header()
 		}
 
 		_patient_id = trim( string (header.patient_id, 80));
+		_recording_id = trim( string (header.patient_id, 80));
 
 	      // sub-parse patient_id into SSubjectId struct
 		{
@@ -641,6 +547,7 @@ _parse_header()
 			}
 		}
 
+	      // parse times
 		{
 			struct tm ts;
 			char *p;
@@ -650,14 +557,14 @@ _parse_header()
 			p = strptime( tmp.c_str(), "%d.%m.%y", &ts);
 			if ( p == NULL || *p != '\0' ) {
 				_status |= date_unparsable;
-				if ( not (flags() & no_field_consistency_check) )
+				if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 					return -2;
 			}
 			tmp = {string (header.recording_time, 8)};
 			p = strptime( tmp.c_str(), "%H.%M.%S", &ts);
 			if ( p == NULL || *p != '\0' ) {
 				_status |= time_unparsable;
-				if ( not (flags() & no_field_consistency_check) )
+				if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 					return -2;
 			}
 
@@ -670,9 +577,12 @@ _parse_header()
 				_end_time = _start_time + n_data_records * data_record_size;
 		}
 
+	      // assign "reserved"
+		_reserved = trim( string (header.reserved, 44));
+
 		if ( n_channels > max_channels ) {
 			_status |= bad_numfld;
-			if ( not (flags() & no_field_consistency_check) )
+			if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 				return -2;
 		} else {
 			channels.resize( n_channels);
@@ -718,7 +628,7 @@ _parse_header()
 				if ( sscanf( H.header.physical_min, "%8lg",
 					     &H.physical_min) != 1 ) {
 					_status |= bad_numfld;
-					if ( not (flags() & no_field_consistency_check) )
+					if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 						return -2;
 				}
 			}
@@ -729,7 +639,7 @@ _parse_header()
 				if ( sscanf( H.header.physical_max, "%8lg",
 					     &H.physical_max) != 1 ) {
 					_status |= bad_numfld;
-					if ( not (flags() & no_field_consistency_check) )
+					if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 						return -2;
 				}
 			}
@@ -741,7 +651,7 @@ _parse_header()
 				if ( sscanf( H.header.digital_min, "%8d",
 					     &H.digital_min) != 1 ) {
 					_status |= bad_numfld;
-					if ( not (flags() & no_field_consistency_check) )
+					if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 						return -2;
 				}
 			}
@@ -752,7 +662,7 @@ _parse_header()
 				if ( sscanf( H.header.digital_max, "%8d",
 					     &H.digital_max) != 1 ) {
 					_status |= bad_numfld;
-					if ( not (flags() & no_field_consistency_check) )
+					if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 						return -2;
 				}
 			}
@@ -768,7 +678,7 @@ _parse_header()
 					strtoul( t.c_str(), &tail, 10);
 				if ( tail == NULL || *tail != '\0' ) {
 					_status |= bad_numfld;
-					if ( not (flags() & no_field_consistency_check) )
+					if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 						return -2;
 				}
 			}
@@ -781,7 +691,7 @@ _parse_header()
 		return -1;
 	} catch (invalid_argument ex) {
 		_status |= bad_numfld;
-		if ( not (flags() & no_field_consistency_check) )
+		if ( not (flags() & sigfile::CSource::no_field_consistency_check) )
 			return -3;
 	}
 
@@ -995,7 +905,7 @@ details( const int which) const
 
 string
 CEDFFile::
-explain_edf_status( const int status)
+explain_status( const int status)
 {
 	list<string> recv;
 	if ( status & sysfail )
@@ -1036,6 +946,9 @@ explain_edf_status( const int status)
 		recv.emplace_back( "* Extra subfields in PatientId");
 	if ( status & recognised_channel_conflicting_type )
 		recv.emplace_back( "* Explicitly specified signal type does not match type of known channel name");
+	if ( status & mmap_error )
+		recv.emplace_back( "* mmap error");
+
 	return join(recv, "\n");
 }
 
diff --git a/src/libsigfile/edf.hh b/src/libsigfile/edf.hh
index c917f29..a8ba074 100644
--- a/src/libsigfile/edf.hh
+++ b/src/libsigfile/edf.hh
@@ -9,8 +9,8 @@
  *         License:  GPL
  */
 
-#ifndef _SIGFILE_EDF_H
-#define _SIGFILE_EDF_H
+#ifndef AGH_SIGFILE_EDF_H_
+#define AGH_SIGFILE_EDF_H_
 
 #include <cinttypes>
 #include <cstring>
@@ -77,7 +77,6 @@ class CEDFFile
 	enum TFlags {
 		no_mmap				= 1<<3,
 		no_cache			= 1<<4, // just considering
-		no_field_consistency_check	= 1<<5,
 	};
 	// open existing
 	CEDFFile (const string& fname, int flags = 0);
@@ -93,21 +92,19 @@ class CEDFFile
       // interface
 	// status
 	string explain_status() const
-		{ return explain_edf_status( _status); }
+		{ return explain_status( _status); }
 
 	// identification
-	const char* filename() const
-		{ return _filename.c_str(); }
 	const char* patient_id() const
 		{ return _patient_id.c_str(); }
 	const char* recording_id() const
-		{ return header.recording_id; }
-	const char* comment() const
-		{ return header.reserved; }
+		{ return _recording_id.c_str(); }
 	const char* episode() const
 		{ return _episode.c_str(); }
 	const char* session() const
 		{ return _session.c_str(); }
+	const char* comment() const
+		{ return _reserved.c_str(); }
 
 	// times
 	time_t start_time() const
@@ -122,9 +119,9 @@ class CEDFFile
 	int set_recording_id( const string&);
 	int set_episode( const string&);
 	int set_session( const string&);
-	int set_reserved( const string&);
-	int set_comment( const string& s)
-		{ return set_reserved( s); }
+	int set_comment( const string&) // note that there's no room in EDF header to store anything useful
+		{ return 1; }
+	int set_reserved( const string&); // but you can clobber "reserved" field if you must
 
 	int set_start_time( time_t);
 	// channels
@@ -249,18 +246,15 @@ class CEDFFile
 
       // adjust capacity
 	size_t
-	resize( size_t new_records);
+	resize_records( size_t new_records);
+	// unused, undefined
 
       // export
-	int
-	export_original( int h, const string& fname) const;
-	int
-	export_filtered( int h, const string& fname) const;
+	int export_original( int h, const string& fname) const;
+	int export_filtered( int h, const string& fname) const;
 
-	int
-	export_original_( int h, const string& fname) const;
-	int
-	export_filtered_( int h, const string& fname) const;
+	int export_original_( int h, const string& fname) const;
+	int export_filtered_( int h, const string& fname) const;
 
 
       // reporting & misc
@@ -286,7 +280,7 @@ class CEDFFile
 	};
 	SEDFHeader header;
 
-      // (relevant converted integers)
+      // relevant converted integers
 	double	data_record_size;
 	size_t	n_data_records;
 
@@ -344,7 +338,6 @@ class CEDFFile
 	};
 	vector<SSignal>
 		channels;
-	static size_t max_channels;
 
 	list<SAnnotation> // timepoints in seconds
 		common_annotations;
@@ -402,6 +395,7 @@ class CEDFFile
 		trailing_junk             = (1 << 18),
 		extra_patientid_subfields = (1 << 19),
 		recognised_channel_conflicting_type = (1 << 20),
+		mmap_error		  = (1 << 21),
 
 		inoperable		 = (bad_header
 					   | bad_version
@@ -412,9 +406,10 @@ class CEDFFile
 					   | nogain
 					   | sysfail
 					   | too_many_channels
-					   | file_truncated)
+					   | file_truncated
+					   | mmap_error)
 	};
-	static string explain_edf_status( int);
+	static string explain_status( int);
 
     private:
 	TSubtype _subtype;
@@ -423,10 +418,12 @@ class CEDFFile
 		_end_time;
 
 	string	_patient_id, // this is trimmed, raw; parsed into SSubjectId fields
+		_recording_id,
        // take care of file being named 'episode-1.edf'
 		_episode,
        // loosely/possibly also use RecordingID as session
-		_session;
+		_session,
+		_reserved;
 
 	void _lay_out_header();
 	int _parse_header();
diff --git a/src/libsigfile/forward-decls.hh b/src/libsigfile/forward-decls.hh
index 4c44934..1acf89f 100644
--- a/src/libsigfile/forward-decls.hh
+++ b/src/libsigfile/forward-decls.hh
@@ -10,8 +10,8 @@
  */
 
 
-#ifndef _SIGFILE_FORWARD_DECLS_H
-#define _SIGFILE_FORWARD_DECLS_H
+#ifndef AGH_SIGFILE_FORWARD_DECLS_H_
+#define AGH_SIGFILE_FORWARD_DECLS_H_
 
 namespace sigfile {
 
@@ -24,9 +24,12 @@ class CSource;
 class CTypedSource;
 class CHypnogram;
 
+class CTSVFile;
+class CEDFFile;
+
 } // namespace sigfile
 
-#endif // _SIGFILE_FORWARD_DECLS_H
+#endif
 
 // Local Variables:
 // Mode: c++
diff --git a/src/libsigfile/source-base.cc b/src/libsigfile/source-base.cc
index 9f2405c..818894f 100644
--- a/src/libsigfile/source-base.cc
+++ b/src/libsigfile/source-base.cc
@@ -10,6 +10,8 @@
  */
 
 
+#include <fstream>
+#include "common/string.hh"
 #include "source-base.hh"
 
 using namespace std;
@@ -121,6 +123,144 @@ dirty_signature() const
 
 
 
+int
+sigfile::CSource::
+load_ancillary_files()
+{
+	int retval = 0;
+
+	for ( int h = 0; h < (int)n_channels(); ++h ) {
+		auto& H = channel_by_id(h);
+
+	      // 1. artifacts
+		{
+			ifstream thomas (make_fname_artifacts( H));
+			if ( not thomas.good() )
+				goto step2;
+
+			auto& AA = artifacts(h);
+			while ( !thomas.eof() ) {
+				double aa = NAN, az = NAN;
+				thomas >> aa >> az;
+				if ( not isfinite(aa) || not isfinite(az) ) {
+					retval = -1;
+					break;
+				}
+				AA.mark_artifact( aa, az);
+			}
+		}
+
+	step2:
+	      // 2. annotations
+		{
+			ifstream fd (make_fname_annotations( H));
+			if ( not fd.good() )
+				goto step3;
+
+			auto& AA = annotations(h);
+			while ( fd.good() and not fd.eof() ) {
+				int type = -1;
+				double aa = NAN, az = NAN;
+				string an;
+				fd >> type >> aa >> az;
+				getline( fd, an, SAnnotation::EOA);
+				if ( isfinite(aa) and isfinite(az) and
+				     aa < az and az <= recording_time()
+				     and type < SAnnotation::TType_total and type >= 0 )
+					AA.emplace_back(
+						aa, az,
+						agh::str::trim(an),
+						(SAnnotation::TType)type);
+				else {
+					retval = -1;
+					break;
+				}
+			}
+			AA.sort();
+		}
+	step3:
+		;
+	}
+
+      // 3. filters
+	{
+		ifstream thomas (make_fname_filters(_filename));
+		if ( !thomas.good() )
+			for ( int h = 0; h < (int)n_channels(); ++h ) {
+				auto& AA = filters(h);
+
+				unsigned lpo = -1, hpo = -1, nf = -1;
+				double lpc = 0., hpc = 0.;
+				thomas >> lpc >> lpo
+				       >> hpc >> hpo >> nf;
+				AA = {lpc, lpo, hpc, hpo, (SFilterPack::TNotchFilter)nf};
+				if ( not AA.is_valid() )
+					AA.reset();
+			}
+	}
+
+	return retval;
+}
+
+
+
+
+
+int
+sigfile::CSource::
+save_ancillary_files()
+{
+	int retval = 0;
+	for ( int h = 0; h < (int)n_channels(); ++h ) {
+		auto& H = channel_by_id(h);
+		{
+			auto& AA = artifacts(h);
+			if ( not AA.empty() ) {
+				ofstream thomas (make_fname_artifacts( H), ios_base::trunc);
+				for ( auto &A : AA() )
+					thomas << A.a << ' ' << A.z << endl;
+				if ( not thomas.good() )
+					retval = -1;
+			} else
+				if ( unlink( make_fname_artifacts( H).c_str()) ) {}
+		}
+
+		{
+			auto& AA = annotations(h);
+
+			auto fname = make_fname_annotations( H);
+
+			if ( not AA.empty() ) {
+				ofstream thomas (fname, ios_base::trunc);
+				for ( auto &A : AA ) {
+					thomas << (int)A.type << ' '
+					       << A.span.a << ' ' << A.span.z << ' '
+					       << A.label << SAnnotation::EOA << endl;
+					if ( not thomas.good() )
+						retval = -1;
+				}
+
+			} else
+				if ( unlink( fname.c_str()) ) {}
+		}
+	}
+	ofstream thomas (make_fname_filters( filename()), ios_base::trunc);
+	if ( thomas.good() )
+		for ( int h = 0; h < (int)n_channels(); ++h ) {
+			auto& AA = filters(h);
+			thomas << AA.low_pass_cutoff << ' ' << AA.low_pass_order << ' '
+			       << AA.high_pass_cutoff << ' ' << AA.high_pass_order << ' '
+			       << (int)AA.notch_filter << endl;
+			if ( not thomas.good() )
+				retval = -1;
+		}
+
+	return retval;
+}
+
+
+
+
 
 
 sigfile::CSource::
diff --git a/src/libsigfile/source-base.hh b/src/libsigfile/source-base.hh
index 945d3f0..a0d77e8 100644
--- a/src/libsigfile/source-base.hh
+++ b/src/libsigfile/source-base.hh
@@ -84,6 +84,10 @@ struct SArtifacts {
 			return obj;
 		}
 
+	bool empty() const
+		{
+			return obj.empty();
+		}
 	size_t total() const
 		{
 			size_t q = 0;
@@ -109,6 +113,8 @@ struct SArtifacts {
 
 
 struct SAnnotation {
+	static const char EOA = '$';
+
 	agh::alg::SSpan<double> span;
 	string label;
 	enum TType {
@@ -120,7 +126,7 @@ struct SAnnotation {
 	};
 	TType type;
 
-	SAnnotation( double aa, double az, const string& l, TType t = TType::plain)
+	SAnnotation (double aa, double az, const string& l, TType t = TType::plain)
 	      : span {aa, az},
 		label (l),
 		type (t)
@@ -154,29 +160,49 @@ mark_annotation( list<SAnnotation>& annotations,
 
 
 struct SFilterPack {
-	SFilterPack()
-	      : high_pass_cutoff (0.),
-		low_pass_cutoff (0.),
-		high_pass_order (0),
+	enum TNotchFilter : int {
+		none, at50Hz, at60Hz, TNotchFilter_total
+	};
+
+	SFilterPack ()
+	      : low_pass_cutoff (0.),
 		low_pass_order (0),
+		high_pass_cutoff (0.),
+		high_pass_order (0),
 		notch_filter (TNotchFilter::none)
 		{}
+	SFilterPack (double lpo, unsigned lpc, double hpo, unsigned hpc, TNotchFilter nf)
+	      : low_pass_cutoff (lpc),
+		low_pass_order (lpo),
+		high_pass_cutoff (hpc),
+		high_pass_order (hpo),
+		notch_filter (nf)
+		{}
 
 	bool have_filters() const
 		{
 			return low_pass_cutoff > 0. || high_pass_cutoff > 0. ||
 				notch_filter != SFilterPack::TNotchFilter::none;
 		}
+	bool is_valid() const
+		{
+			return high_pass_order < 5 &&
+			       low_pass_order < 5 &&
+			       notch_filter >= TNotchFilter::none &&
+			       notch_filter < TNotchFilter::TNotchFilter_total;
+		}
+	void reset()
+		{
+			high_pass_cutoff = low_pass_cutoff = 0.;
+			high_pass_order = low_pass_order = 0;
+			notch_filter = TNotchFilter::none;
+		}
 
-	double	high_pass_cutoff,
-		low_pass_cutoff;
-	unsigned
-		high_pass_order,
-		low_pass_order;
+	double		low_pass_cutoff;
+	unsigned	low_pass_order;
+	double		high_pass_cutoff;
+	unsigned	high_pass_order;
 
-	enum TNotchFilter : int {
-		none, at50Hz, at60Hz
-	};
 	TNotchFilter
 		notch_filter;
 
@@ -190,10 +216,20 @@ class CSource {
 	friend class CTypedSource;
     protected:
 	string	_filename;
+
 	int	_status;
+	void clear_status()
+		{ _status = 0; }
+
+	enum TFlags {
+		no_ancillary_files         = 1<<1,
+		no_field_consistency_check = 1<<2,
+	};
 	int	_flags;
+
 	agh::SSubjectId
 		_subject;
+
     public:
 	DELETE_DEFAULT_METHODS (CSource);
 	CSource (const string& fname_, int flags_ = 0)
@@ -203,10 +239,14 @@ class CSource {
 		{}
 	CSource( CSource&&);
 	virtual ~CSource()
-		{}
+		{
+			if ( not (_flags & no_ancillary_files) )
+				save_ancillary_files();
+		}
 
 	int status()	const { return _status; }
 	int flags()	const { return _flags; }
+
 	virtual string explain_status()			const = 0;
 	virtual string details( int which_details)	const = 0;
 
@@ -232,6 +272,8 @@ class CSource {
 	virtual double recording_time()			const = 0;
 
       // channels
+	const static size_t max_channels = 1024;
+
 	virtual size_t n_channels()			const = 0;
 	virtual list<SChannel> channel_list()		const = 0;
 	virtual bool have_channel( const SChannel&) 	const = 0;
@@ -267,13 +309,15 @@ class CSource {
 	virtual const SFilterPack&
 	filters( int)				const = 0;
 
-	template <typename T>
 	unsigned long
-	dirty_signature( T id) const
+	dirty_signature( int id) const
 		{
 			return artifacts(id).dirty_signature() + filters(id).dirty_signature();
 		}
 
+	virtual int load_ancillary_files();
+	virtual int save_ancillary_files();
+
       // setters
 	virtual int set_patient_id( const string&)    = 0;
 	virtual int set_recording_id( const string&)  = 0;
@@ -335,16 +379,11 @@ class CSource {
 	virtual int
 	put_region_smpl( int, const valarray<TFloat>&, size_t) const = 0;
 
-	int
-	put_region_sec( const int h, const valarray<TFloat>& src, const float offset) const
+	int put_region_sec( const int h, const valarray<TFloat>& src, const float offset) const
 		{ return put_region_smpl( h, src, offset * samplerate(h)); }
 
-	int
-	put_signal( const int h,
-		    const valarray<TFloat>& src)
-		{
-			return put_region_smpl( h, src, 0);
-		}
+	int put_signal( const int h, const valarray<TFloat>& src)
+		{ return put_region_smpl( h, src, 0); }
 
       // signal data info
 	virtual pair<TFloat, TFloat>
@@ -374,9 +413,6 @@ class CSource {
 		{
 			return sigfile::make_fname_annotations( filename(), channel);
 		}
-
-      // misc useful bits
-	virtual void write_ancillary_files() = 0;
 };
 
 
diff --git a/src/libsigfile/source.hh b/src/libsigfile/source.hh
index 4bd2cfa..191fb9f 100644
--- a/src/libsigfile/source.hh
+++ b/src/libsigfile/source.hh
@@ -51,7 +51,6 @@ class CTypedSource
 			throw invalid_argument("nono");
 		}
       // ctor
-	enum { no_ancillary_files = 1 };
 	CTypedSource (const string& fname, size_t pagesize, int flags = 0);
 	CTypedSource (CTypedSource&& rv);
        ~CTypedSource ();
diff --git a/src/libsigfile/edf.cc b/src/libsigfile/tsv.cc
similarity index 73%
copy from src/libsigfile/edf.cc
copy to src/libsigfile/tsv.cc
index 5a54285..2f56573 100644
--- a/src/libsigfile/edf.cc
+++ b/src/libsigfile/tsv.cc
@@ -1,20 +1,18 @@
 /*
- *       File name:  libsigfile/edf.cc
+ *       File name:  libsigfile/tsv.cc
  *         Project:  Aghermann
  *          Author:  Andrei Zavada <johnhommer at gmail.com>
- * Initial version:  2008-07-01
+ * Initial version:  2013-06-22
  *
- *         Purpose:  EDF class methods
+ *         Purpose:  TSV source
  *
  *         License:  GPL
  */
 
 
 #include <sys/stat.h>
-#include <sys/mman.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <cinttypes>
 #include <fstream>
 #include <sstream>
 #include <list>
@@ -22,7 +20,7 @@
 
 #include "common/lang.hh"
 #include "common/string.hh"
-#include "edf.hh"
+#include "tsv.hh"
 #include "source.hh"
 
 using namespace std;
@@ -33,58 +31,17 @@ using agh::str::join;
 using agh::str::tokens;
 using agh::str::tokens_trimmed;
 
-using sigfile::CEDFFile;
+using sigfile::CTSVFile;
 
 int
-CEDFFile::
-set_patient_id( const string& s)
-{
-	memcpy( header.patient_id, pad( s, 80).c_str(), 80);
-	return s.size() > 80;
-}
-
-int
-CEDFFile::
-set_recording_id( const string& s)
-{
-	memcpy( header.recording_id, pad( s, 80).c_str(), 80);
-	return s.size() > 80;
-}
-
-int
-CEDFFile::
-set_episode( const string& s)
-{
-	_episode.assign( s);
-	return set_recording_id( (_session + '/' + _episode).c_str());
-}
-
-int
-CEDFFile::
-set_session( const string& s)
-{
-	_session.assign( s);
-	return set_recording_id( (_session + '/' + _episode).c_str());
-}
-
-int
-CEDFFile::
-set_reserved( const string&s)
-{
-	memcpy( header.reserved, pad( s, 44).c_str(), 44);
-	return s.size() > 44;
-}
-
-int
-CEDFFile::
+CTSVFile::
 set_start_time( time_t s)
 {
 	char b[9];
-	// set start
 	strftime( b, 9, "%d.%m.%y", localtime(&s));
-	memcpy( header.recording_date, b, 8);
+	header.recording_date.assign( b);
 	strftime( b, 9, "%H.%M.%s", localtime(&s));
-	memcpy( header.recording_time, b, 8);
+	header.recording_time.assign( b);
 
 	return 0;
 }
@@ -92,202 +49,56 @@ set_start_time( time_t s)
 
 
 
-
-
-
-
-
-
-
-#define EOA '$'
-
-namespace {
-
-const char version_string[8]  = {'0',' ',' ',' ', ' ',' ',' ',' '};
-
-}
-
-
-CEDFFile::
-CEDFFile (const string& fname_, const int flags_)
+CTSVFile::
+CTSVFile (const string& fname_, const int flags_)
       : CSource (fname_, flags_)
 {
 	{
 		struct stat stat0;
 		int stst = stat( fname_.c_str(), &stat0);
-		if ( stst == -1 ) {
-			_status |= TStatus::sysfail;
-			throw invalid_argument (explain_edf_status(_status));
-		}
-		_fsize = stat0.st_size;
+		if ( stst == -1 )
+			throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 	}
 	_fd = open( fname_.c_str(), O_RDWR);
-	if ( _fd == -1 ) {
-		_status |= TStatus::sysfail;
-		throw invalid_argument (explain_edf_status(_status));
-	}
-
-      // mmap
-	_mmapping =
-		mmap( NULL,
-		      _fsize,
-		      PROT_READ | PROT_WRITE, MAP_SHARED,
-		      _fd, 0);
-	if ( _mmapping == (void*)-1 ) {
-		close( _fd);
-		throw length_error ("CEDFFile::CEDFFile(): mmap error");
-	}
+	if ( _fd == -1 )
+		throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 
       // parse header
 	if ( _parse_header() ) {  // creates channels list
-		if ( not (flags_ & no_field_consistency_check) ) {
+		if ( not (flags_ & sigfile::CSource::no_field_consistency_check) ) {
 			close( _fd);
-			munmap( _mmapping, _fsize);
-			throw invalid_argument (explain_edf_status(_status));
+			throw invalid_argument (explain_status(_status)); // _status set in _parse_header()
 		} else
-			fprintf( stderr, "CEDFFile::CEDFFile(\"%s\") Warning: parse header failed, but proceeding anyway\n", fname_.c_str());
+			fprintf( stderr, "CTSVFile::CTSVFile(\"%s\") Warning: parse header failed, but proceeding anyway\n", fname_.c_str());
 	}
 	// channels now available
 
-	header_length = 256 + (channels.size() * 256);
-
-      // lest we ever access past mmapped region
-	{
-		size_t	total_samples_per_record = 0;
-		for ( auto& H : channels )
-			total_samples_per_record += H.samples_per_record;
-		size_t	expected_fsize = header_length + sizeof(int16_t) * total_samples_per_record * n_data_records;
-		if ( _fsize < expected_fsize ) {
-			fprintf( stderr, "CEDFFile::CEDFFile(\"%s\") file size less than declared in header\n", fname_.c_str());
-			close( _fd);
-			munmap( _mmapping, _fsize);
-			_status |= file_truncated;
-			throw invalid_argument (explain_edf_status(_status));
-		} else if ( _fsize > expected_fsize ) {
-			_status |= trailing_junk;
-			fprintf( stderr, "CEDFFile::CEDFFile(\"%s\") Warning: %zu bytes of trailing junk\n",
-				 fname_.c_str(), _fsize - expected_fsize);
-		}
-	}
-
-	_extract_embedded_annotations();
-
       // ancillary files:
-	if ( flags_ & sigfile::CTypedSource::no_ancillary_files )
-		;
-	else {
-	      // 1. artifacts, per signal
-		for ( auto &H : channels ) {
-			ifstream thomas (make_fname_artifacts( H.ucd));
-			if ( not thomas.good() )
-				continue;
-
-			while ( !thomas.eof() ) {
-				double aa = NAN, az = NAN;
-				thomas >> aa >> az;
-				if ( not isfinite(aa) || not isfinite(az) )
-					break;
-				H.artifacts.mark_artifact( aa, az);
-			}
-		}
-
-	      // 2. annotations, per signal
-		for ( auto &H : channels ) {
-			ifstream fd (make_fname_annotations( H.ucd));
-			if ( not fd.good() )
-				continue;
-			while ( fd.good() and not fd.eof() ) {
-				int type = -1;
-				double aa = NAN, az = NAN;
-				string an;
-				fd >> type >> aa >> az;
-				getline( fd, an, EOA);
-				if ( isfinite(aa) and isfinite(az) and
-				     aa < az and az < n_data_records * data_record_size
-				     and type < SAnnotation::TType_total and type >= 0 )
-					H.annotations.emplace_back(
-						aa, az,
-						trim(an),
-						(SAnnotation::TType)type);
-				else {
-					fprintf( stderr, "Bad annotation: (%d %g %g %50s)\n", type, aa, az, an.c_str());
-					break;
-				}
-			}
-			H.annotations.sort();
-		}
-
-	      // 3. filters
-		{
-			ifstream thomas (make_fname_filters(fname_));
-			if ( !thomas.fail() )
-				for ( auto &I : channels ) {
-					int ol = -1, oh = -1, nf = -1;
-					float fl = 0., fh = 0.;
-					thomas >> fl >> ol
-					       >> fh >> oh >> nf;
-					if ( ol > 0 && oh > 0 && ol < 5 && oh < 5
-					     && fl >= 0. && fh >= 0.
-					     && nf >= 0 && nf <= 2 ) {
-						I.filters.low_pass_cutoff = fl;
-						I.filters.low_pass_order  = ol;
-						I.filters.high_pass_cutoff = fh;
-						I.filters.high_pass_order  = oh;
-						I.filters.notch_filter = (SFilterPack::TNotchFilter)nf;
-					}
-				}
-		}
-	}
+	if ( not (flags_ & sigfile::CSource::no_ancillary_files) )
+		load_ancillary_files();
 }
 
 
 
 
-CEDFFile::
-CEDFFile (const string& fname_, const TSubtype subtype_, const int flags_,
-	  const list<pair<SChannel, size_t>>& channels_,
-	  const size_t data_record_size_,
-	  const size_t n_data_records_)
+CTSVFile::
+CTSVFile (const string& fname_, const TSubtype subtype_, const int flags_,
+	  const list<SChannel>& channels_,
+	  const size_t samplerate_,
+	  const double recording_time_)
       : CSource (fname_, flags_),
-	data_record_size (data_record_size_),
-	n_data_records (n_data_records_),
-	_subtype (subtype_)
+	_subtype (subtype_),
+	_samplerate (samplerate_)
 {
 	_fd = open( fname_.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP);
 	if ( _fd == -1 ) {
-		_status |= TStatus::sysfail;
-		throw invalid_argument ("CEDFFile::CEDFFile(): file open error");
-	}
-
-	header_length = 256 + (channels_.size() * 256);
-	size_t total_samplerate = 0;
-	for ( auto& H : channels_ )
-		total_samplerate += H.second; // total samplerate
-
-	_fsize = header_length + 2 * total_samplerate * data_record_size * n_data_records;
-	// extend
-	if ( lseek( _fd, _fsize-1, SEEK_SET) == -1 || write( _fd, "\0", 1) != 1 ) {
-		_status |= TStatus::sysfail;
-		throw invalid_argument ("CEDFFile::CEDFFile(): file write error");
-	}
-
-//	size_t sys_page_size = (size_t) sysconf( _SC_PAGESIZE);
-	_mmapping =
-		mmap( NULL,
-		      _fsize,
-		      PROT_READ | PROT_WRITE, MAP_SHARED,
-		      _fd,
-		      0);
-	if ( _mmapping == (void*)-1 ) {
-		close( _fd);
-		throw length_error ("CEDFFile::CEDFFile(): mmap error");
+		fprintf( stderr, "CTSVFile::CTSVFile(\"%s\"): Failed to open file for writing\n", fname_.c_str());
+		throw invalid_argument (explain_status(_status |= TStatus::sysfail));
 	}
 
       // fill out some essential header fields
-	channels.resize( channels_.size());
-	_lay_out_header();
+	resize_seconds( recording_time_);
 
-	strncpy( header.version_number, version_string, 8);
 	_subject.id = "Fafa_1";
 	set_recording_id( "Zzz");
 	set_comment( fname_);
@@ -331,7 +142,7 @@ CEDFFile (const string& fname_, const TSubtype subtype_, const int flags_,
 
 
 void
-CEDFFile::SSignal::
+CTSVFile::SSignal::
 set_physical_range( const double m, const double M)
 {
 	strncpy( header.physical_min, pad( to_string( physical_min = m), 8).c_str(), 8);
@@ -340,7 +151,7 @@ set_physical_range( const double m, const double M)
 
 
 void
-CEDFFile::SSignal::
+CTSVFile::SSignal::
 set_digital_range( const int16_t m, const int16_t M)
 {
 	strncpy( header.digital_min, pad( to_string( digital_min = m), 8).c_str(), 8);
@@ -353,7 +164,7 @@ set_digital_range( const int16_t m, const int16_t M)
 // uncomment on demand (also un-dnl AC_CHECK_FUNCS(mremap,,) in configure.ac)
 /*
 size_t
-CEDFFile::
+CTSVFile::
 resize( const size_t new_records)
 {
 	size_t total_samples_per_record = 0;
@@ -384,7 +195,7 @@ resize( const size_t new_records)
 
 	if ( _mmapping == (void*)-1 ) {
 		close( _fd);
-		throw length_error ("CEDFFile::resize(): mmap error");
+		throw length_error ("CTSVFile::resize(): mmap error");
 	}
 
 	_fsize = new_fsize;
@@ -393,8 +204,8 @@ resize( const size_t new_records)
 
 */
 
-CEDFFile::
-CEDFFile (CEDFFile&& rv)
+CTSVFile::
+CTSVFile (CTSVFile&& rv)
       : CSource (move(rv))
 {
 	header = rv.header; // no need to re-layout as we don't mremap
@@ -420,12 +231,12 @@ CEDFFile (CEDFFile&& rv)
 	_mmapping     = rv._mmapping;
 	_fd           = rv._fd;
 
-	rv._mmapping = (void*)-1;  // will prevent munmap in ~CEDFFile()
+	rv._mmapping = (void*)-1;  // will prevent munmap in ~CTSVFile()
 }
 
 
-CEDFFile::
-~CEDFFile ()
+CTSVFile::
+~CTSVFile ()
 {
 	if ( _mmapping != (void*)-1 ) {
 		munmap( _mmapping, _fsize);
@@ -442,7 +253,7 @@ CEDFFile::
 
 
 void
-CEDFFile::
+CTSVFile::
 write_ancillary_files()
 {
 	for ( auto &I : channels ) {
@@ -478,63 +289,8 @@ write_ancillary_files()
 
 
 
-
-void
-CEDFFile::
-_lay_out_header()
-{
-	header.version_number 	 = (char*)_mmapping;               //[ 8],
-	header.patient_id     	 = header.version_number   +  8;   //[80],
-	header.recording_id   	 = header.patient_id       + 80;   //[80],
-	header.recording_date 	 = header.recording_id     + 80;   //[ 8],
-	header.recording_time 	 = header.recording_date   +  8;   //[ 8],
-	header.header_length  	 = header.recording_time   +  8;   //[ 8],
-	header.reserved       	 = header.header_length    +  8;   //[44],
-	header.n_data_records 	 = header.reserved         + 44;   //[ 8],
-	header.data_record_size  = header.n_data_records   +  8;   //[ 8],
-	header.n_channels        = header.data_record_size +  8;   //[ 4];
-
-	char *p = (char*)_mmapping + 256;
-	size_t h;
-	vector<SSignal>::iterator H;
-#define FOR(A, C)							\
-		for ( h = 0, H = channels.begin(); H != channels.end(); ++h, ++H, p += C ) H->A = p;
-
-	FOR (header.label,			16);
-	FOR (header.transducer_type,		80);
-	FOR (header.physical_dim,		 8);
-	FOR (header.physical_min,		 8);
-	FOR (header.physical_max,		 8);
-	FOR (header.digital_min,		 8);
-	FOR (header.digital_max,		 8);
-	FOR (header.filtering_info,		80);
-	FOR (header.samples_per_record,		 8);
-	FOR (header.reserved,			32);
-#undef FOR
-}
-
-
-
-
-char*
-CEDFFile::
-_get_next_field( char *&field, const size_t fld_size) throw (TStatus)
-{
-	if ( _fld_pos + fld_size > _fsize ) {
-		_status |= bad_header;
-		throw bad_header;
-	}
-
-	field = (char*)_mmapping + _fld_pos;
-	_fld_pos += fld_size;
-
-	return field;
-}
-
-size_t	CEDFFile::max_channels = 256;
-
 int
-CEDFFile::
+CTSVFile::
 _parse_header()
 {
 	size_t	n_channels;
@@ -825,7 +581,7 @@ outer_break:
 
 
 int
-CEDFFile::
+CTSVFile::
 _extract_embedded_annotations()
 {
 	auto S = find( channels.begin(), channels.end(), sigfile::edf_annotations_label);
@@ -899,7 +655,7 @@ _extract_embedded_annotations()
 
 
 string
-CEDFFile::
+CTSVFile::
 details( const int which) const
 {
 	ostringstream recv;
@@ -994,7 +750,7 @@ details( const int which) const
 
 
 string
-CEDFFile::
+CTSVFile::
 explain_edf_status( const int status)
 {
 	list<string> recv;
diff --git a/src/libsigfile/edf.hh b/src/libsigfile/tsv.hh
similarity index 57%
copy from src/libsigfile/edf.hh
copy to src/libsigfile/tsv.hh
index c917f29..9152c47 100644
--- a/src/libsigfile/edf.hh
+++ b/src/libsigfile/tsv.hh
@@ -1,16 +1,16 @@
 /*
- *       File name:  libsigfile/edf.hh
+ *       File name:  libsigfile/tsv.hh
  *         Project:  Aghermann
  *          Author:  Andrei Zavada <johnhommer at gmail.com>
- * Initial version:  2008-07-01
+ * Initial version:  2013-06-22
  *
- *         Purpose:  EDF class, also accommodating EDF+
+ *         Purpose:  plain ascii (tsv) data source
  *
  *         License:  GPL
  */
 
-#ifndef _SIGFILE_EDF_H
-#define _SIGFILE_EDF_H
+#ifndef AGH_SIGFILE_TSV_H_
+#define AGH_SIGFILE_TSV_H_
 
 #include <cinttypes>
 #include <cstring>
@@ -38,19 +38,18 @@ namespace sigfile {
 
 
 
-class CEDFFile
+class CTSVFile
   : public CSource {
 
       // deleted
-	bool operator==( const CEDFFile&) const = delete;
-	CEDFFile() = delete;
+	bool operator==( const CTSVFile&) const = delete;
+	CTSVFile() = delete;
 
     public:
 	// subtype
 	enum TSubtype {
-		edf,
-		edfplus_c,  // continuous
-		edfplus_d   // discontinuous
+		csv,
+		tsv,
 	};
 	TSubtype subtype() const
 		{ return _subtype; }
@@ -58,10 +57,9 @@ class CEDFFile
 	subtype_s( TSubtype t)
 		{
 			switch (t) {
-			case edf:       return "edf";
-			case edfplus_c: return "edf+c";
-			case edfplus_d: return "edf+d";
-			default:        return "(invalid)";
+			case csv: return "csv";
+			case tsv: return "tsv";
+			default:  return "(invalid)";
 			}
 		}
 	const char*
@@ -69,45 +67,41 @@ class CEDFFile
 		{ return subtype_s( _subtype); }
 
       // ctor
-	CEDFFile( const CEDFFile&)
+	CTSVFile( const CTSVFile&)
 	      : CSource("")
 		{
 			throw invalid_argument("nono");
 		}
 	enum TFlags {
-		no_mmap				= 1<<3,
-		no_cache			= 1<<4, // just considering
 		no_field_consistency_check	= 1<<5,
 	};
 	// open existing
-	CEDFFile (const string& fname, int flags = 0);
+	CTSVFile (const string& fname, int flags = 0);
 	// create new
-	CEDFFile (const string& fname, TSubtype subtype_, int flags,
+	CTSVFile (const string& fname, TSubtype, int flags,
 		  const list<pair<SChannel, size_t>>& channels,
 		  size_t data_record_size = 1,
 		  size_t n_data_records = 0);
-	CEDFFile (CEDFFile&& rv);
+	CTSVFile (CTSVFile&& rv);
       // dtor
-       ~CEDFFile ();
+       ~CTSVFile ();
 
       // interface
 	// status
 	string explain_status() const
-		{ return explain_edf_status( _status); }
+		{ return explain_status( _status); }
 
 	// identification
-	const char* filename() const
-		{ return _filename.c_str(); }
 	const char* patient_id() const
-		{ return _patient_id.c_str(); }
+		{ return header.patient_id.c_str(); }
 	const char* recording_id() const
-		{ return header.recording_id; }
+		{ return header.recording_id.c_str(); }
 	const char* comment() const
-		{ return header.reserved; }
+		{ return header.comment.c_str(); }
 	const char* episode() const
-		{ return _episode.c_str(); }
+		{ return header._episode.c_str(); }
 	const char* session() const
-		{ return _session.c_str(); }
+		{ return header._session.c_str(); }
 
 	// times
 	time_t start_time() const
@@ -115,18 +109,37 @@ class CEDFFile
 	time_t end_time() const
 		{ return _end_time; }
 	double recording_time() const // in seconds
-		{ return n_data_records * data_record_size; }
+		{ return channels.front().data.size() * _samplerate; } // all channels have the same sr, obviously
 
 	// setters
-	int set_patient_id( const string&);
-	int set_recording_id( const string&);
-	int set_episode( const string&);
-	int set_session( const string&);
-	int set_reserved( const string&);
+	int set_patient_id( const string& s)
+		{
+			header.patient_id = s;
+			return 0;
+		}
+	int set_recording_id( const string& s)
+		{
+			header.recording_id = s;
+			return 0;
+		}
+	int set_episode( const string& s) // assigning to _episode or _session directly won't have a lasting effect; think again.
+		{
+			header._episode = s;
+			return 0;
+		}
+	int set_session( const string& s)
+		{
+			header._session = s;
+			return 0;
+		}
 	int set_comment( const string& s)
-		{ return set_reserved( s); }
+		{
+			header.comment = s;
+			return 0;
+		}
 
 	int set_start_time( time_t);
+
 	// channels
 	size_t n_channels() const
 		{ return channels.size(); }
@@ -158,8 +171,8 @@ class CEDFFile
 		{ return operator[](h).ucd.type(); }
 
 	size_t
-	samplerate( const int h) const
-		{ return operator[](h).samples_per_record / data_record_size; }
+	samplerate( int) const
+		{ return _samplerate; }
 
 	list<SAnnotation>&
 	annotations( const int h)
@@ -198,26 +211,21 @@ class CEDFFile
 
 	valarray<TFloat>
 	get_signal_original( const int h) const // there is a CSource::get_signal_original already, but this one is a little better
-		{ return get_region_original_smpl(
-				h, 0, n_data_records * operator[](h).samples_per_record); }
+		{ return get_region_original_smpl( h, 0, channels.front().data.size()); }
 
 	valarray<TFloat>
 	get_region_filtered_smpl( int, size_t, size_t) const;
 
 	valarray<TFloat>
 	get_signal_filtered( const int h) const
-		{ return get_region_filtered_smpl(
-				h, 0, n_data_records * operator[](h).samples_per_record); }
+		{ return get_region_filtered_smpl( h, 0, channels.front().data.size()); }
 
       // put signal
-	int
-	put_region_smpl( int, const valarray<TFloat>&, size_t) const;
-	int
-	put_region_sec( const int h, const valarray<TFloat>& src, const float offset) const
-		{ return put_region_smpl( h, src, (size_t)(offset * samplerate(h))); }
+	int put_region_smpl( int, const valarray<TFloat>&, size_t) const;
+	int put_region_sec( const int h, const valarray<TFloat>& src, const float offset) const
+		{ return put_region_smpl( h, src, (size_t)(offset * _samplerate)); }
 
-	int
-	put_signal( const int h, const valarray<TFloat>& src) const
+	int put_signal( const int h, const valarray<TFloat>& src) const
 		{ return put_region_smpl( h, src, 0); }
 
       // signal data info
@@ -229,11 +237,6 @@ class CEDFFile
 		}
 
 	pair<TFloat, TFloat>
-	get_max_original_signal_range( const int h) const
-		{ return {(TFloat)channels[h].digital_min, (TFloat)channels[h].digital_max}; }
-
-
-	pair<TFloat, TFloat>
 	get_real_filtered_signal_range( const int h) const
 		{
 			auto x = get_signal_filtered( h);
@@ -249,79 +252,49 @@ class CEDFFile
 
       // adjust capacity
 	size_t
-	resize( size_t new_records);
+	resize_seconds( double);
 
       // export
-	int
-	export_original( int h, const string& fname) const;
-	int
-	export_filtered( int h, const string& fname) const;
+	int export_original( int h, const string& fname) const;
+	int export_filtered( int h, const string& fname) const;
 
-	int
-	export_original_( int h, const string& fname) const;
-	int
-	export_filtered_( int h, const string& fname) const;
+	int export_original_( int h, const string& fname) const;
+	int export_filtered_( int h, const string& fname) const;
 
 
       // reporting & misc
 	void write_ancillary_files();
 
-	enum TEdfDetails { with_channels = 1, with_annotations = 2 };
+	enum TTsvDetails { with_channels = 1, with_annotations = 2 };
 	string details( int which) const;
 
 	sigproc::TWinType af_dampen_window_type; // master copy
 
-      // static fields (mmapped)
-	struct SEDFHeader {
-		char	*version_number	 ,   //[ 8],
-			*patient_id    	 ,   //[80],  // maps to subject name
-			*recording_id  	 ,   //[80],  // maps to episode_name (session_name)
-			*recording_date	 ,   //[ 8],
-			*recording_time	 ,   //[ 8],
-			*header_length 	 ,   //[ 8],
-			*reserved      	 ,   //[44],
-			*n_data_records	 ,   //[ 8],
-			*data_record_size,   //[ 8],
-			*n_channels      ;   //[ 4];
+      // header
+	struct SHeader {
+		string	patient_id,
+			recording_id,
+			recording_date,
+			recording_time,
+			comment;
+
+		string	_episode,
+			_session;
 	};
-	SEDFHeader header;
+	SHeader header;
 
-      // (relevant converted integers)
-	double	data_record_size;
-	size_t	n_data_records;
+	map<string,string>
+		metadata;
 
       // channels
 	struct SSignal {
-		static const char* edf_annotations_label;
-		struct SEDFSignalHeader {
-			char	*label             ,//  [16],
-				*transducer_type   ,//  [80],
-				*physical_dim      ,//  [ 8],
-				*physical_min      ,//  [ 8],
-				*physical_max      ,//  [ 8],
-				*digital_min       ,//  [ 8],
-				*digital_max       ,//  [ 8],
-				*filtering_info    ,//  [80],
-				*samples_per_record,//  [ 8],
-				*reserved          ;//  [32];
-		};
-		SEDFSignalHeader
-	    		header;
 		SChannel
 			ucd; // Universal Channel Designation, епта
-		string	transducer_type,
-			physical_dim,
-			filtering_info,
-			reserved;
-
-		int	digital_min,
-			digital_max;
-		double	physical_min,
-			physical_max,
-			scale;
-		void set_physical_range( double, double);
-		void set_digital_range( int16_t, int16_t);
-		size_t	samples_per_record;
+
+		double	scale;
+
+		valarray<TFloat>
+			data;
 
 		bool operator==( const SChannel& h) const
 			{
@@ -338,13 +311,9 @@ class CEDFFile
 			artifacts;
 		SFilterPack
 			filters;
-	    private:
-		friend class CEDFFile;
-		size_t	_at;  // offset of our chunk within record, in samples
 	};
 	vector<SSignal>
 		channels;
-	static size_t max_channels;
 
 	list<SAnnotation> // timepoints in seconds
 		common_annotations;
@@ -382,9 +351,7 @@ class CEDFFile
 	enum TStatus : int_least32_t {
 		ok			  = 0,
 		bad_header		  = (1 <<  0),
-		bad_version		  = (1 <<  1),
 		bad_numfld		  = (1 <<  2),
-		bad_recording		  = (1 <<  3),
 		date_unparsable		  = (1 <<  4),
 		time_unparsable		  = (1 <<  5),
 		nosession		  = (1 <<  6),
@@ -392,57 +359,32 @@ class CEDFFile
 		nonkemp_signaltype	  = (1 <<  8),
 		non1020_channel		  = (1 <<  9),
 		dup_channels		  = (1 << 10),
-		nogain			  = (1 << 11),
 		sysfail			  = (1 << 12),
 		too_many_channels	  = (1 << 13),
 		nonconforming_patient_id  = (1 << 14),
 		missing_patient_id        = (1 << 15),
 		invalid_subject_details   = (1 << 16),
-		file_truncated            = (1 << 17),
-		trailing_junk             = (1 << 18),
 		extra_patientid_subfields = (1 << 19),
-		recognised_channel_conflicting_type = (1 << 20),
 
 		inoperable		 = (bad_header
-					   | bad_version
 					   | bad_numfld
-					   | bad_recording
 					   | date_unparsable | time_unparsable
 					   | dup_channels
-					   | nogain
 					   | sysfail
-					   | too_many_channels
-					   | file_truncated)
+					   | too_many_channels)
 	};
-	static string explain_edf_status( int);
+	static string explain_status( int);
 
     private:
 	TSubtype _subtype;
 
+	size_t	_samplerate;
 	time_t	_start_time,
 		_end_time;
 
-	string	_patient_id, // this is trimmed, raw; parsed into SSubjectId fields
-       // take care of file being named 'episode-1.edf'
-		_episode,
-       // loosely/possibly also use RecordingID as session
-		_session;
-
-	void _lay_out_header();
-	int _parse_header();
-	int _extract_embedded_annotations();
-
-	size_t	header_length,
-		_fsize,
-		_fld_pos,
-		_total_samples_per_record;
-	char* _get_next_field( char*&, size_t) throw (TStatus);
-
-	void	*_mmapping;
 	int	_fd;
 
-	vector<double>
-		_record_offsets;
+	int _parse_header();
 };
 
 

-- 
Sleep experiment manager



More information about the debian-med-commit mailing list