Programmers Notes

CSV tables

In some modules CSV tables may by imported (mk_master_cal).

The implemented style is:

serial

chopper

f

a

p

stddev a

stddev p

1

1

1.232800e-01

2.000200e-01

8.823600e+01

2.734738e-04

2.734738e-04

1

0

1.000000e+00

1.892500e-01

1.112900e+02

1.312173e-03

1.312173e-03

serial, chopper, f, a, p, stddev a, stddev p
1, 1, 1.232800e-01, 2.000200e-01, 8.823600e+01, 2.734738e-04, 2.734738e-04
1, 0, 1.000000e+00, 1.892500e-01, 1.112900e+02, 1.312173e-03, 1.312173e-03

CVS tables are always TEXT. When performing actions based on tables, be aware that 1.0e+00 and 1.0E+00 are NOT THE SAME
Also in case of mathematics a string must be converted into a number and back:
.. 1.0e+00 -> double is ok, 1.0e+00 -> int is not ok; so internally we always go 1.0e+00 -> double -> int (in case).
In case of string data we always compare lower case!
All frequencies you use have to be sorted from low to high.

ATS header

ats stands for advanced timeseries.
In languages such as C++ you do simlilar like (here with C++ & Qt)

ATSHeader_80   bin_atsheader;   //!< binaray header for read/write

....
file.setFileName("myatsfile.ats");
file.open(QIODevice::ReadOnly))
QDataStream qds;
qds.setDevice(file);
qds.readRawData((char*) &bin_atsheader, sizeof(bin_atsheader));
qDebug() << "samples: " << bin_atsheader.uiSamples;

In other languages reading a binary file is maybe similar.

#ifndef ATSHEADER_80_DEF_H
#define ATSHEADER_80_DEF_H

#include <cstdint>

//
//
//
//         array of chars are not NULL terminated !
//         achChanType   [2] = "Ex" is not NULL terminated
//
//

#define C_ATS_CEA_NUM_HEADERS   1023     /*!< maximum number of 1023 headers of the ATS sliced file.*/


/*!
\brief The ATSSliceHeader_s struct
A sliced ats file ALWAYS contains a default header and C_ATS_CEA_NUM_HEADERS (1023) = 1024 total.<br>
The FIRST and DEFAULT slice contains ALL information (total samples of all slices)<br>
The 0 ... 1022 slices (=  ATS_CEA_NUM_HEADERS ) contain the individual descriptions<br>
start tsdata = 400h = 1024th byte<br>
start of CEA data is 400h + 3FFh * 20h = 83E0h<br>
                        1024 + 1023 * 32  = 33760<br>

assuming 3 slices of 4096 iSamples will be 12,288 samples in the header,
followed by 3 slice headers with 4096 iSamples each
and 1019 empty slice headers<br>
the data has to sorted out on/after 83E0h according to the slice info

*/

struct ATSSliceHeader_1080
{
    std::uint32_t uiSamples;                    //!< 000h  number of samples for this slice
    std::uint32_t uiStartDateTime;              //!< 004h  startdate/time as UNIX timestamp (UTC)
    double        dbDCOffsetCorrValue;          //!< 008h  DC offset correction value in mV
    float         rPreGain;                     //!< 010h  originally used pre-gain (GainStage1) - LSB is the same for all slices
    float         rPostGain;                    //!< 014h  originally used post-gain (GainStage2) - LSB is the same for all slices
    std::int8_t   DCOffsetCorrOn;               //!< 018h  DC offset correction was on/off for this slice
    std::int8_t   reserved[7];                  //!< 020h  reserved bytes to get to word / dword boundary
};

/*! @todo TX info
maybe UTM Zone number
maybe UTM letter
Tx times six double at least
Tx base freq

*/

