Exemplo n.º 1
0
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
Exemplo n.º 2
0
Arquivo: util.py Projeto: cv90/obspy
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
Exemplo n.º 4
0
 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
Exemplo n.º 5
0
    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")
Exemplo n.º 6
0
 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)
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
    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")
Exemplo n.º 9
0
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()
Exemplo n.º 10
0
Arquivo: core.py Projeto: kaeufl/obspy
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()