def __init__(self, trace, data, dataquality): """ The init function requires a ObsPy Trace object which will be used to fill self.mstg. """ self.mst = clibmseed.mst_init(None) # Figure out the datatypes. sampletype = SAMPLETYPE[data.dtype.type] # Set the header values. self.mst.contents.network = trace.stats.network self.mst.contents.station = trace.stats.station self.mst.contents.location = trace.stats.location self.mst.contents.channel = trace.stats.channel self.mst.contents.dataquality = dataquality self.mst.contents.type = "\x00" self.mst.contents.starttime = util._convertDatetimeToMSTime(trace.stats.starttime) self.mst.contents.endtime = util._convertDatetimeToMSTime(trace.stats.endtime) self.mst.contents.samprate = trace.stats.sampling_rate self.mst.contents.samplecnt = trace.stats.npts self.mst.contents.numsamples = trace.stats.npts self.mst.contents.sampletype = sampletype # libmseed expects data in the native byteorder. if data.dtype.byteorder != "=": data = data.byteswap() # Copy the data. The copy appears to be necessary so that Python's # garbage collection does not interfere it. bytecount = data.itemsize * data.size self.mst.contents.datasamples = clibmseed.allocate_bytes(bytecount) C.memmove(self.mst.contents.datasamples, data.ctypes.get_data(), bytecount)
def __init__(self, trace, data, dataquality): """ The init function requires a ObsPy Trace object which will be used to fill self.mstg. """ # Initialize MSTraceGroup mstg = clibmseed.mst_initgroup(None) self.mstg = mstg # Set numtraces. mstg.contents.numtraces = 1 # Initialize MSTrace object and connect with group mstg.contents.traces = clibmseed.mst_init(None) chain = mstg.contents.traces # Figure out the datatypes. sampletype = SAMPLETYPE[data.dtype.type] c_dtype = DATATYPES[sampletype] # Set the header values. chain.contents.network = trace.stats.network chain.contents.station = trace.stats.station chain.contents.location = trace.stats.location chain.contents.channel = trace.stats.channel chain.contents.dataquality = dataquality chain.contents.type = '\x00' chain.contents.starttime = \ util._convertDatetimeToMSTime(trace.stats.starttime) chain.contents.endtime = \ util._convertDatetimeToMSTime(trace.stats.endtime) chain.contents.samprate = trace.stats.sampling_rate chain.contents.samplecnt = trace.stats.npts chain.contents.numsamples = trace.stats.npts chain.contents.sampletype = sampletype # Create a single datapoint and resize its memory to be able to # hold all datapoints. tempdatpoint = c_dtype() datasize = SAMPLESIZES[sampletype] * trace.stats.npts # XXX: Ugly workaround for traces with less than 17 data points if datasize < 17: datasize = 17 C.resize(tempdatpoint, datasize) # The datapoints in the MSTG structure are a pointer to the memory # area reserved for tempdatpoint. chain.contents.datasamples = C.cast(C.pointer(tempdatpoint), C.c_void_p) # Swap if wrong byte order because libmseed expects native byteorder. if data.dtype.byteorder != "=": data = data.byteswap() # Pointer to the NumPy data buffer. datptr = data.ctypes.get_data() # Manually move the contents of the NumPy data buffer to the # address of the previously created memory area. C.memmove(chain.contents.datasamples, datptr, datasize)
def __init__(self, trace, data, dataquality): """ The init function requires a ObsPy Trace object which will be used to fill self.mstg. """ self.mst = clibmseed.mst_init(None) # Figure out the datatypes. sampletype = SAMPLETYPE[data.dtype.type] # Set the header values. self.mst.contents.network = trace.stats.network self.mst.contents.station = trace.stats.station self.mst.contents.location = trace.stats.location self.mst.contents.channel = trace.stats.channel self.mst.contents.dataquality = dataquality self.mst.contents.type = '\x00' self.mst.contents.starttime = \ util._convertDatetimeToMSTime(trace.stats.starttime) self.mst.contents.endtime = \ util._convertDatetimeToMSTime(trace.stats.endtime) self.mst.contents.samprate = trace.stats.sampling_rate self.mst.contents.samplecnt = trace.stats.npts self.mst.contents.numsamples = trace.stats.npts self.mst.contents.sampletype = sampletype # libmseed expects data in the native byteorder. if data.dtype.byteorder != "=": data = data.byteswap() # Copy the data. The copy appears to be necessary so that Python's # garbage collection does not interfere it. bytecount = data.itemsize * data.size self.mst.contents.datasamples = clibmseed.allocate_bytes(bytecount) C.memmove(self.mst.contents.datasamples, data.ctypes.get_data(), bytecount)
def writeMSEED(stream, filename, encoding=None, reclen=None, byteorder=None, flush=1, verbose=0, **_kwargs): """ Write Mini-SEED file from a Stream object. .. warning:: This function should NOT be called directly, it registers via the the :meth:`~obspy.core.stream.Stream.write` method of an ObsPy :class:`~obspy.core.stream.Stream` object, call this instead. :type stream: :class:`~obspy.core.stream.Stream` :param stream: A Stream object. :type filename: str :param filename: Name of the output file :type encoding: int or str, optional :param encoding: Should be set to one of the following supported Mini-SEED data encoding formats: ASCII (``0``)*, INT16 (``1``), INT32 (``3``), FLOAT32 (``4``)*, FLOAT64 (``5``)*, STEIM1 (``10``) and STEIM2 (``11``)*. Default data types a marked with an asterisk. Currently INT24 (``2``) is not supported due to lacking NumPy support. :type reclen: int, optional :param reclen: Should be set to the desired data record length in bytes which must be expressible as 2 raised to the power of X where X is between (and including) 8 to 20. Defaults to 4096 :type byteorder: [``0`` or ``'<'`` | ``1`` or ``'>'`` | ``'='``], optional :param byteorder: Must be either ``0`` or ``'<'`` for LSBF or little-endian, ``1`` or ``'>'`` for MBF or big-endian. ``'='`` is the native byteorder. If ``-1`` it will be passed directly to libmseed which will also default it to big endian. Defaults to big endian. :type flush: int, optional :param flush: If it is not zero all of the data will be packed into records, otherwise records will only be packed while there are enough data samples to completely fill a record. :type verbose: int, optional :param verbose: Controls verbosity, a value of zero will result in no diagnostic output. .. note:: The reclen, encoding and byteorder keyword arguments can be set in the stats.mseed of each :class:`~obspy.core.trace.Trace` as well as as kwargs of this function. If both are given the kwargs will be used. .. rubric:: Example >>> from obspy.core import read >>> st = read() >>> st.write('filename.mseed', format='MSEED') # doctest: +SKIP """ # Some sanity checks for the keyword arguments. if reclen is not None and reclen not in VALID_RECORD_LENGTHS: msg = ( "Invalid record length. The record length must be a value\n" + "of 2 to the power of X where 8 <= X <= 20." ) raise ValueError(msg) if byteorder is not None and byteorder not in [0, 1, -1]: if byteorder == "=": byteorder = NATIVE_BYTEORDER # If not elif because NATIVE_BYTEORDER is '<' or '>'. if byteorder == "<": byteorder = 0 elif byteorder == ">": byteorder = 1 else: msg = "Invalid byteorder. It must be either '<', '>', '=', " + "0, 1 or -1" raise ValueError(msg) # Check if encoding kwarg is set and catch invalid encodings. # XXX: Currently INT24 is not working due to lacking NumPy support. encoding_strings = dict([(v[0], k) for (k, v) in ENCODINGS.iteritems()]) if encoding is not None: if isinstance(encoding, int) and encoding in ENCODINGS: pass elif encoding and isinstance(encoding, basestring) and encoding in encoding_strings: encoding = encoding_strings[encoding] else: msg = "Invalid encoding %s. Valid encodings: %s" raise ValueError(msg % (encoding, encoding_strings)) trace_attributes = [] use_blkt_1001 = 0 # The data might need to be modified. To not modify the input data keep # references of which data to finally write. trace_data = [] # Loop over every trace and figure out the correct settings. for _i, trace in enumerate(stream): # Create temporary dict for storing information while writing. trace_attr = {} trace_attributes.append(trace_attr) stats = trace.stats # Figure out whether or not to use Blockette 1001. This check is done # once to ensure that Blockette 1001 is either written for every record # in the file or for none. It checks the starttime as well as the # sampling rate. If either one has a precision of more than 100 # microseconds, Blockette 1001 will be written for every record. starttime = util._convertDatetimeToMSTime(trace.stats.starttime) if starttime % 100 != 0 or (1.0 / trace.stats.sampling_rate * HPTMODULUS) % 100 != 0: use_blkt_1001 += 1 # Determine if a blockette 100 will be needed to represent the input # sample rate or if the sample rate in the fixed section of the data # header will suffice (see ms_genfactmult in libmseed/genutils.c) if trace.stats.sampling_rate >= 32727.0 or trace.stats.sampling_rate <= (1.0 / 32727.0): use_blkt_100 = True else: use_blkt_100 = False # Set data quality to indeterminate (= D) if it is not already set. try: trace_attr["dataquality"] = trace.stats["mseed"]["dataquality"].upper() except: trace_attr["dataquality"] = "D" # Sanity check for the dataquality to get a nice Python exception # instead of a C error. if trace_attr["dataquality"] not in ["D", "R", "Q", "M"]: msg = ( "Invalid dataquality in Stream[%i].stats" % _i + ".mseed.dataquality\n" + "The dataquality for Mini-SEED must be either D, R, Q " + "or M. See the SEED manual for further information." ) raise ValueError(msg) # Check that data is of the right type. if not isinstance(trace.data, np.ndarray): msg = "Unsupported data type %s" % type(trace.data) + " for Stream[%i].data." % _i raise ValueError(msg) # Check if ndarray is contiguous (see #192, #193) if not trace.data.flags.c_contiguous: msg = "Detected non contiguous data array in Stream[%i]" % _i + ".data. Trying to fix array." warnings.warn(msg) trace.data = np.require(trace.data, requirements=("C_CONTIGUOUS",)) # Handle the record length. if reclen is not None: trace_attr["reclen"] = reclen elif hasattr(stats, "mseed") and hasattr(stats.mseed, "record_length"): if stats.mseed.record_length in VALID_RECORD_LENGTHS: trace_attr["reclen"] = stats.mseed.record_length else: msg = ( "Invalid record length in Stream[%i].stats." % _i + "mseed.reclen.\nThe record length must be a value " + "of 2 to the power of X where 8 <= X <= 20." ) raise ValueError(msg) else: trace_attr["reclen"] = 4096 # Handle the byteorder. if byteorder is not None: trace_attr["byteorder"] = byteorder elif hasattr(stats, "mseed") and hasattr(stats.mseed, "byteorder"): if stats.mseed.byteorder in [0, 1, -1]: trace_attr["byteorder"] = stats.mseed.byteorder elif stats.mseed.byteorder == "=": if NATIVE_BYTEORDER == "<": trace_attr["byteorder"] = 0 else: trace_attr["byteorder"] = 1 elif stats.mseed.byteorder == "<": trace_attr["byteorder"] = 0 elif stats.mseed.byteorder == ">": trace_attr["byteorder"] = 1 else: msg = ( "Invalid byteorder in Stream[%i].stats." % _i + "mseed.byteorder. It must be either '<', '>', '='," + " 0, 1 or -1" ) raise ValueError(msg) else: trace_attr["byteorder"] = 1 if trace_attr["byteorder"] == -1: if NATIVE_BYTEORDER == "<": trace_attr["byteorder"] = 0 else: trace_attr["byteorder"] = 1 # Handle the encoding. trace_attr["encoding"] = None if encoding is not None: # Check if the dtype for all traces is compatible with the enforced # encoding. id, _, dtype = ENCODINGS[encoding] if trace.data.dtype.type != dtype: msg = """ Wrong dtype for Stream[%i].data for encoding %s. Please change the dtype of your data or use an appropriate encoding. See the obspy.mseed documentation for more information. """ % ( _i, id, ) raise Exception(msg) trace_attr["encoding"] = encoding elif hasattr(trace.stats, "mseed") and hasattr(trace.stats.mseed, "encoding"): mseed_encoding = stats.mseed.encoding # Check if the encoding is valid. if isinstance(mseed_encoding, int) and mseed_encoding in ENCODINGS: trace_attr["encoding"] = mseed_encoding elif isinstance(mseed_encoding, basestring) and mseed_encoding in encoding_strings: trace_attr["encoding"] = encoding_strings[mseed_encoding] else: msg = "Invalid encoding %s in " + "Stream[%i].stats.mseed.encoding. Valid encodings: %s" raise ValueError(msg % (mseed_encoding, _i, encoding_strings)) # Check if the encoding matches the data's dtype. if trace.data.dtype.type != ENCODINGS[trace_attr["encoding"]][2]: msg = ( "The encoding specified in " + "trace.stats.mseed.encoding does not match the " + "dtype of the data.\nA suitable encoding will " + "be chosen." ) warnings.warn(msg, UserWarning) trace_attr["encoding"] = None # automatically detect encoding if no encoding is given. if not trace_attr["encoding"]: if trace.data.dtype.type == np.dtype("int32"): trace_attr["encoding"] = 11 elif trace.data.dtype.type == np.dtype("float32"): trace_attr["encoding"] = 4 elif trace.data.dtype.type == np.dtype("float64"): trace_attr["encoding"] = 5 elif trace.data.dtype.type == np.dtype("int16"): trace_attr["encoding"] = 1 elif trace.data.dtype.type == np.dtype("|S1").type: trace_attr["encoding"] = 0 else: msg = "Unsupported data type %s in Stream[%i].data" % (trace.data.dtype, _i) raise Exception(msg) # Convert data if necessary, otherwise store references in list. if trace_attr["encoding"] == 1: # INT16 needs INT32 data type trace_data.append(trace.data.copy().astype(np.int32)) else: trace_data.append(trace.data) # Do some final sanity checks and raise a warning if a file will be written # with more than one different encoding, record length or byteorder. encodings = set([_i["encoding"] for _i in trace_attributes]) reclens = set([_i["reclen"] for _i in trace_attributes]) byteorders = set([_i["byteorder"] for _i in trace_attributes]) msg = ( "File will be written with more than one different %s.\n" + "This might have a negative influence on the compatibility " + "with other programs." ) if len(encodings) != 1: warnings.warn(msg % "encodings") if len(reclens) != 1: warnings.warn(msg % "record lengths") if len(byteorders) != 1: warnings.warn(msg % "byteorders") # Open filehandler or use an existing file like object. if not hasattr(filename, "write"): f = open(filename, "wb") else: f = filename # Loop over every trace and finally write it to the filehandler. for trace, data, trace_attr in izip(stream, trace_data, trace_attributes): # Create C struct MSTraceGroup. mstg = MSTG(trace, data, dataquality=trace_attr["dataquality"]) # Initialize packedsamples pointer for the mst_pack function packedsamples = C.c_int() # Callback function for mst_pack to actually write the file def record_handler(record, reclen, _stream): f.write(record[0:reclen]) # Define Python callback function for use in C function recHandler = C.CFUNCTYPE(C.c_void_p, C.POINTER(C.c_char), C.c_int, C.c_void_p)(record_handler) # Fill up msr record structure, this is already contained in # mstg, however if blk1001 is set we need it anyway msr = clibmseed.msr_init(None) msr.contents.network = trace.stats.network msr.contents.station = trace.stats.station msr.contents.location = trace.stats.location msr.contents.channel = trace.stats.channel msr.contents.dataquality = trace_attr["dataquality"] # Only use Blockette 1001 if necessary. if use_blkt_1001: size = C.sizeof(blkt_1001_s) blkt1001 = C.c_char(" ") C.memset(C.pointer(blkt1001), 0, size) ret_val = clibmseed.msr_addblockette(msr, C.pointer(blkt1001), size, 1001, 0) # Usually returns a pointer to the added blockette in the # blockette link chain and a NULL pointer if it fails. # NULL pointers have a false boolean value according to the # ctypes manual. if bool(ret_val) is False: clibmseed.msr_free(C.pointer(msr)) del mstg, msr raise Exception("Error in msr_addblockette") # Only use Blockette 100 if necessary. if use_blkt_100: size = C.sizeof(blkt_100_s) blkt100 = C.c_char(" ") C.memset(C.pointer(blkt100), 0, size) ret_val = clibmseed.msr_addblockette(msr, C.pointer(blkt100), size, 100, 0) # Usually returns a pointer to the added blockette in the # blockette link chain and a NULL pointer if it fails. # NULL pointers have a false boolean value according to the # ctypes manual. if bool(ret_val) is False: clibmseed.msr_free(C.pointer(msr)) del mstg, msr raise Exception("Error in msr_addblockette") # Pack mstg into a MSEED file using the callback record_handler as # write method. errcode = clibmseed.mst_packgroup( mstg.getMstg(), recHandler, None, trace_attr["reclen"], trace_attr["encoding"], trace_attr["byteorder"], C.byref(packedsamples), flush, verbose, msr, ) if errcode == 0: msg = 'Skipping empty trace "%s".' % (trace) warnings.warn(msg) if errcode == -1: clibmseed.msr_free(C.pointer(msr)) del mstg, msr raise Exception("Error in mst_packgroup") # Deallocate any allocated memory. clibmseed.msr_free(C.pointer(msr)) del mstg, msr # Close if its a file handler. if isinstance(f, file): f.close()
def readMSEED( mseed_object, starttime=None, endtime=None, headonly=False, sourcename=None, reclen=None, recinfo=True, **kwargs ): """ Reads a Mini-SEED file and returns a Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param mseed_object: Filename or open file like object that contains the binary Mini-SEED data. Any object that provides a read() method will be considered to be a file like object. :type starttime: UTCDateTime :param starttime: Only read data samples after or at the starttime. :type endtime: UTCDateTime :param endtime: Only read data samples before or at the starttime. :param headonly: Determines whether or not to unpack the data or just read the headers. :type sourcename: str :param sourcename: Sourcename has to have the structure 'network.station.location.channel' and can contain globbing characters. Defaults to ``None``. :param reclen: If it is None, it will be automatically determined for every record. If it is known, just set it to the record length in bytes which will increase the reading speed slightly. :type recinfo: bool, optional :param recinfo: If ``True`` the byteorder, record length and the encoding of the file will be read and stored in every Trace's stats.mseed AttribDict. These stored attributes will also be used while writing a Mini-SEED file. Only the very first record of the file will be read and all following records are assumed to be the same. Defaults to ``True``. .. rubric:: Example >>> from obspy.core import read >>> st = read("/path/to/two_channels.mseed") >>> print(st) # doctest: +ELLIPSIS 2 Trace(s) in Stream: BW.UH3..EHE | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples BW.UH3..EHZ | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples >>> from obspy.core import UTCDateTime >>> st = read("/path/to/test.mseed", ... starttime=UTCDateTime("2003-05-29T02:16:00"), ... selection="NL.*.*.?HZ") >>> print(st) # doctest: +ELLIPSIS 1 Trace(s) in Stream: NL.HGN.00.BHZ | 2003-05-29T02:15:59.993400Z - ... | 40.0 Hz, 5629 samples """ # Parse the headonly and reclen flags. if headonly is True: unpack_data = 0 else: unpack_data = 1 if reclen is None: reclen = -1 elif reclen is not None and reclen not in VALID_RECORD_LENGTHS: msg = "Invalid record length. Autodetection will be used." warnings.warn(msg) reclen = -1 else: reclen = int(log(reclen, 2)) # The quality flag is no more supported. Raise a warning. if "quality" in kwargs: msg = ( "The quality flag is no more supported in this version of " + "obspy.mseed. obspy.mseed.util has some functions with similar " + "behaviour." ) warnings.warn(msg, category=DeprecationWarning) # Parse some information about the file. if recinfo: info = util.getRecordInformation(mseed_object) info["encoding"] = ENCODINGS[info["encoding"]][0] # Only keep information relevant for the whole file. info = { "encoding": info["encoding"], "filesize": info["filesize"], "record_length": info["record_length"], "byteorder": info["byteorder"], "number_of_records": info["number_of_records"], } # If its a filename just read it. if isinstance(mseed_object, basestring): # Read to NumPy array which is used as a buffer. buffer = np.fromfile(mseed_object, dtype="b") elif hasattr(mseed_object, "read"): buffer = np.fromstring(mseed_object.read(), dtype="b") # Get the record length try: record_length = pow(2, int("".join([chr(_i) for _i in buffer[19:21]]))) except ValueError: record_length = 4096 # Search for data records and pass only the data part to the underlying C # routine. offset = 0 # 0 to 9 are defined in a row in the ASCII charset. min_ascii = ord("0") # Small function to check whether an array of ASCII values contains only # digits. isdigit = lambda x: True if (x - min_ascii).max() <= 9 else False while True: # This should never happen if (isdigit(buffer[offset : offset + 6]) is False) or (buffer[offset + 6] not in VALID_CONTROL_HEADERS): msg = "Not a valid (Mini-)SEED file" raise Exception(msg) elif buffer[offset + 6] in SEED_CONTROL_HEADERS: offset += record_length continue break buffer = buffer[offset:] buflen = len(buffer) # If no selection is given pass None to the C function. if starttime is None and endtime is None and sourcename is None: selections = None else: select_time = SelectTime() selections = Selections() selections.timewindows.contents = select_time if starttime is not None: if not isinstance(starttime, UTCDateTime): msg = "starttime needs to be a UTCDateTime object" raise ValueError(msg) selections.timewindows.contents.starttime = util._convertDatetimeToMSTime(starttime) else: # HPTERROR results in no starttime. selections.timewindows.contents.starttime = HPTERROR if endtime is not None: if not isinstance(endtime, UTCDateTime): msg = "endtime needs to be a UTCDateTime object" raise ValueError(msg) selections.timewindows.contents.endtime = util._convertDatetimeToMSTime(endtime) else: # HPTERROR results in no starttime. selections.timewindows.contents.endtime = HPTERROR if sourcename is not None: if not isinstance(sourcename, basestring): msg = "sourcename needs to be a string" raise ValueError(msg) # libmseed uses underscores as separators and allows filtering # after the dataquality which is disabled here to not confuse # users. (* == all data qualities) selections.srcname = sourcename.replace(".", "_") + "_*" else: selections.srcname = "*" all_data = [] # Use a callback function to allocate the memory and keep track of the # data. def allocate_data(samplecount, sampletype): data = np.empty(samplecount, dtype=DATATYPES[sampletype]) all_data.append(data) return data.ctypes.data # XXX: Do this properly! # Define Python callback function for use in C function. Return a long so # it hopefully works on 32 and 64 bit systems. allocData = C.CFUNCTYPE(C.c_long, C.c_int, C.c_char)(allocate_data) lil = clibmseed.readMSEEDBuffer(buffer, buflen, selections, unpack_data, reclen, 0, allocData) # XXX: Check if the freeing works. del selections traces = [] try: currentID = lil.contents # Return stream if not traces are found. except ValueError: clibmseed.lil_free(lil) del lil return Stream() while True: # Init header with the essential information. header = { "network": currentID.network.strip(), "station": currentID.station.strip(), "location": currentID.location.strip(), "channel": currentID.channel.strip(), "mseed": {"dataquality": currentID.dataquality}, } # Loop over segments. try: currentSegment = currentID.firstSegment.contents except ValueError: break while True: header["sampling_rate"] = currentSegment.samprate header["starttime"] = util._convertMSTimeToDatetime(currentSegment.starttime) if headonly is False: # The data always will be in sequential order. data = all_data.pop(0) header["npts"] = len(data) else: data = np.array([]) header["npts"] = currentSegment.samplecnt # Make sure to init the number of samples. trace = Trace(header=header, data=data) # Append information if necessary. if recinfo: for key, value in info.iteritems(): setattr(trace.stats.mseed, key, value) traces.append(trace) # A Null pointer access results in a ValueError try: currentSegment = currentSegment.next.contents except ValueError: break try: currentID = currentID.next.contents except ValueError: break clibmseed.lil_free(lil) del lil return Stream(traces=traces)
def readMSEED(mseed_object, starttime=None, endtime=None, headonly=False, sourcename=None, reclen=None, recinfo=True, **kwargs): """ Reads a Mini-SEED file and returns a Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param mseed_object: Filename or open file like object that contains the binary Mini-SEED data. Any object that provides a read() method will be considered to be a file like object. :type starttime: UTCDateTime :param starttime: Only read data samples after or at the starttime. :type endtime: UTCDateTime :param endtime: Only read data samples before or at the starttime. :param headonly: Determines whether or not to unpack the data or just read the headers. :type sourcename: str :param sourcename: Sourcename has to have the structure 'network.station.location.channel' and can contain globbing characters. Defaults to ``None``. :param reclen: If it is None, it will be automatically determined for every record. If it is known, just set it to the record length in bytes which will increase the reading speed slightly. :type recinfo: bool, optional :param recinfo: If ``True`` the byteorder, record length and the encoding of the file will be read and stored in every Trace's stats.mseed AttribDict. These stored attributes will also be used while writing a Mini-SEED file. Only the very first record of the file will be read and all following records are assumed to be the same. Defaults to ``True``. .. rubric:: Example >>> from obspy.core import read >>> st = read("/path/to/two_channels.mseed") >>> print(st) # doctest: +ELLIPSIS 2 Trace(s) in Stream: BW.UH3..EHE | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples BW.UH3..EHZ | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples >>> from obspy.core import UTCDateTime >>> st = read("/path/to/test.mseed", ... starttime=UTCDateTime("2003-05-29T02:16:00"), ... selection="NL.*.*.?HZ") >>> print(st) # doctest: +ELLIPSIS 1 Trace(s) in Stream: NL.HGN.00.BHZ | 2003-05-29T02:15:59.993400Z - ... | 40.0 Hz, 5629 samples """ # Parse the headonly and reclen flags. if headonly is True: unpack_data = 0 else: unpack_data = 1 if reclen is None: reclen = -1 elif reclen is not None and reclen not in VALID_RECORD_LENGTHS: msg = 'Invalid record length. Autodetection will be used.' warnings.warn(msg) reclen = -1 else: reclen = int(log(reclen, 2)) # The quality flag is no more supported. Raise a warning. if 'quality' in kwargs: msg = 'The quality flag is no more supported in this version of ' + \ 'obspy.mseed. obspy.mseed.util has some functions with similar ' + \ 'behaviour.' warnings.warn(msg, category=DeprecationWarning) # Parse some information about the file. if recinfo: info = util.getRecordInformation(mseed_object) info['encoding'] = ENCODINGS[info['encoding']][0] # Only keep information relevant for the whole file. info = { 'encoding': info['encoding'], 'filesize': info['filesize'], 'record_length': info['record_length'], 'byteorder': info['byteorder'], 'number_of_records': info['number_of_records'] } # If its a filename just read it. if isinstance(mseed_object, basestring): # Read to NumPy array which is used as a buffer. buffer = np.fromfile(mseed_object, dtype='b') elif hasattr(mseed_object, 'read'): buffer = np.fromstring(mseed_object.read(), dtype='b') # Get the record length try: record_length = pow(2, int(''.join([chr(_i) for _i in buffer[19:21]]))) except ValueError: record_length = 4096 # Search for data records and pass only the data part to the underlying C # routine. offset = 0 # 0 to 9 are defined in a row in the ASCII charset. min_ascii = ord('0') # Small function to check whether an array of ASCII values contains only # digits. isdigit = lambda x: True if (x - min_ascii).max() <= 9 else False while True: # This should never happen if (isdigit(buffer[offset:offset + 6]) is False) or \ (buffer[offset + 6] not in VALID_CONTROL_HEADERS): msg = 'Not a valid (Mini-)SEED file' raise Exception(msg) elif buffer[offset + 6] in SEED_CONTROL_HEADERS: offset += record_length continue break buffer = buffer[offset:] buflen = len(buffer) # If no selection is given pass None to the C function. if starttime is None and endtime is None and sourcename is None: selections = None else: select_time = SelectTime() selections = Selections() selections.timewindows.contents = select_time if starttime is not None: if not isinstance(starttime, UTCDateTime): msg = 'starttime needs to be a UTCDateTime object' raise ValueError(msg) selections.timewindows.contents.starttime = \ util._convertDatetimeToMSTime(starttime) else: # HPTERROR results in no starttime. selections.timewindows.contents.starttime = HPTERROR if endtime is not None: if not isinstance(endtime, UTCDateTime): msg = 'endtime needs to be a UTCDateTime object' raise ValueError(msg) selections.timewindows.contents.endtime = \ util._convertDatetimeToMSTime(endtime) else: # HPTERROR results in no starttime. selections.timewindows.contents.endtime = HPTERROR if sourcename is not None: if not isinstance(sourcename, basestring): msg = 'sourcename needs to be a string' raise ValueError(msg) # libmseed uses underscores as separators and allows filtering # after the dataquality which is disabled here to not confuse # users. (* == all data qualities) selections.srcname = sourcename.replace('.', '_') + '_*' else: selections.srcname = '*' all_data = [] # Use a callback function to allocate the memory and keep track of the # data. def allocate_data(samplecount, sampletype): data = np.empty(samplecount, dtype=DATATYPES[sampletype]) all_data.append(data) return data.ctypes.data # XXX: Do this properly! # Define Python callback function for use in C function. Return a long so # it hopefully works on 32 and 64 bit systems. allocData = C.CFUNCTYPE(C.c_long, C.c_int, C.c_char)(allocate_data) lil = clibmseed.readMSEEDBuffer(buffer, buflen, selections, unpack_data, reclen, 0, allocData) # XXX: Check if the freeing works. del selections traces = [] try: currentID = lil.contents # Return stream if not traces are found. except ValueError: clibmseed.lil_free(lil) del lil return Stream() while True: # Init header with the essential information. header = { 'network': currentID.network.strip(), 'station': currentID.station.strip(), 'location': currentID.location.strip(), 'channel': currentID.channel.strip(), 'mseed': { 'dataquality': currentID.dataquality } } # Loop over segments. try: currentSegment = currentID.firstSegment.contents except ValueError: break while True: header['sampling_rate'] = currentSegment.samprate header['starttime'] = \ util._convertMSTimeToDatetime(currentSegment.starttime) if headonly is False: # The data always will be in sequential order. data = all_data.pop(0) header['npts'] = len(data) else: data = np.array([]) header['npts'] = currentSegment.samplecnt # Make sure to init the number of samples. trace = Trace(header=header, data=data) # Append information if necessary. if recinfo: for key, value in info.iteritems(): setattr(trace.stats.mseed, key, value) traces.append(trace) # A Null pointer access results in a ValueError try: currentSegment = currentSegment.next.contents except ValueError: break try: currentID = currentID.next.contents except ValueError: break clibmseed.lil_free(lil) del lil return Stream(traces=traces)
def writeMSEED(stream, filename, encoding=None, reclen=None, byteorder=None, flush=1, verbose=0, **_kwargs): """ Write Mini-SEED file from a Stream object. .. warning:: This function should NOT be called directly, it registers via the the :meth:`~obspy.core.stream.Stream.write` method of an ObsPy :class:`~obspy.core.stream.Stream` object, call this instead. :type stream: :class:`~obspy.core.stream.Stream` :param stream: A Stream object. :type filename: str :param filename: Name of the output file :type encoding: int or str, optional :param encoding: Should be set to one of the following supported Mini-SEED data encoding formats: ASCII (``0``)*, INT16 (``1``), INT32 (``3``), FLOAT32 (``4``)*, FLOAT64 (``5``)*, STEIM1 (``10``) and STEIM2 (``11``)*. Default data types a marked with an asterisk. Currently INT24 (``2``) is not supported due to lacking NumPy support. :type reclen: int, optional :param reclen: Should be set to the desired data record length in bytes which must be expressible as 2 raised to the power of X where X is between (and including) 8 to 20. Defaults to 4096 :type byteorder: [``0`` or ``'<'`` | ``1`` or ``'>'`` | ``'='``], optional :param byteorder: Must be either ``0`` or ``'<'`` for LSBF or little-endian, ``1`` or ``'>'`` for MBF or big-endian. ``'='`` is the native byteorder. If ``-1`` it will be passed directly to libmseed which will also default it to big endian. Defaults to big endian. :type flush: int, optional :param flush: If it is not zero all of the data will be packed into records, otherwise records will only be packed while there are enough data samples to completely fill a record. :type verbose: int, optional :param verbose: Controls verbosity, a value of zero will result in no diagnostic output. .. note:: The reclen, encoding and byteorder keyword arguments can be set in the stats.mseed of each :class:`~obspy.core.trace.Trace` as well as as kwargs of this function. If both are given the kwargs will be used. .. rubric:: Example >>> from obspy.core import read >>> st = read() >>> st.write('filename.mseed', format='MSEED') # doctest: +SKIP """ # Some sanity checks for the keyword arguments. if reclen is not None and reclen not in VALID_RECORD_LENGTHS: msg = 'Invalid record length. The record length must be a value\n' + \ 'of 2 to the power of X where 8 <= X <= 20.' raise ValueError(msg) if byteorder is not None and byteorder not in [0, 1, -1]: if byteorder == '=': byteorder = NATIVE_BYTEORDER # If not elif because NATIVE_BYTEORDER is '<' or '>'. if byteorder == '<': byteorder = 0 elif byteorder == '>': byteorder = 1 else: msg = "Invalid byteorder. It must be either '<', '>', '=', " + \ "0, 1 or -1" raise ValueError(msg) # Check if encoding kwarg is set and catch invalid encodings. # XXX: Currently INT24 is not working due to lacking NumPy support. encoding_strings = dict([(v[0], k) for (k, v) in ENCODINGS.iteritems()]) if encoding is not None: if isinstance(encoding, int) and encoding in ENCODINGS: pass elif encoding and isinstance(encoding, basestring) and encoding \ in encoding_strings: encoding = encoding_strings[encoding] else: msg = 'Invalid encoding %s. Valid encodings: %s' raise ValueError(msg % (encoding, encoding_strings)) trace_attributes = [] use_blkt_1001 = 0 # The data might need to be modified. To not modify the input data keep # references of which data to finally write. trace_data = [] # Loop over every trace and figure out the correct settings. for _i, trace in enumerate(stream): # Create temporary dict for storing information while writing. trace_attr = {} trace_attributes.append(trace_attr) stats = trace.stats # Figure out whether or not to use Blockette 1001. This check is done # once to ensure that Blockette 1001 is either written for every record # in the file or for none. It checks the starttime as well as the # sampling rate. If either one has a precision of more than 100 # microseconds, Blockette 1001 will be written for every record. starttime = util._convertDatetimeToMSTime(trace.stats.starttime) if starttime % 100 != 0 or \ (1.0 / trace.stats.sampling_rate * HPTMODULUS) % 100 != 0: use_blkt_1001 += 1 # Determine if a blockette 100 will be needed to represent the input # sample rate or if the sample rate in the fixed section of the data # header will suffice (see ms_genfactmult in libmseed/genutils.c) if trace.stats.sampling_rate >= 32727.0 or \ trace.stats.sampling_rate <= (1.0 / 32727.0): use_blkt_100 = True else: use_blkt_100 = False # Set data quality to indeterminate (= D) if it is not already set. try: trace_attr['dataquality'] = \ trace.stats['mseed']['dataquality'].upper() except: trace_attr['dataquality'] = 'D' # Sanity check for the dataquality to get a nice Python exception # instead of a C error. if trace_attr['dataquality'] not in ['D', 'R', 'Q', 'M']: msg = 'Invalid dataquality in Stream[%i].stats' % _i + \ '.mseed.dataquality\n' + \ 'The dataquality for Mini-SEED must be either D, R, Q ' + \ 'or M. See the SEED manual for further information.' raise ValueError(msg) # Check that data is of the right type. if not isinstance(trace.data, np.ndarray): msg = "Unsupported data type %s" % type(trace.data) + \ " for Stream[%i].data." % _i raise ValueError(msg) # Check if ndarray is contiguous (see #192, #193) if not trace.data.flags.c_contiguous: msg = "Detected non contiguous data array in Stream[%i]" % _i + \ ".data. Trying to fix array." warnings.warn(msg) trace.data = np.require(trace.data, requirements=('C_CONTIGUOUS', )) # Handle the record length. if reclen is not None: trace_attr['reclen'] = reclen elif hasattr(stats, 'mseed') and \ hasattr(stats.mseed, 'record_length'): if stats.mseed.record_length in VALID_RECORD_LENGTHS: trace_attr['reclen'] = stats.mseed.record_length else: msg = 'Invalid record length in Stream[%i].stats.' % _i + \ 'mseed.reclen.\nThe record length must be a value ' + \ 'of 2 to the power of X where 8 <= X <= 20.' raise ValueError(msg) else: trace_attr['reclen'] = 4096 # Handle the byteorder. if byteorder is not None: trace_attr['byteorder'] = byteorder elif hasattr(stats, 'mseed') and \ hasattr(stats.mseed, 'byteorder'): if stats.mseed.byteorder in [0, 1, -1]: trace_attr['byteorder'] = stats.mseed.byteorder elif stats.mseed.byteorder == '=': if NATIVE_BYTEORDER == '<': trace_attr['byteorder'] = 0 else: trace_attr['byteorder'] = 1 elif stats.mseed.byteorder == '<': trace_attr['byteorder'] = 0 elif stats.mseed.byteorder == '>': trace_attr['byteorder'] = 1 else: msg = "Invalid byteorder in Stream[%i].stats." % _i + \ "mseed.byteorder. It must be either '<', '>', '='," + \ " 0, 1 or -1" raise ValueError(msg) else: trace_attr['byteorder'] = 1 if trace_attr['byteorder'] == -1: if NATIVE_BYTEORDER == '<': trace_attr['byteorder'] = 0 else: trace_attr['byteorder'] = 1 # Handle the encoding. trace_attr['encoding'] = None if encoding is not None: # Check if the dtype for all traces is compatible with the enforced # encoding. id, _, dtype = ENCODINGS[encoding] if trace.data.dtype.type != dtype: msg = """ Wrong dtype for Stream[%i].data for encoding %s. Please change the dtype of your data or use an appropriate encoding. See the obspy.mseed documentation for more information. """ % (_i, id) raise Exception(msg) trace_attr['encoding'] = encoding elif hasattr(trace.stats, 'mseed') and hasattr(trace.stats.mseed, 'encoding'): mseed_encoding = stats.mseed.encoding # Check if the encoding is valid. if isinstance(mseed_encoding, int) and mseed_encoding in ENCODINGS: trace_attr['encoding'] = mseed_encoding elif isinstance(mseed_encoding, basestring) and \ mseed_encoding in encoding_strings: trace_attr['encoding'] = encoding_strings[mseed_encoding] else: msg = 'Invalid encoding %s in ' + \ 'Stream[%i].stats.mseed.encoding. Valid encodings: %s' raise ValueError(msg % (mseed_encoding, _i, encoding_strings)) # Check if the encoding matches the data's dtype. if trace.data.dtype.type != ENCODINGS[trace_attr['encoding']][2]: msg = 'The encoding specified in ' + \ 'trace.stats.mseed.encoding does not match the ' + \ 'dtype of the data.\nA suitable encoding will ' + \ 'be chosen.' warnings.warn(msg, UserWarning) trace_attr['encoding'] = None # automatically detect encoding if no encoding is given. if not trace_attr['encoding']: if trace.data.dtype.type == np.dtype("int32"): trace_attr['encoding'] = 11 elif trace.data.dtype.type == np.dtype("float32"): trace_attr['encoding'] = 4 elif trace.data.dtype.type == np.dtype("float64"): trace_attr['encoding'] = 5 elif trace.data.dtype.type == np.dtype("int16"): trace_attr['encoding'] = 1 elif trace.data.dtype.type == np.dtype('|S1').type: trace_attr['encoding'] = 0 else: msg = "Unsupported data type %s in Stream[%i].data" % \ (trace.data.dtype, _i) raise Exception(msg) # Convert data if necessary, otherwise store references in list. if trace_attr['encoding'] == 1: # INT16 needs INT32 data type trace_data.append(trace.data.copy().astype(np.int32)) else: trace_data.append(trace.data) # Do some final sanity checks and raise a warning if a file will be written # with more than one different encoding, record length or byteorder. encodings = set([_i['encoding'] for _i in trace_attributes]) reclens = set([_i['reclen'] for _i in trace_attributes]) byteorders = set([_i['byteorder'] for _i in trace_attributes]) msg = 'File will be written with more than one different %s.\n' + \ 'This might have a negative influence on the compatibility ' + \ 'with other programs.' if len(encodings) != 1: warnings.warn(msg % 'encodings') if len(reclens) != 1: warnings.warn(msg % 'record lengths') if len(byteorders) != 1: warnings.warn(msg % 'byteorders') # Open filehandler or use an existing file like object. if not hasattr(filename, 'write'): f = open(filename, 'wb') else: f = filename # Loop over every trace and finally write it to the filehandler. for trace, data, trace_attr in izip(stream, trace_data, trace_attributes): # Create C struct MSTraceGroup. mstg = MSTG(trace, data, dataquality=trace_attr['dataquality']) # Initialize packedsamples pointer for the mst_pack function packedsamples = C.c_int() # Callback function for mst_pack to actually write the file def record_handler(record, reclen, _stream): f.write(record[0:reclen]) # Define Python callback function for use in C function recHandler = C.CFUNCTYPE(C.c_void_p, C.POINTER(C.c_char), C.c_int, C.c_void_p)(record_handler) # Fill up msr record structure, this is already contained in # mstg, however if blk1001 is set we need it anyway msr = clibmseed.msr_init(None) msr.contents.network = trace.stats.network msr.contents.station = trace.stats.station msr.contents.location = trace.stats.location msr.contents.channel = trace.stats.channel msr.contents.dataquality = trace_attr['dataquality'] # Only use Blockette 1001 if necessary. if use_blkt_1001: size = C.sizeof(blkt_1001_s) blkt1001 = C.c_char(' ') C.memset(C.pointer(blkt1001), 0, size) ret_val = clibmseed.msr_addblockette(msr, C.pointer(blkt1001), size, 1001, 0) # Usually returns a pointer to the added blockette in the # blockette link chain and a NULL pointer if it fails. # NULL pointers have a false boolean value according to the # ctypes manual. if bool(ret_val) is False: clibmseed.msr_free(C.pointer(msr)) del mstg, msr raise Exception('Error in msr_addblockette') # Only use Blockette 100 if necessary. if use_blkt_100: size = C.sizeof(blkt_100_s) blkt100 = C.c_char(' ') C.memset(C.pointer(blkt100), 0, size) ret_val = clibmseed.msr_addblockette(msr, C.pointer(blkt100), size, 100, 0) # Usually returns a pointer to the added blockette in the # blockette link chain and a NULL pointer if it fails. # NULL pointers have a false boolean value according to the # ctypes manual. if bool(ret_val) is False: clibmseed.msr_free(C.pointer(msr)) del mstg, msr raise Exception('Error in msr_addblockette') # Pack mstg into a MSEED file using the callback record_handler as # write method. errcode = clibmseed.mst_packgroup(mstg.getMstg(), recHandler, None, trace_attr['reclen'], trace_attr['encoding'], trace_attr['byteorder'], C.byref(packedsamples), flush, verbose, msr) if errcode == 0: msg = 'Skipping empty trace "%s".' % (trace) warnings.warn(msg) if errcode == -1: clibmseed.msr_free(C.pointer(msr)) del mstg, msr raise Exception('Error in mst_packgroup') # Deallocate any allocated memory. clibmseed.msr_free(C.pointer(msr)) del mstg, msr # Close if its a file handler. if isinstance(f, file): f.close()
def readMSEED(mseed_object, starttime=None, endtime=None, headonly=False, sourcename=None, reclen=None, recinfo=True, details=False, header_byteorder=None, verbose=None, **kwargs): """ Reads a Mini-SEED file and returns a Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param mseed_object: Filename or open file like object that contains the binary Mini-SEED data. Any object that provides a read() method will be considered to be a file like object. :type starttime: UTCDateTime :param starttime: Only read data samples after or at the starttime. :type endtime: UTCDateTime :param endtime: Only read data samples before or at the starttime. :param headonly: Determines whether or not to unpack the data or just read the headers. :type sourcename: str :param sourcename: Sourcename has to have the structure 'network.station.location.channel' and can contain globbing characters. Defaults to ``None``. :param reclen: If it is None, it will be automatically determined for every record. If it is known, just set it to the record length in bytes which will increase the reading speed slightly. :type recinfo: bool, optional :param recinfo: If ``True`` the byteorder, record length and the encoding of the file will be read and stored in every Trace's stats.mseed AttribDict. These stored attributes will also be used while writing a Mini-SEED file. Only the very first record of the file will be read and all following records are assumed to be the same. Defaults to ``True``. :type details: bool, optional :param details: If ``True`` read additional information: timing quality and availability of calibration information. Note, that the traces are then also split on these additional information. Thus the number of traces in a stream will change. Details are stored in the mseed stats AttribDict of each trace. -1 specifies for both cases, that these information is not available. ``timing_quality`` specifies the timing quality from 0 to 100 [%]. ``calibration_type`` specifies the type of available calibration information: 1 == Step Calibration, 2 == Sine Calibration, 3 == Pseudo-random Calibration, 4 == Generic Calibration and -2 == Calibration Abort. :type header_byteorder: [``0`` or ``'<'`` | ``1`` or ``'>'`` | ``'='``], optional :param header_byteorder: Must be either ``0`` or ``'<'`` for LSBF or little-endian, ``1`` or ``'>'`` for MBF or big-endian. ``'='`` is the native byteorder. Used to enforce the header byteorder. Useful in some rare cases where the automatic byte order detection fails. .. rubric:: Example >>> from obspy import read >>> st = read("/path/to/two_channels.mseed") >>> print(st) # doctest: +ELLIPSIS 2 Trace(s) in Stream: BW.UH3..EHE | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples BW.UH3..EHZ | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples >>> from obspy import UTCDateTime >>> st = read("/path/to/test.mseed", ... starttime=UTCDateTime("2003-05-29T02:16:00"), ... selection="NL.*.*.?HZ") >>> print(st) # doctest: +ELLIPSIS 1 Trace(s) in Stream: NL.HGN.00.BHZ | 2003-05-29T02:15:59.993400Z - ... | 40.0 Hz, 5629 samples """ # Parse the headonly and reclen flags. if headonly is True: unpack_data = 0 else: unpack_data = 1 if reclen is None: reclen = -1 elif reclen not in VALID_RECORD_LENGTHS: msg = 'Invalid record length. Autodetection will be used.' warnings.warn(msg) reclen = -1 # Determine the byteorder. if header_byteorder == "=": header_byteorder = NATIVE_BYTEORDER if header_byteorder is None: header_byteorder = -1 elif header_byteorder in [0, "0", "<"]: header_byteorder = 0 elif header_byteorder in [1, "1", ">"]: header_byteorder = 1 # The quality flag is no more supported. Raise a warning. if 'quality' in kwargs: msg = 'The quality flag is no more supported in this version of ' + \ 'obspy.mseed. obspy.mseed.util has some functions with similar' + \ ' behaviour.' warnings.warn(msg, category=DeprecationWarning) # Parse some information about the file. if recinfo: # Pass the byteorder if enforced. if header_byteorder == 0: bo = "<" elif header_byteorder > 0: bo = ">" else: bo = None info = util.getRecordInformation(mseed_object, endian=bo) info['encoding'] = ENCODINGS[info['encoding']][0] # Only keep information relevant for the whole file. info = { 'encoding': info['encoding'], 'filesize': info['filesize'], 'record_length': info['record_length'], 'byteorder': info['byteorder'], 'number_of_records': info['number_of_records'] } # If its a filename just read it. if isinstance(mseed_object, basestring): # Read to NumPy array which is used as a buffer. buffer = np.fromfile(mseed_object, dtype='b') elif hasattr(mseed_object, 'read'): buffer = np.fromstring(mseed_object.read(), dtype='b') # Get the record length try: record_length = pow(2, int(''.join([chr(_i) for _i in buffer[19:21]]))) except ValueError: record_length = 4096 # Search for data records and pass only the data part to the underlying C # routine. offset = 0 # 0 to 9 are defined in a row in the ASCII charset. min_ascii = ord('0') # Small function to check whether an array of ASCII values contains only # digits. isdigit = lambda x: True if (x - min_ascii).max() <= 9 else False while True: # This should never happen if (isdigit(buffer[offset:offset + 6]) is False) or \ (buffer[offset + 6] not in VALID_CONTROL_HEADERS): msg = 'Not a valid (Mini-)SEED file' raise Exception(msg) elif buffer[offset + 6] in SEED_CONTROL_HEADERS: offset += record_length continue break buffer = buffer[offset:] buflen = len(buffer) # If no selection is given pass None to the C function. if starttime is None and endtime is None and sourcename is None: selections = None else: select_time = SelectTime() selections = Selections() selections.timewindows.contents = select_time if starttime is not None: if not isinstance(starttime, UTCDateTime): msg = 'starttime needs to be a UTCDateTime object' raise ValueError(msg) selections.timewindows.contents.starttime = \ util._convertDatetimeToMSTime(starttime) else: # HPTERROR results in no starttime. selections.timewindows.contents.starttime = HPTERROR if endtime is not None: if not isinstance(endtime, UTCDateTime): msg = 'endtime needs to be a UTCDateTime object' raise ValueError(msg) selections.timewindows.contents.endtime = \ util._convertDatetimeToMSTime(endtime) else: # HPTERROR results in no starttime. selections.timewindows.contents.endtime = HPTERROR if sourcename is not None: if not isinstance(sourcename, basestring): msg = 'sourcename needs to be a string' raise ValueError(msg) # libmseed uses underscores as separators and allows filtering # after the dataquality which is disabled here to not confuse # users. (* == all data qualities) selections.srcname = sourcename.replace('.', '_') + '_*' else: selections.srcname = '*' all_data = [] # Use a callback function to allocate the memory and keep track of the # data. def allocate_data(samplecount, sampletype): # Enhanced sanity checking for libmseed 2.10 can result in the # sampletype not being set. Just return an empty array in this case. if sampletype == "\x00": data = np.empty(0) else: data = np.empty(samplecount, dtype=DATATYPES[sampletype]) all_data.append(data) return data.ctypes.data # XXX: Do this properly! # Define Python callback function for use in C function. Return a long so # it hopefully works on 32 and 64 bit systems. allocData = C.CFUNCTYPE(C.c_long, C.c_int, C.c_char)(allocate_data) def log_error_or_warning(msg): if msg.startswith("ERROR: "): raise InternalMSEEDReadingError(msg[7:].strip()) if msg.startswith("INFO: "): warnings.warn(msg[6:].strip(), InternalMSEEDReadingWarning) diag_print = C.CFUNCTYPE(C.c_void_p, C.c_char_p)(log_error_or_warning) def log_message(msg): print msg[6:].strip() log_print = C.CFUNCTYPE(C.c_void_p, C.c_char_p)(log_message) try: verbose = int(verbose) except: verbose = 0 lil = clibmseed.readMSEEDBuffer(buffer, buflen, selections, C.c_int8(unpack_data), reclen, C.c_int8(verbose), C.c_int8(details), header_byteorder, allocData, diag_print, log_print) # XXX: Check if the freeing works. del selections traces = [] try: currentID = lil.contents # Return stream if not traces are found. except ValueError: clibmseed.lil_free(lil) del lil return Stream() while True: # Init header with the essential information. header = { 'network': currentID.network.strip(), 'station': currentID.station.strip(), 'location': currentID.location.strip(), 'channel': currentID.channel.strip(), 'mseed': { 'dataquality': currentID.dataquality } } # Loop over segments. try: currentSegment = currentID.firstSegment.contents except ValueError: break while True: header['sampling_rate'] = currentSegment.samprate header['starttime'] = \ util._convertMSTimeToDatetime(currentSegment.starttime) # TODO: write support is missing if details: timing_quality = currentSegment.timing_quality if timing_quality == 0xFF: # 0xFF is mask for not known timing timing_quality = -1 header['mseed']['timing_quality'] = timing_quality header['mseed']['calibration_type'] = \ currentSegment.calibration_type if headonly is False: # The data always will be in sequential order. data = all_data.pop(0) header['npts'] = len(data) else: data = np.array([]) header['npts'] = currentSegment.samplecnt # Make sure to init the number of samples. trace = Trace(header=header, data=data) # Append information if necessary. if recinfo: for key, value in info.iteritems(): setattr(trace.stats.mseed, key, value) traces.append(trace) # A Null pointer access results in a ValueError try: currentSegment = currentSegment.next.contents except ValueError: break try: currentID = currentID.next.contents except ValueError: break clibmseed.lil_free(lil) # NOQA del lil # NOQA return Stream(traces=traces)
def readMSEED(mseed_object, starttime=None, endtime=None, headonly=False, sourcename=None, reclen=None, recinfo=True, details=False, header_byteorder=None, verbose=None, **kwargs): """ Reads a Mini-SEED file and returns a Stream object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.stream.read` function, call this instead. :param mseed_object: Filename or open file like object that contains the binary Mini-SEED data. Any object that provides a read() method will be considered to be a file like object. :type starttime: UTCDateTime :param starttime: Only read data samples after or at the starttime. :type endtime: UTCDateTime :param endtime: Only read data samples before or at the starttime. :param headonly: Determines whether or not to unpack the data or just read the headers. :type sourcename: str :param sourcename: Sourcename has to have the structure 'network.station.location.channel' and can contain globbing characters. Defaults to ``None``. :param reclen: If it is None, it will be automatically determined for every record. If it is known, just set it to the record length in bytes which will increase the reading speed slightly. :type recinfo: bool, optional :param recinfo: If ``True`` the byteorder, record length and the encoding of the file will be read and stored in every Trace's stats.mseed AttribDict. These stored attributes will also be used while writing a Mini-SEED file. Only the very first record of the file will be read and all following records are assumed to be the same. Defaults to ``True``. :type details: bool, optional :param details: If ``True`` read additional information: timing quality and availability of calibration information. Note, that the traces are then also split on these additional information. Thus the number of traces in a stream will change. Details are stored in the mseed stats AttribDict of each trace. -1 specifies for both cases, that these information is not available. ``timing_quality`` specifies the timing quality from 0 to 100 [%]. ``calibration_type`` specifies the type of available calibration information: 1 == Step Calibration, 2 == Sine Calibration, 3 == Pseudo-random Calibration, 4 == Generic Calibration and -2 == Calibration Abort. :type header_byteorder: [``0`` or ``'<'`` | ``1`` or ``'>'`` | ``'='``], optional :param header_byteorder: Must be either ``0`` or ``'<'`` for LSBF or little-endian, ``1`` or ``'>'`` for MBF or big-endian. ``'='`` is the native byteorder. Used to enforce the header byteorder. Useful in some rare cases where the automatic byte order detection fails. .. rubric:: Example >>> from obspy import read >>> st = read("/path/to/two_channels.mseed") >>> print(st) # doctest: +ELLIPSIS 2 Trace(s) in Stream: BW.UH3..EHE | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples BW.UH3..EHZ | 2010-06-20T00:00:00.279999Z - ... | 200.0 Hz, 386 samples >>> from obspy import UTCDateTime >>> st = read("/path/to/test.mseed", ... starttime=UTCDateTime("2003-05-29T02:16:00"), ... selection="NL.*.*.?HZ") >>> print(st) # doctest: +ELLIPSIS 1 Trace(s) in Stream: NL.HGN.00.BHZ | 2003-05-29T02:15:59.993400Z - ... | 40.0 Hz, 5629 samples """ # Parse the headonly and reclen flags. if headonly is True: unpack_data = 0 else: unpack_data = 1 if reclen is None: reclen = -1 elif reclen is not None and reclen not in VALID_RECORD_LENGTHS: msg = 'Invalid record length. Autodetection will be used.' warnings.warn(msg) reclen = -1 else: reclen = int(log(reclen, 2)) # Determine the byteorder. if header_byteorder == "=": header_byteorder = NATIVE_BYTEORDER if header_byteorder is None: header_byteorder = -1 elif header_byteorder in [0, "0", "<"]: header_byteorder = 0 elif header_byteorder in [1, "1", ">"]: header_byteorder = 1 # The quality flag is no more supported. Raise a warning. if 'quality' in kwargs: msg = 'The quality flag is no more supported in this version of ' + \ 'obspy.mseed. obspy.mseed.util has some functions with similar ' + \ 'behaviour.' warnings.warn(msg, category=DeprecationWarning) # Parse some information about the file. if recinfo: # Pass the byteorder if enforced. if header_byteorder == 0: bo = "<" elif header_byteorder > 0: bo = ">" else: bo = None info = util.getRecordInformation(mseed_object, endian=bo) info['encoding'] = ENCODINGS[info['encoding']][0] # Only keep information relevant for the whole file. info = {'encoding': info['encoding'], 'filesize': info['filesize'], 'record_length': info['record_length'], 'byteorder': info['byteorder'], 'number_of_records': info['number_of_records']} # If its a filename just read it. if isinstance(mseed_object, basestring): # Read to NumPy array which is used as a buffer. buffer = np.fromfile(mseed_object, dtype='b') elif hasattr(mseed_object, 'read'): buffer = np.fromstring(mseed_object.read(), dtype='b') # Get the record length try: record_length = pow(2, int(''.join([chr(_i) for _i in buffer[19:21]]))) except ValueError: record_length = 4096 # Search for data records and pass only the data part to the underlying C # routine. offset = 0 # 0 to 9 are defined in a row in the ASCII charset. min_ascii = ord('0') # Small function to check whether an array of ASCII values contains only # digits. isdigit = lambda x: True if (x - min_ascii).max() <= 9 else False while True: # This should never happen if (isdigit(buffer[offset:offset + 6]) is False) or \ (buffer[offset + 6] not in VALID_CONTROL_HEADERS): msg = 'Not a valid (Mini-)SEED file' raise Exception(msg) elif buffer[offset + 6] in SEED_CONTROL_HEADERS: offset += record_length continue break buffer = buffer[offset:] buflen = len(buffer) # If no selection is given pass None to the C function. if starttime is None and endtime is None and sourcename is None: selections = None else: select_time = SelectTime() selections = Selections() selections.timewindows.contents = select_time if starttime is not None: if not isinstance(starttime, UTCDateTime): msg = 'starttime needs to be a UTCDateTime object' raise ValueError(msg) selections.timewindows.contents.starttime = \ util._convertDatetimeToMSTime(starttime) else: # HPTERROR results in no starttime. selections.timewindows.contents.starttime = HPTERROR if endtime is not None: if not isinstance(endtime, UTCDateTime): msg = 'endtime needs to be a UTCDateTime object' raise ValueError(msg) selections.timewindows.contents.endtime = \ util._convertDatetimeToMSTime(endtime) else: # HPTERROR results in no starttime. selections.timewindows.contents.endtime = HPTERROR if sourcename is not None: if not isinstance(sourcename, basestring): msg = 'sourcename needs to be a string' raise ValueError(msg) # libmseed uses underscores as separators and allows filtering # after the dataquality which is disabled here to not confuse # users. (* == all data qualities) selections.srcname = sourcename.replace('.', '_') + '_*' else: selections.srcname = '*' all_data = [] # Use a callback function to allocate the memory and keep track of the # data. def allocate_data(samplecount, sampletype): # Enhanced sanity checking for libmseed 2.10 can result in the # sampletype not being set. Just return an empty array in this case. if sampletype == "\x00": data = np.empty(0) else: data = np.empty(samplecount, dtype=DATATYPES[sampletype]) all_data.append(data) return data.ctypes.data # XXX: Do this properly! # Define Python callback function for use in C function. Return a long so # it hopefully works on 32 and 64 bit systems. allocData = C.CFUNCTYPE(C.c_long, C.c_int, C.c_char)(allocate_data) try: verbose = int(verbose) except: verbose = 0 lil = clibmseed.readMSEEDBuffer(buffer, buflen, selections, unpack_data, reclen, C.c_int(verbose), C.c_int(details), header_byteorder, allocData) # XXX: Check if the freeing works. del selections traces = [] try: currentID = lil.contents # Return stream if not traces are found. except ValueError: clibmseed.lil_free(lil) del lil return Stream() while True: # Init header with the essential information. header = {'network': currentID.network.strip(), 'station': currentID.station.strip(), 'location': currentID.location.strip(), 'channel': currentID.channel.strip(), 'mseed': {'dataquality': currentID.dataquality}} # Loop over segments. try: currentSegment = currentID.firstSegment.contents except ValueError: break while True: header['sampling_rate'] = currentSegment.samprate header['starttime'] = \ util._convertMSTimeToDatetime(currentSegment.starttime) # TODO: write support is missing if details: timing_quality = currentSegment.timing_quality if timing_quality == 0xFF: # 0xFF is mask for not known timing timing_quality = -1 header['mseed']['timing_quality'] = timing_quality header['mseed']['calibration_type'] = \ currentSegment.calibration_type if headonly is False: # The data always will be in sequential order. data = all_data.pop(0) header['npts'] = len(data) else: data = np.array([]) header['npts'] = currentSegment.samplecnt # Make sure to init the number of samples. trace = Trace(header=header, data=data) # Append information if necessary. if recinfo: for key, value in info.iteritems(): setattr(trace.stats.mseed, key, value) traces.append(trace) # A Null pointer access results in a ValueError try: currentSegment = currentSegment.next.contents except ValueError: break try: currentID = currentID.next.contents except ValueError: break clibmseed.lil_free(lil) del lil return Stream(traces=traces)