/*!
\brief The ATSComments_80 struct for ats header 80,81 and 90 <br>
Some software will automatically convert to higher version<br>
Some fields are external - the user has to set then; examples:<br>
northing, easting, UTM Zone : these can be used for high density grids<br>
*/
struct ATSComments_80 {
    char          achClient        [16];        //!< 000h  UTF-8 storage, used in EDI
    char          achContractor    [16];        //!< 010h  UTF-8 storage, used in EDI
    char          achArea          [16];        //!< 020h  UTF-8 storage, used in EDI
    char          achSurveyID      [16];        //!< 030h  UTF-8 storage, used in EDI
    char          achOperator      [16];        //!< 040h  UTF-8 storage, used in EDI
    char          achSiteName     [112];        //!< 050h  UTF-8 storage, no BOM at the beginning!; WORST case = 28 Chinese/Japanese chars (multibyte chars)
    char          achXmlHeader     [64];        //!< 0C0h  UTF-8 storage  points to the corresponding XML file, containing addtional information for this header; no PATH=XML file is in the same directory as the ats file
    // deleted September 2018 char          achComments   [512];          //!< 100h  UTF-8 storage  comment block starts (comment field can start with "weather:" but has no meaning
    // new September 2018
    char          achComments     [288];        //!< 100h  UTF-8 storage  comment block starts (comment field can start with "weather:" but has no meaning
    char          achSiteNameRR   [112];        //!< 220h  UTF-8 storage, no BOM at the beginning!; WORST case = 28 Chinese/Japanese chars (multibyte chars)
    char          achSiteNameEMAP [112];        //!< 290h  UTF-8 storage, no BOM at the beginning!; WORST case = 28 Chinese/Japanese chars (multibyte chars); INDICATES a default EMAP center station, where we expect the H (magnetic)


};


/*!
\brief The ATSHeader_80 struct<br>
reference is lat and long; additional entries like northing and easting should be empty // not used<br>
these entries can be overwritten by the user in case of high density grids <br>
The site_name (site name, achSiteName, is in the comment field; the site_no, site_num, site_number, site number is nver stored; this is part of external numeration
*/
struct ATSHeader_80 {
    std::uint16_t uiHeaderLength;               //!< 000h  length of header in bytes (default 1024 and  33760 for CEA)
    std::int16_t  siHeaderVers;                 //!< 002h  80 for ats, 81 for 64bit possible / metronix, 1080 for CEA / sliced header

    // This information can be found in the ChannelTS datastructure
    std::uint32_t uiSamples;                    //!< 004h  amount of samples (each is a 32bit / 64bit int INTEL byte order little endian) in the file total of all slices; if uiSamples == UINT32_MAX, uiSamples64bit at address 0F0h will be used instead; uiSamples64bit replaces uiSamples in that case; do not add!
    //!<
    float         rSampleFreq;                  //!< 008h  sampling frequency in Hz
    std::uint32_t uiStartDateTime;              //!< 00Ch  unix TIMESTAMP (some computers will revert that to negative numbers if this number is grater than 32bit signed); 2106-02-07T06:28:14 is the END of this format
    double        dblLSBMV;                     //!< 010h  least significant bit in mV ()
    std::int32_t  iGMTOffset;                   //!< 018h  not used, default 0; can be used as "UTC to GMT"
    float         rOrigSampleFreq;              //!< 01Ch  sampling frequency in Hz as ORIGINALLY recorded; this value should NOT change (for example after filtering)

    //The required data could probably found in the HardwareConfig
    std::uint16_t uiADUSerNum;                  //!< 020h  Serial number of the system (logger)
    std::uint16_t uiADCSerNum;                  //!< 022h  Serial number of the ADC board (ADB)
    std::uint8_t  uiChanNo;                     //!< 024h  Channel number
    std::uint8_t  uiChopper;                    //!< 025h  Chopper On (1) / Off (0); e.g. chopper is on for 512Hz and lower

    // Data from XML Job-specification
    char          achChanType   [2];            //!< 026h  (Ex, Ey, Ez, Hx, Hy, Hz, Jx, Jy, Jz, Px, Py, Pz, Rx, Ry, Rz and so on)
    char          achSensorType [6];            //!< 028h  (MFS06 MFS06e MFS07 MFS07e MFS10e SHFT02 SHF02e FGS02 FGS03 FGS03e etc. e.g. the "-" in MFS-06e is skipped)
    std::int16_t  siSensorSerNum;               //!< 02Eh  Serial number of connected sensor

