Saving .sac files of a given time duration from a miniSEED stream

Hello, I have a Seedlink client that receives miniSEED packets from a Ringserver. I would like to be able to save .sac files of a given time duration from a given start time from this stream.

So for example let’s say I’m receiving a hour of miniSEED packets, I’d like to be able to create 6 .sac files containing each 10 minutes of recordings since the start of the stream, or 2 .sac files of 15 minutes each after the first half hour and so on.

I had already done something similar simply counting the packets received, like this:

def handle_data(trace):
    print('Received the following trace:')
    #print(trace.data)
    print(trace.data)
    st.append(trace)
    if(st.count()==1000):  #every 1000 packets, save a .sac file and reset the stream
        seed2sac(st)
        st.clear()

def seed2sac(st): 
    working_dir="C:/Users/User/Desktop/Client/miniSEED/"
    st.merge(method=0, fill_value=None)
    st[0].data=st[0].data.compressed() #merging the traces in the stream first
    outfile = str(st[0].stats.starttime).replace(':', '_') + '.SAC'
    outfile = os.path.join(working_dir, outfile)
    st.write(outfile, format='SAC')

but I would like to have more control over how I’m saving the files and the time duration of each .sac file. I was thinking of a simple

if st.count()>1:
    if (st[st.count()-1].stats.endtime.timestamp-st[0].stats.starttime.timestamp>600):
         seed2sac(st)
         st.clear()

but it seems kind of clunky. Any help or any better solution would be very appreciated :slightly_smiling_face:

I would probably do this using a class borrowing from the EasySeedlinkClient example.

Something like:

from obspy import Stream
from obspy.clients.seedlink.easyseedlink import EasySeedLinkClient

class Seedlink2SAC(EasySeedLinkClient):
    st = Stream()
    
    def __init__(self, save_length, server_url, autoconnect=True):
        self.save_length = save_length
        super().__init__(server_url=server_url, autoconnect=autoconnect)

    def on_data(self, trace):
        # Let obspy work out what to do with the trace
        self.st += trace
        self.st.merge()  # Ensure we get one trace for each seed ID
        for tr in self.st:
            if (tr.stats.endtime - tr.stats.starttime) >= self.save_length:
                trim_start = tr.stats.starttime
                trim_end = trim_start + self.save_length
                # Use your function here if you want
                seed2sac(tr.slice(trim_start, trim_end)
                # Remove old data
                tr.trim(trim_end, self.tr.stats.endtime)

I haven’t tested this, but hopefully it gives you the idea. The main things are to exploit the + of adding traces to streams and using the start and endtime of traces rather than counting packets. Note that .slice creates a view of the data so won’t remove data from self.st whereas .trim works in place on data and removes data that isn’t in the window required, so the final call to tr.trim should remove old data that you don’t need anymore.

Thanks for the answer! I ended up doing something like this

def handle_data(trace):

    st.append(trace)

    if st.count()>1:

        if streamDuration(st)>600:

            seed2sac(st)

            st.clear()

with streamDuration and seed2sac defined as follows

def streamDuration(st):

    return st[st.count()-1].stats.endtime.timestamp-st[0].stats.starttime.timestamp

def seed2sac(st):

    working_dir="C:/Users/Desktop/scriptClient/miniSEED/data/"

    st.merge(method=0, fill_value=None)

    st[0].data=st[0].data.compressed()

    outfile = str(st[0].stats.starttime).replace(':', '_') + '.SAC'

    outfile = os.path.join(working_dir, outfile)

    st.write(outfile, format='SAC')

I didn’t use the slice and trim functions, which might actually be the better options. I will try your solution and see if it works better. Do you think that using the + method is better than using append like I do in my code?

I don’t think it matters whether you use + or .append(). I just prefer the syntax of +. My code was missing a necessary .merge(). I have edited the code example to include that now.

At the moment your code will probably do unexpected things if you use it to handle data from multiple channels or stations, I’m also unsure of where st is defined. I assume that you are using a global variable for this, which is often not very safe in python and is one of the reasons I would recommend using a class for this.

One of the nice things about UTCDateTime objects is that when you subtract one from another you get seconds, so you don’t need to access the .timestamp when you check the length. You also don’t need to index using st.count() - 1, you can just index the last trace as -1 as you would in other lists in Python. Your indexing does assume that your traces are ordered, which they should be, but using a .merge after you .append your new trace to your stream will ensure that you only have one Trace per seed ID, so you can use the for tr in st syntax in my example.

If you want all your files to be the same length then I would recommend the slice and trim version to ensure that all files are your desired length, and that you don’t then remove data that you still want.

Yes, I was using a global variable instead of the class.

Thanks again for the suggestions! I’m fairly new with obspy and not that well versed in Python yet, so this has been very useful.