Sintela DAS Raw Data v3 File Reader

P. McGill
24Mar25

Create a function to read  Sintela DAS V3 format files, which was  initially developed in the notebook SintelaReadRawV3.nb according to the documentation at

 https://sintela.atlassian.net/wiki/spaces/DEMO/pages/336917/Phase+Data+Sensor+Data+Sintela+Binary+Format.

This updated version of the function allows the user to select specific channels and times from which to import data in order to make working with large Sintela data files much easier.

sintelaReadRawV3[fileName_String,OptionsPattern[{firstChan->1,lastChan->-1,startTime->{1970},endTime->{2050},verbose->False}]]:= Module[ (* v1.2.0 of this function *)
{fileSizeBytes,inStream,syncWord,hdrSize,hdrVers,sampCnt,sampTime,numChans,numSamps,sampRate,chanSpacing,gaugeLen,trigger,startChan,chanStep,gpsPPSTime,gpsPPSSampOffs,gpsStat,trigFlags,statFlags,packetSizeBytes,totalPackets,totalSamps,packet,sampTimeOffset=20,startTimeSecs,endTimeSecs,rdBuf,data={},firstPacket=True,firstSampTime},

(* Decode header information *)
fileSizeBytes=UnitConvert[FileSize[filename],"Bytes"]//QuantityMagnitude;
inStream=OpenRead[fileName,BinaryFormat->True];
syncWord=BinaryRead[inStream, "UnsignedInteger32"] (* offset 0 *);
hdrSize=BinaryRead[inStream, "UnsignedInteger32"] (* offset 4 *);
hdrVers=BinaryRead[inStream, "UnsignedInteger32"] (* offset 8 *);
If[OptionValue[verbose],
If[
syncWord==16^^11223344&&hdrSize==96,
Print["Sintela Data Type 3 - Raw 32 Bit."], 
(Print["Not a Sintela Type 3 File."];Close[inStream];Abort[])
] ;(* end if *)
]; (* end if *)
sampCnt=BinaryRead[inStream, "UnsignedInteger64"] (* offset 12 *);
sampTime=BinaryRead[inStream,"UnsignedInteger64"] (* offset 20 *);
numChans=BinaryRead[inStream,"UnsignedInteger32"] (* offset 28 *);
numSamps=BinaryRead[inStream,"UnsignedInteger32"] (* offset 32 *);
sampRate=BinaryRead[inStream,"Real32"] (* offset 36 *);
chanSpacing=BinaryRead[inStream,"Real32"] (* offset 40 *);
gaugeLen=BinaryRead[inStream,"Real32"] (* offset 44 *);
trigger=BinaryRead[inStream,"UnsignedInteger32"] (* offset 48 *);
startChan=BinaryRead[inStream,"UnsignedInteger32"] (* offset 52 *);
chanStep=BinaryRead[inStream,"UnsignedInteger32"] (* offset 56 *);
gpsPPSTime=BinaryRead[inStream,"UnsignedInteger32"] (* offset 60 *);
gpsPPSSampOffs=BinaryRead[inStream,"Integer32"] (* offset 64 *);
gpsStat=BinaryRead[inStream,"UnsignedInteger32"] (* offset 68 *);
trigFlags=BinaryRead[inStream,"UnsignedInteger8"] (* offset 72 *);
statFlags=BinaryRead[inStream,"UnsignedInteger8"] (* offset 73 *);

(* Display first header *)
If[OptionValue[verbose],
Print["Packet Channels x Samples : ",numChans," x ",numSamps];Print["Start Channel: ",startChan];
Print["Fs: ",NumberForm[sampRate,{8,3}]," Hz"] ;Print["Channel Spacing: ",NumberForm[chanSpacing,{4,1}]," m"];
Print["Gauge Length: ",NumberForm[gaugeLen,{4,1}]," m"];

Print["File Start Time: ",DateString[FromUnixTime[sampTime/10^9,TimeZone->"GMT"]],".",Mod[sampTime,10^9]," GMT"];
]; (* end If *)
packetSizeBytes=(hdrSize+(numChans*numSamps*4));
totalPackets=Round[fileSizeBytes/packetSizeBytes];
totalSamps=totalPackets*numSamps;
If[OptionValue[verbose],
Print["Total Samples Per Channel: ",totalSamps];
Print["Total Packets: ",totalPackets];
Print["Bytes per Packet: ",packetSizeBytes];
]; (* end If *)

(* Convert start and end times to UnixTime seconds *)
startTimeSecs=Block[{$TimeZone=0},UnixTime[OptionValue[startTime]]];
endTimeSecs=Block[{$TimeZone=0},UnixTime[OptionValue[endTime]]];

(* Read and extract data samples (skipping over header blocks) *)
SetStreamPosition[inStream,0];(* Rewind file *)
For[packet=1,packet<=totalPackets,packet++,
(* skip to sample time of current packet *)
SetStreamPosition[inStream,StreamPosition[inStream]+sampTimeOffset];
sampTime=BinaryRead[inStream,"UnsignedInteger64"]; (* read sample time *)
(* skip to end of header *)
SetStreamPosition[inStream,StreamPosition[inStream]+hdrSize-sampTimeOffset-8];
(* read samples if they're within requested time window, and keep only requested channels *)
If[startTimeSecs<(sampTime/10^9)<endTimeSecs,
rdBuf=Partition[BinaryReadList[inStream,"Real32",numChans*numSamps],numChans][[All,OptionValue[firstChan];;OptionValue[lastChan]]];
AppendTo[data,rdBuf];
(* if this is first saved packet, keep the time *)
If[firstPacket,firstSampTime=sampTime;firstPacket=False],
(* if packet not within requested time window, skip to start of next packet *)
SetStreamPosition[inStream,StreamPosition[inStream]+(numChans*numSamps*4)] 
]; (* end If *)
If[(sampTime/10^9)>endTimeSecs,Break[]]; (* stop if all requested samples have been read *)
]; (* end For *)
Close[inStream];

(* Data are now in sublists for each packet and we must rearrange to only sublists of requested channels. If no data at all were extracted from file, then just print message and quit *)
If[!firstPacket, (* if data in time range were found *)
If[OptionValue[firstChan]!=OptionValue[lastChan], (* if more than one channel selected *)
data=Flatten[data,1],
data=Flatten[data,2] (* else only one channel selected *)
]; (* end If *)
If[OptionValue[verbose],
If[OptionValue[firstChan]!=OptionValue[lastChan], (* if more than one channel selected *)
Print["Output Data Channels x Samples : ",Dimensions[data][[2]]," x ",Dimensions[data][[1]]],
Print["Output Data Channels x Samples : 1 x ",Length[data]] (* else only one channel selected *)
]; (* end If *)
Print["First Output Sample Time: ",DateString[FromUnixTime[firstSampTime/10^9,TimeZone->"GMT"]],".",Mod[firstSampTime,10^9]," GMT"]
]; (* end If *),
(* else if no data in time range were found *)
Print["### No data in this file for selected time range ###"];
Abort[];
] ;(* end If *)

Return[{firstSampTime,data}]
] (* end module *)

Test ability to read selected channels and time range.

filename="/Volumes/ProjectLibrary/902401_GeoSense/Project.Data/Deployments/DAS_2025_01_28/Geo-Sense\ data/decimator_2025-02-01_05.15.23_UTC_000481.raw";

{time,dasDat}=sintelaReadRawV3[filename,firstChan->82,lastChan->85,startTime->{2025,2,1,5,19,40},endTime->{2025,2,1,5,19,50},verbose->True];

FromUnixTime[time/10^9,TimeZone->"UTC"]

ListPlot[dasDat[[All,4]],Frame->True]

Ask for data at a time not in the file in order to test time-range checking.

{time,dasDat}=sintelaReadRawV3[filename,firstChan->82,lastChan->85,startTime->{2026,2,1,5,19,40},endTime->{2026,2,1,5,19,50}];

Ask for data with default options to test that all data are read.

{time,dasDat}=sintelaReadRawV3[filename];

Dimensions@dasDat

Test for ability to select only one channel.

{time,dasDat}=sintelaReadRawV3[filename,firstChan->85,lastChan->85,startTime->{2025,2,1,5,19,40},endTime->{2025,2,1,5,19,50},verbose->True];

ListPlot[dasDat,Frame->True]