    float         rPosX1;                       //!< 030h  e.g. South negative [m]
    float         rPosY1;                       //!< 034h  e.g. West negative [m]
    float         rPosZ1;                       //!< 038h  e.g. top, sky [m]
    float         rPosX2;                       //!< 03Ch  e.g. North positive [m]
    float         rPosY2;                       //!< 040h  e.g. East positive [m]
    float         rPosZ2;                       //!< 044h  e.g. bottom, earth [m]

    // not used any more use pos values!!; GUI interfaces using Length and direction MUST calculate pos x,y,z and set above.
    float         rDipLength;                   //!< 048h  e.g. to be calculated; should not be used - my be over written in FUTURE
    // not used any more use pos values!!
    float         rAngle;                       //!< 04Ch  e.g. to be calculated; should not be used - my be over written in FUTURE

    // Data from Selftest
    float         rProbeRes;                    //!< 050h  [ohm]
    float         rDCOffset;                    //!< 054h  [mV]
    float         rPreGain;                     //!< 058h  e.g. Gain Stage 1
    float         rPostGain;                    //!< 05Ch  e.g. Gain Stage 2

    // Data from status information ?
    std::int32_t  iLatitude;                    //!< 060h  must be used, UNIT = milli seconds
    std::int32_t  iLongitude;                   //!< 064h  must be used, UNIT = milli seconds
    std::int32_t  iElevation;                   //!< 068h  must be used, UNIT = cm
    char          byLatLongType;                //!< 06Ch  'G' default, 'U' user, GPS should be used
    char          byAddCoordType;               //!< 06Dh  'U' = UTM, default empty
    std::int16_t  siRefMeridian;                //!< 06Eh  default empty, should not be used (external)



    //!@todo can we store 64bit time and 64bit samples here ??
    double        dblNorthing;                  //!< 070h  also xcoord should not be used, default 0 (external)
    double        dblEasting;                   //!< 078h  also ycoord should not be used  default 0 (external)
    char          byGPSStat;                    //!< 080h  '-' unknown, 'N' no fix, 'C' full fix
    char          byGPSAccuracy;                //!< 081h  '0' - not used, 1 in case GF4-Fix & Syrlinks was active (system was synced before Syrlinks took over)
    std::int16_t  iUTCOffset;                   //!< 082h  UTC + iUTCOffset = GPS time for example; can be used for other offsets, not used, default 0
    char          achSystemType[12];            //!< 084h  MMS03e GMS05 ADU06 ADU07 ADU08 ADU09 SPAMMKV SPAMMKIII

    // Data from XML-Job specification
    char          achSurveyHeaderName [12];     //!< 090h  UTF-8 storage  free for usage
    char          achMeasType          [4];     //!< 09Ch  free for usage MT CSMT


    double        DCOffsetCorrValue;            //!< 0A0h  DAC offset double
    std::int8_t   DCOffsetCorrOn;               //!< 0A8h  DC offset was switched on (1) or off(0)
    std::int8_t   InputDivOn;                   //!< 0A9h  inputput divider on(1) off(0); e.g when coil was connected = 1
    std::int16_t  bit_indicator;                //!< 0AAh  0 = 32bit int INTEL byte order, 1 = 64bit INTEL byte order, little endian; since atsheader version 81
    char          achSelfTestResult [2];        //!< 0ACh  'NO' or 'OK'
    std::uint16_t numslices;                    //!< 0AEh  number of slices used (1....1024, 1 is the first, that is list.size() )

    //std::int16_t  calentries    // max 128 entries

    // Were the following fields ever used ?
    std::int16_t  siCalFreqs;                   //!< 0B0h  not used  (external)
    std::int16_t  siCalEntryLength;             //!< 0B2h  not used  (external)
    std::int16_t  siCalVersion;                 //!< 0B4h  not used  (external)
    std::int16_t  siCalStartAddress;            //!< 0B6h  not used, never used  (external)


