def _convert_and_check_encoding_for_writing(encoding): """ Helper function to handle and test encodings. If encoding is a string, it will be converted to the appropriate integer. It will furthermore be checked if the specified encoding can be written using libmseed. Appropriate errors will be raised if necessary. """ # Check if encoding kwarg is set and catch invalid encodings. encoding_strings = dict([(v[0], k) for (k, v) in ENCODINGS.items()]) try: encoding = int(encoding) except: pass if isinstance(encoding, int): if (encoding in ENCODINGS and ENCODINGS[encoding][3] is False) or \ encoding in UNSUPPORTED_ENCODINGS: msg = ("Encoding %i cannot be written with ObsPy. Please " "use another encoding.") % encoding raise ValueError(msg) elif encoding not in ENCODINGS: raise ValueError("Unknown encoding: %i." % encoding) else: if encoding not in encoding_strings: raise ValueError("Unknown encoding: '%s'." % str(encoding)) elif ENCODINGS[encoding_strings[encoding]][3] is False: msg = ("Encoding '%s' cannot be written with ObsPy. Please " "use another encoding.") % encoding raise ValueError(msg) encoding = encoding_strings[encoding] return encoding
def test_writeAndReadDifferentEncodings(self): """ Writes and read a file with different encoding via the obspy.core methods. """ npts = 1000 np.random.seed(815) # make test reproducible data = np.random.randn(npts).astype('float64') * 1e3 + .5 st = Stream([Trace(data=data)]) # Loop over some record lengths. for encoding, value in ENCODINGS.items(): seed_dtype = value[2] # Special handling for the ASCII dtype. NumPy 1.7 changes the # default dtype of numpy.string_ from "|S1" to "|S32". Enforce # "|S1|" here to be consistent across NumPy versions. if encoding == 0: seed_dtype = "|S1" with NamedTemporaryFile() as tf: tempfile = tf.name # Write it once with the encoding key and once with the value. st[0].data = data.astype(seed_dtype) st.verify() st.write(tempfile, format="MSEED", encoding=encoding) st2 = read(tempfile) del st2[0].stats.mseed np.testing.assert_array_equal( st[0].data, st2[0].data, "Arrays are not equal for encoding '%s'" % ENCODINGS[encoding][0]) del st2 ms = _MSStruct(tempfile) ms.read(-1, 1, 1, 0) self.assertEqual(ms.msr.contents.encoding, encoding) del ms # for valgrind
def test_writeAndReadDifferentEncodings(self): """ Writes and read a file with different encoding via the obspy.core methods. """ npts = 1000 np.random.seed(815) # make test reproducable data = np.random.randn(npts).astype('float64') * 1e3 + .5 st = Stream([Trace(data=data)]) # Loop over some record lengths. for encoding, value in ENCODINGS.items(): seed_dtype = value[2] # Special handling for the ASCII dtype. NumPy 1.7 changes the # default dtype of numpy.string_ from "|S1" to "|S32". Enforce # "|S1|" here to be consistent across NumPy versions. if encoding == 0: seed_dtype = "|S1" with NamedTemporaryFile() as tf: tempfile = tf.name # Write it once with the encoding key and once with the value. st[0].data = data.astype(seed_dtype) st.verify() st.write(tempfile, format="MSEED", encoding=encoding) st2 = read(tempfile) del st2[0].stats.mseed np.testing.assert_array_equal( st[0].data, st2[0].data, "Arrays are not equal for encoding '%s'" % ENCODINGS[encoding][0]) del st2 ms = _MSStruct(tempfile) ms.read(-1, 1, 1, 0) self.assertEqual(ms.msr.contents.encoding, encoding) del ms # for valgrind
def test_writing_with_some_encoding_fails(self): """ Writing with some encoding fails as libmseed does not support those. Make sure an appropriate error is raised. """ tr = read()[0] tr.data = tr.data[:10] for encoding, value in ENCODINGS.items(): # Convert the data to the appropriate type to make it does not # fail because of that. tr2 = tr.copy() tr2.data = np.require(tr2.data, dtype=value[2]) buf = io.BytesIO() # Test writing by forcing the encoding. # Should not fail with write support. if value[3]: # Test with integer code and string name. tr2.write(buf, format="mseed", encoding=encoding) tr2.write(buf, format="mseed", encoding=value[0]) # Should fail without write support. else: # Test with integer code and string name. self.assertRaises(ValueError, tr2.write, buf, format="mseed", encoding=encoding) self.assertRaises(ValueError, tr2.write, buf, format="mseed", encoding=value[0]) # Test again by setting the encoding on the trace stats. tr2.stats.mseed = AttribDict() tr2.stats.mseed.encoding = encoding if value[3]: tr2.write(buf, format="mseed") else: self.assertRaises(ValueError, tr2.write, buf, format="mseed") # Again with setting the string code. tr2.stats.mseed.encoding = value[0] if value[3]: tr2.write(buf, format="mseed") else: self.assertRaises(ValueError, tr2.write, buf, format="mseed")
def test_getRecordInformation(self): """ Tests the reading of Mini-SEED record information. """ # Build encoding strings. encoding_strings = {} for key, value in ENCODINGS.items(): encoding_strings[value[0]] = key # Test the encodings and byte orders. path = os.path.join(self.path, "data", "encoding") files = ['float32_Float32_bigEndian.mseed', 'float32_Float32_littleEndian.mseed', 'float64_Float64_bigEndian.mseed', 'float64_Float64_littleEndian.mseed', 'fullASCII_bigEndian.mseed', 'fullASCII_littleEndian.mseed', 'int16_INT16_bigEndian.mseed', 'int16_INT16_littleEndian.mseed', 'int32_INT32_bigEndian.mseed', 'int32_INT32_littleEndian.mseed', 'int32_Steim1_bigEndian.mseed', 'int32_Steim1_littleEndian.mseed', 'int32_Steim2_bigEndian.mseed', 'int32_Steim2_littleEndian.mseed'] for file in files: info = util.getRecordInformation(os.path.join(path, file)) if 'ASCII' not in file: encoding = file.split('_')[1].upper() byteorder = file.split('_')[2].split('.')[0] else: encoding = 'ASCII' byteorder = file.split('_')[1].split('.')[0] if 'big' in byteorder: byteorder = '>' else: byteorder = '<' self.assertEqual(encoding_strings[encoding], info['encoding']) self.assertEqual(byteorder, info['byteorder']) # Also test the record length although it is equal for all files. self.assertEqual(256, info['record_length']) # No really good test files for the record length so just two files # with known record lengths are tested. info = util.getRecordInformation( os.path.join(self.path, 'data', 'timingquality.mseed')) self.assertEqual(info['record_length'], 512) info = util.getRecordInformation(os.path.join(self.path, 'data', 'steim2.mseed')) self.assertEqual(info['record_length'], 4096)
def test_getRecordInformation(self): """ Tests the reading of Mini-SEED record information. """ # Build encoding strings. encoding_strings = {} for key, value in ENCODINGS.items(): encoding_strings[value[0]] = key # Test the encodings and byte orders. path = os.path.join(self.path, "data", "encoding") files = [ 'float32_Float32_bigEndian.mseed', 'float32_Float32_littleEndian.mseed', 'float64_Float64_bigEndian.mseed', 'float64_Float64_littleEndian.mseed', 'fullASCII_bigEndian.mseed', 'fullASCII_littleEndian.mseed', 'int16_INT16_bigEndian.mseed', 'int16_INT16_littleEndian.mseed', 'int32_INT32_bigEndian.mseed', 'int32_INT32_littleEndian.mseed', 'int32_Steim1_bigEndian.mseed', 'int32_Steim1_littleEndian.mseed', 'int32_Steim2_bigEndian.mseed', 'int32_Steim2_littleEndian.mseed' ] for file in files: info = util.getRecordInformation(os.path.join(path, file)) if 'ASCII' not in file: encoding = file.split('_')[1].upper() byteorder = file.split('_')[2].split('.')[0] else: encoding = 'ASCII' byteorder = file.split('_')[1].split('.')[0] if 'big' in byteorder: byteorder = '>' else: byteorder = '<' self.assertEqual(encoding_strings[encoding], info['encoding']) self.assertEqual(byteorder, info['byteorder']) # Also test the record length although it is equal for all files. self.assertEqual(256, info['record_length']) # No really good test files for the record length so just two files # with known record lengths are tested. info = util.getRecordInformation( os.path.join(self.path, 'data', 'timingquality.mseed')) self.assertEqual(info['record_length'], 512) info = util.getRecordInformation( os.path.join(self.path, 'data', 'steim2.mseed')) self.assertEqual(info['record_length'], 4096)
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 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.items()]) if encoding is not None: if isinstance(encoding, int) and encoding in ENCODINGS: pass elif encoding and isinstance(encoding, (str, native_str)) \ 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, (str, native_str)) 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 zip(stream, trace_data, trace_attributes): if not len(data): msg = 'Skipping empty trace "%s".' % (trace) warnings.warn(msg) continue # Create C struct MSTrace. mst = MST(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.encode('ascii', 'strict') msr.contents.station = trace.stats.station.encode('ascii', 'strict') msr.contents.location = trace.stats.location.encode('ascii', 'strict') msr.contents.channel = trace.stats.channel.encode('ascii', 'strict') msr.contents.dataquality = trace_attr['dataquality'].\ encode('ascii', 'strict') # Only use Blockette 1001 if necessary. if use_blkt_1001: size = C.sizeof(blkt_1001_s) blkt1001 = C.c_char(b' ') 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 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(b' ') C.memset(C.pointer(blkt100), 0, size) ret_val = clibmseed.msr_addblockette(msr, C.pointer(blkt100), size, 100, 0) # NOQA # 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)) # NOQA del msr # NOQA raise Exception('Error in msr_addblockette') # Pack mstg into a MSEED file using the callback record_handler as # write method. errcode = clibmseed.mst_pack(mst.mst, recHandler, None, trace_attr['reclen'], trace_attr['encoding'], trace_attr['byteorder'], C.byref(packedsamples), flush, verbose, msr) # NOQA if errcode == 0: msg = ("Did not write any data for trace '%s' even though it " "contains data values.") % trace raise ValueError(msg) if errcode == -1: clibmseed.msr_free(C.pointer(msr)) # NOQA del mst, msr # NOQA raise Exception('Error in mst_pack') # Deallocate any allocated memory. clibmseed.msr_free(C.pointer(msr)) # NOQA del mst, msr # NOQA # Close if its a file handler. if not hasattr(filename, 'write'): f.close()
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 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.items()]) if encoding is not None: if isinstance(encoding, int) and encoding in ENCODINGS: pass elif encoding and isinstance(encoding, (str, native_str)) \ 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, (str, native_str)) 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 zip(stream, trace_data, trace_attributes): if not len(data): msg = 'Skipping empty trace "%s".' % (trace) warnings.warn(msg) continue # Create C struct MSTrace. mst = MST(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.encode('ascii', 'strict') msr.contents.station = trace.stats.station.encode('ascii', 'strict') msr.contents.location = trace.stats.location.encode('ascii', 'strict') msr.contents.channel = trace.stats.channel.encode('ascii', 'strict') msr.contents.dataquality = trace_attr['dataquality'].\ encode('ascii', 'strict') # Only use Blockette 1001 if necessary. if use_blkt_1001: size = C.sizeof(blkt_1001_s) blkt1001 = C.c_char(b' ') 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 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(b' ') C.memset(C.pointer(blkt100), 0, size) ret_val = clibmseed.msr_addblockette( msr, C.pointer(blkt100), size, 100, 0) # NOQA # 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)) # NOQA del msr # NOQA raise Exception('Error in msr_addblockette') # Pack mstg into a MSEED file using the callback record_handler as # write method. errcode = clibmseed.mst_pack( mst.mst, recHandler, None, trace_attr['reclen'], trace_attr['encoding'], trace_attr['byteorder'], C.byref(packedsamples), flush, verbose, msr) # NOQA if errcode == 0: msg = ("Did not write any data for trace '%s' even though it " "contains data values.") % trace raise ValueError(msg) if errcode == -1: clibmseed.msr_free(C.pointer(msr)) # NOQA del mst, msr # NOQA raise Exception('Error in mst_pack') # Deallocate any allocated memory. clibmseed.msr_free(C.pointer(msr)) # NOQA del mst, msr # NOQA # Close if its a file handler. if not hasattr(filename, 'write'): f.close()