    char          abyLFFilters [8];             //!< 0B8h  is a bitfield

    char          achUTMZone  [12];             //!< 0C0h  not used  (external)  (formerly abyADU06CalFilename) 32U or 01G, 32UMD7403 : alway NNC od NNCCNNNN
    std::uint32_t uiADUCalTimeDate;             //!< 0CCh  not used  (external)
    char          achSensorCalFilename [12];    //!< 0D0h  not used  ("SENSOR.CAL") (external)
    std::uint32_t uiSensorCalTimeDate;          //!< 0DCh  not used  (external)

    float         rPowerlineFreq1;              //!< 0E0h  e.g. empty  (external)
    float         rPowerlineFreq2;              //!< 0E4h  e.g. empty  (external)
    char          abyHFFilters[8];              //!< 0E8h  is a bitfield


    // IF uiSamples == UINT32_MAX you find
    std::uint64_t uiSamples64bit;               //!< 0F0h  amount of samples (each is a 32bit /64bit int) in the file total of all slices; ONLY used in case uiSamples == UINT32_MAX; else 0; REPLACSES the uiSamples; do not add

    //double        OriginalLSBMV;              //!< 0F0h  NOT USED ANYMORE! orig lsb from selftest without gains; used for ADC values
    float         rExtGain;                     //!< 0F8h  for external satellite box
    char          achADBBoardType[4];           //!< 0FCh  LF HF or MF or BB


    ATSComments_80 tscComment;                  //!< 100h

    // size if comments is total 100h + 100h data  + (512byte comments = 200h) = 400h
    // start tsdata = 400h = 1024th byte
    // start of CEA header is 400h + 3FFh * 20h = 83E0h
    //                        1024 + 1023 * 23  = 33760
};


#endif // ATSHEADER_80_DEF_H

LSB

LSB == least significant bit

The dblLSBMV multiplied with the int32_t data values results in the measured values in mV Gains and others are included in th LSB.
To scale the E field use rPosX1 … rPosZ2 values and NOT! the rDipLength and rAngle; these values are obsolete
metronix software assumes for the magnetic field that in case all rPosX1 … rPosZ2 are zero, that Hx is pointing North, Hy East and Hz down.

ATM header

That is the binary marker file

struct atmheader {
  qint16 siHeaderLength;
  qint16 siHeaderVers;
  quint32 iSamples;
};

Followed by booleans (for each sample); true = will be processed, false not.

Calibration File

E.g. the calibration file has to sections: “chopper on” and “chopper off”.
First column indicateds frequency in Hz.
Second column amplitude in V/(nT * Hz)
Third phase in degree (0-360, not radians).

Be default the chopper should be ON for recordings with equal/less than 512 Hz.
For chopper on the lower frequencies can be extended by the theroretical function. For chopper off this is not possible.

The amplitude is normalized by f (as you can see from the units). So for lower frequencies the amplitude of the MFS-06e converges agains 0.2 V/(nT*Hz) (and gives a horizontal line in a plot for the chopper on data).

For usage in your software you may

  • multiply magnitude by f

  • make a complex number from magnitude and phase

  • if you take the mV data from the ats, multiply calibration by 1000 in case

  • interpolate the calibration data onto your nodes of the FFT

  • divide by the calibration

Calibration measurement with NumetriQ PSM 3750 - 0.1.100000.1.33
Metronix GmbH, Kocherstr. 3, 38120 Braunschweig

Magnetometer: MFS06e#727    Date: 17/01/12    Time: 12:19:57


FREQUENCY    MAGNITUDE    PHASE
Hz           V/(nT*Hz)    deg
Chopper On
+1.0000E-01  +1.9996E-01  +8.8589E+01
+1.2328E-01  +2.0008E-01  +8.8225E+01
+1.5199E-01  +2.0014E-01  +8.7796E+01
+1.8738E-01  +1.9987E-01  +8.7329E+01
+2.3101E-01  +1.9982E-01  +8.6762E+01
+2.8480E-01  +1.9984E-01  +8.5986E+01
+3.5111E-01  +1.9961E-01  +8.5047E+01
+4.3287E-01  +1.9919E-01  +8.3911E+01
+5.3367E-01  +1.9863E-01  +8.2537E+01
+6.5793E-01  +1.9767E-01  +8.0848E+01
+8.1113E-01  +1.9619E-01  +7.8796E+01
+1.0000E+00  +1.9432E-01  +7.6298E+01
+1.2329E+00  +1.9151E-01  +7.3290E+01
+1.5199E+00  +1.8781E-01  +6.9669E+01
+1.8738E+00  +1.8220E-01  +6.5470E+01
+2.3101E+00  +1.7438E-01  +6.0673E+01
+2.8480E+00  +1.6449E-01  +5.5318E+01
+3.5112E+00  +1.5217E-01  +4.9534E+01
+4.3288E+00  +1.3785E-01  +4.3570E+01
+5.3367E+00  +1.2226E-01  +3.7647E+01
+6.5793E+00  +1.0622E-01  +3.2028E+01
+8.1113E+00  +9.0622E-02  +2.6892E+01
+1.0000E+01  +7.6212E-02  +2.2313E+01
+1.2329E+01  +6.3445E-02  +1.8389E+01
+1.5199E+01  +5.2247E-02  +1.5069E+01
+1.8738E+01  +4.2964E-02  +1.2247E+01
+2.3101E+01  +3.5147E-02  +9.8945E+00
+2.8480E+01  +2.8661E-02  +8.0352E+00
+3.5112E+01  +2.3266E-02  +6.4090E+00
+4.3288E+01  +1.8976E-02  +5.1069E+00
+5.3367E+01  +1.5470E-02  +3.9140E+00
+6.5793E+01  +1.2495E-02  +3.0121E+00
+8.1113E+01  +1.0154E-02  +2.2737E+00
+1.0000E+02  +8.2466E-03  +1.5682E+00
+1.2329E+02  +6.6826E-03  +8.7741E-01
+1.5199E+02  +5.4120E-03  +3.2423E-01
+1.8738E+02  +4.3959E-03  -2.9814E-01
+2.3101E+02  +3.5657E-03  -8.7995E-01
+2.8480E+02  +2.8912E-03  -1.5233E+00
+3.5112E+02  +2.3408E-03  -2.1688E+00
+4.3288E+02  +1.9000E-03  -3.0046E+00
+5.3367E+02  +1.5399E-03  -3.9069E+00
+6.5793E+02  +1.2478E-03  -4.9982E+00
+8.1113E+02  +1.0105E-03  -6.2920E+00
+1.0000E+03  +8.1795E-04  -7.8551E+00
+1.2329E+03  +6.6137E-04  -9.7494E+00
+1.5199E+03  +5.3415E-04  -1.2052E+01
+1.8738E+03  +4.3092E-04  -1.4858E+01
+2.3101E+03  +3.4918E-04  -1.8440E+01
+2.8480E+03  +2.7656E-04  -2.3375E+01
+3.5112E+03  +2.1821E-04  -2.8876E+01
+4.3288E+03  +1.6854E-04  -3.6126E+01
+5.3367E+03  +1.2103E-04  -4.4781E+01
+6.5793E+03  +8.0732E-05  -4.5047E+01
+8.1113E+03  +6.6265E-05  -4.4906E+01
+1.0000E+04  +5.3269E-05  -5.2671E+01


FREQUENCY    MAGNITUDE    PHASE
Hz           V/(nT*Hz)    deg
Chopper Off
+1.0000E+00  +1.8929E-01  +1.1098E+02
+1.2329E+00  +1.9877E-01  +1.0178E+02
+1.5199E+00  +2.0310E-01  +9.2408E+01
+1.8738E+00  +2.0205E-01  +8.2979E+01
+2.3101E+00  +1.9525E-01  +7.3551E+01
+2.8480E+00  +1.8380E-01  +6.4373E+01
+3.5112E+00  +1.6822E-01  +5.5546E+01
+4.3288E+00  +1.4990E-01  +4.7282E+01
+5.3367E+00  +1.3074E-01  +3.9765E+01
+6.5793E+00  +1.1170E-01  +3.3138E+01
+8.1113E+00  +9.4077E-02  +2.7353E+01
+1.0000E+01  +7.8347E-02  +2.2450E+01
+1.2329E+01  +6.4595E-02  +1.8346E+01
+1.5199E+01  +5.2931E-02  +1.4879E+01
+1.8738E+01  +4.3376E-02  +1.2120E+01
+2.3101E+01  +3.5403E-02  +9.7871E+00
+2.8480E+01  +2.8760E-02  +7.8304E+00
+3.5112E+01  +2.3408E-02  +6.1176E+00
+4.3288E+01  +1.8972E-02  +4.9934E+00
+5.3367E+01  +1.5398E-02  +4.0097E+00
+6.5793E+01  +1.2525E-02  +2.9188E+00
+8.1113E+01  +1.0166E-02  +2.1264E+00
+1.0000E+02  +8.2561E-03  +1.5565E+00
+1.2329E+02  +6.6903E-03  +8.5820E-01
+1.5199E+02  +5.4239E-03  +2.0043E-01
+1.8738E+02  +4.4025E-03  -3.0994E-01
+2.3101E+02  +3.5698E-03  -9.1526E-01
+2.8480E+02  +2.8946E-03  -1.5153E+00
+3.5112E+02  +2.3489E-03  -2.2518E+00
+4.3288E+02  +1.9026E-03  -2.9940E+00
+5.3367E+02  +1.5419E-03  -3.8998E+00
+6.5793E+02  +1.2494E-03  -4.9771E+00
+8.1113E+02  +1.0118E-03  -6.2748E+00
+1.0000E+03  +8.1898E-04  -7.8198E+00
+1.2329E+03  +6.6221E-04  -9.7244E+00
+1.5199E+03  +5.3482E-04  -1.2031E+01
+1.8738E+03  +4.3142E-04  -1.4824E+01
+2.3101E+03  +3.4914E-04  -1.8435E+01
+2.8480E+03  +2.7691E-04  -2.3267E+01
+3.5112E+03  +2.1863E-04  -2.8766E+01
+4.3288E+03  +1.6899E-04  -3.6023E+01
+5.3367E+03  +1.2149E-04  -4.4705E+01
+6.5793E+03  +8.1011E-05  -4.5075E+01
+8.1113E+03  +6.6395E-05  -4.4922E+01
+1.0000E+04  +5.3337E-05  -5.2637E+01

ATSS header

sats stands for advanced timeseries stream.
The stream data itself is stored in .atss as pure binary data streams, 64 bit double IEEE 754 as most processor architectures use it.
The double is is used by default in Python/NumPy, MatLab, C++ and others.

The JSON header is separated from the stream. This allows a continuous data transmission where the stream data only changes at the end. The sync client can use “rsync –append” in order to resume the data transfer.

The JSON header can be transferred with the “rsync -z” option (compress) because it is an ASCII file.

In addition a .sql3 (SQLite file version 3) is written into the data directory, containing the log data of the recording.

The filename is splitted by the underscore tag _ with a default length of 3 and a letter bit_indicator

008_C01_R000_TEy_512H.sats ADU-08e data stream recorded with channel 1 as Ey with 512 Hz
067_C00_R001_TEx_8s.sats ADU-08e data stream recorded with channel 0 as Ex with one sample every 4 seconds

serial ADU

Channel 0 - N

Run 0 - N

Type (of channel)

Sample Frequency or Sample Period

H(ertz) or S(econds)

ext

008

C001

R000

TEy

512

H

atss

067

C000

R001

TEx

8

s

atss

.