def component(self, comp): """set component in metadata and carry through""" if self.channel_metadata.type == "electric": if comp[0].lower() != "e": msg = ( "The current timeseries is an electric channel. " "Cannot change channel type, create a new ChannelTS object." ) self.logger.error(msg) raise MTTSError(msg) elif self.channel_metadata.type == "magnetic": if comp[0].lower() not in ["h", "b"]: msg = ( "The current timeseries is a magnetic channel. " "Cannot change channel type, create a new ChannelTS object." ) self.logger.error(msg) raise MTTSError(msg) if self.channel_metadata.type == "auxiliary": if comp[0].lower() in ["e", "h", "b"]: msg = ( "The current timeseries is an auxiliary channel. " "Cannot change channel type, create a new ChannelTS object." ) self.logger.error(msg) raise MTTSError(msg) self.channel_metadata.component = comp self._update_xarray_metadata()
def from_obspy_trace(self, obspy_trace): """ Fill data from an :class:`obspy.core.Trace` :param obspy.core.trace obspy_trace: Obspy trace object """ if not isinstance(obspy_trace, Trace): msg = f"Input must be obspy.core.Trace, not {type(obspy_trace)}" self.logger.error(msg) raise MTTSError(msg) if obspy_trace.stats.channel[1].lower() in ["e", "q"]: self.channel_metadata = metadata.Electric() elif obspy_trace.stats.channel[1].lower() in ["h", "b", "f"]: self.channel_metadata = metadata.Magnetic() else: self.channel_metadata = metadata.Auxiliary() mt_code = fdsn_tools.make_mt_channel( fdsn_tools.read_channel_code(obspy_trace.stats.channel)) self.channel_metadata.component = mt_code self.start = obspy_trace.stats.starttime.isoformat() self.sample_rate = obspy_trace.stats.sampling_rate self.station_metadata.fdsn.id = obspy_trace.stats.station self.station_metadata.fdsn.network = obspy_trace.stats.network self.station_metadata.id = obspy_trace.stats.station self.channel_metadata.units = "counts" self.ts = obspy_trace.data
def __init__(self, array_list=None, run_metadata=None, station_metadata=None): self.logger = setup_logger(f"{__name__}.{self.__class__.__name__}") self.run_metadata = metadata.Run() self.station_metadata = metadata.Station() self._dataset = xr.Dataset() # load the arrays first this will write run and station metadata if array_list is not None: self.dataset = array_list # if the use inputs metadata, overwrite all values in the metadata element if run_metadata is not None: if isinstance(run_metadata, dict): # make sure the input dictionary has the correct form if "Run" not in list(run_metadata.keys()): run_metadata = {"Run": run_metadata} self.run_metadata.from_dict(run_metadata) elif isinstance(run_metadata, metadata.Run): self.run_metadata.from_dict(run_metadata.to_dict()) else: msg = ("Input metadata must be a dictionary or Run object, " f"not {type(run_metadata)}") self.logger.error(msg) raise MTTSError(msg) # add station metadata, this will be important when propogating a run if station_metadata is not None: if isinstance(station_metadata, metadata.Station): self.station_metadata.from_dict(station_metadata.to_dict()) elif isinstance(station_metadata, dict): if "Station" not in list(station_metadata.keys()): station_metadata = {"Station": station_metadata} self.station_metadata.from_dict(station_metadata) else: msg = "input metadata must be type %s or dict, not %s" self.logger.error(msg, type(self.station_metadata), type(station_metadata)) raise MTTSError( msg % (type(self.station_metadata), type(station_metadata)))
def add_channel(self, channel): """ Add a channel to the dataset, can be an :class:`xarray.DataArray` or :class:`mth5.timeseries.ChannelTS` object. Need to be sure that the coordinates and dimensions are the same as the existing dataset, namely coordinates are time, and dimensions are the same, if the dimesions are larger than the existing dataset then the added channel will be clipped to the dimensions of the existing dataset. If the start time is not the same nan's will be placed at locations where the timing does not match the current start time. This is a feature of xarray. :param channel: a channel xarray or ChannelTS to add to the run :type channel: :class:`xarray.DataArray` or :class:`mth5.timeseries.ChannelTS` """ if isinstance(channel, xr.DataArray): c = ChannelTS() c.ts = channel elif isinstance(channel, ChannelTS): c = channel self.run_metadata.channels.append(c.channel_metadata) else: raise ValueError( "Input Channel must be type xarray.DataArray or ChannelTS") ### need to validate the channel to make sure sample rate is the same if c.sample_rate != self.sample_rate: msg = ( f"Channel sample rate is not correct, current {self.sample_rate} " + f"input {c.sample_rate}") self.logger.error(msg) raise MTTSError(msg) ### should probably check for other metadata like station and run? self._dataset[c.component] = c.ts
def _validate_array_list(self, array_list): """check to make sure all entries are a :class:`ChannelTS` object""" if not isinstance(array_list, (tuple, list)): msg = f"array_list must be a list or tuple, not {type(array_list)}" self.logger.error(msg) raise TypeError(msg) valid_list = [] for index, item in enumerate(array_list): if not isinstance(item, (ChannelTS, xr.DataArray)): msg = f"array entry {index} must be ChannelTS object not {type(item)}" self.logger.error(msg) raise TypeError(msg) if isinstance(item, ChannelTS): valid_list.append(item.to_xarray()) # if a channelTS is input then it comes with run and station metadata # use those first, then the user can update later. self.run_metadata.channels.append(item.channel_metadata) if index == 0: self.station_metadata.from_dict( item.station_metadata.to_dict()) self.run_metadata.from_dict(item.run_metadata.to_dict()) else: self.station_metadata.update(item.station_metadata, match=["id"]) self.run_metadata.update(item.run_metadata, match=["id"]) else: valid_list.append(item) # probably should test for sampling rate. sr_test = dict([(item.component, (item.sample_rate)) for item in valid_list]) if len(set([v for k, v in sr_test.items()])) != 1: msg = f"sample rates are not all the same {sr_test}" self.logger.error(msg) raise MTTSError(msg) return valid_list
def from_obspy_stream(self, obspy_stream, run_metadata=None): """ Get a run from an :class:`obspy.core.stream` which is a list of :class:`obspy.core.Trace` objects. :param obspy_stream: Obspy Stream object :type obspy_stream: :class:`obspy.core.Stream` """ if not isinstance(obspy_stream, Stream): msg = f"Input must be obspy.core.Stream not {type(obspy_stream)}" self.logger.error(msg) raise MTTSError(msg) array_list = [] station_list = [] for obs_trace in obspy_stream: channel_ts = ChannelTS() channel_ts.from_obspy_trace(obs_trace) if channel_ts.channel_metadata.component == "e1": channel_ts.channel_metadata.component = "ex" if channel_ts.channel_metadata.component == "e2": channel_ts.channel_metadata.component = "ey" if channel_ts.channel_metadata.component == "h1": channel_ts.channel_metadata.component = "hx" if channel_ts.channel_metadata.component == "h2": channel_ts.channel_metadata.component = "hy" if channel_ts.channel_metadata.component == "h3": channel_ts.channel_metadata.component = "hz" if run_metadata: try: ch = [ ch for ch in run_metadata.channels if ch.component == channel_ts.component ][0] channel_ts.channel_metadata.update(ch) except IndexError: self.logger.warning("could not find %s" % channel_ts.component) station_list.append(channel_ts.station_metadata.fdsn.id) array_list.append(channel_ts) ### need to merge metadata into something useful, station name is the only ### name that is preserved try: station = list(set([ss for ss in station_list if ss is not None]))[0] except IndexError: station = None msg = "Could not find station name" self.logger.warn(msg) self.station_metadata.fdsn.id = station self.set_dataset(array_list) # need to be sure update any input metadata. if run_metadata is not None: self.run_metadata.update(run_metadata) self.validate_metadata()
def ts(self, ts_arr): """ if setting ts with a pandas data frame, make sure the data is in a column name 'data' """ if isinstance(ts_arr, (np.ndarray, list, tuple)): if not isinstance(ts_arr, np.ndarray): ts_arr = np.array(ts_arr) # Validate an input array to make sure its 1D if len(ts_arr.shape) == 2: if 1 in ts_arr.shape: ts_arr = ts_arr.reshape(ts_arr.size) else: msg = f"Input array must be 1-D array not {ts_arr.shape}" self.logger.error(msg) raise ValueError(msg) dt = make_dt_coordinates(self.start, self.sample_rate, ts_arr.size, self.logger) self._ts = xr.DataArray(ts_arr, coords=[("time", dt)], name="ts") self._update_xarray_metadata() elif isinstance(ts_arr, pd.core.frame.DataFrame): if isinstance(ts_arr.index[0], pd._libs.tslibs.timestamps.Timestamp): dt = ts_arr.index else: dt = make_dt_coordinates( self.start, self.sample_rate, ts_arr["data"].size, self.logger, ) try: self._ts = xr.DataArray(ts_arr["data"], coords=[("time", dt)], name="ts") self._update_xarray_metadata() except AttributeError: msg = ("Data frame needs to have a column named `data` " + "where the time series data is stored") self.logger.error(msg) raise MTTSError(msg) elif isinstance(ts_arr, pd.core.series.Series): if isinstance(ts_arr.index[0], pd._libs.tslibs.timestamps.Timestamp): dt = ts_arr.index else: dt = make_dt_coordinates( self.start, self.sample_rate, ts_arr["data"].size, self.logger, ) self._ts = xr.DataArray(ts_arr.values, coords=[("time", dt)], name="ts") self._update_xarray_metadata() elif isinstance(ts_arr, xr.DataArray): # TODO: need to validate the input xarray self._ts = ts_arr # need to pull out the metadata as a separate dictionary meta_dict = dict([(k, v) for k, v in ts_arr.attrs.items()]) # need to get station and run metadata out station_keys = [k for k in meta_dict.keys() if "station." in k] run_keys = [k for k in meta_dict.keys() if "run." in k] station_dict = {} run_dict = {} for key in station_keys: station_dict[key.split("station.")[-1]] = meta_dict.pop(key) for key in run_keys: run_dict[key.split("run.")[-1]] = meta_dict.pop(key) self.channel_metadata.from_dict({meta_dict["type"]: meta_dict}) self.station_metadata.from_dict({"station": station_dict}) self.run_metadata.from_dict({"run": run_dict}) # need to run this incase things are different. self._update_xarray_metadata() else: msg = ("Data type {0} not supported".format(type(ts_arr)) + ", ts needs to be a numpy.ndarray, pandas DataFrame, " + "or xarray.DataArray.") raise MTTSError(msg)
def __init__( self, channel_type="auxiliary", data=None, channel_metadata=None, station_metadata=None, run_metadata=None, **kwargs, ): self.logger = setup_logger(f"{__name__}.{self.__class__.__name__}") self.station_metadata = metadata.Station() self.run_metadata = metadata.Run() self._ts = xr.DataArray([1], coords=[("time", [1])], name="ts") self._channel_response = ChannelResponseFilter() # get correct metadata class try: self.channel_metadata = meta_classes[channel_type.capitalize()]() self.channel_metadata.type = channel_type.lower() except KeyError: msg = ("Channel type is undefined, must be [ electric | " + "magnetic | auxiliary ]") self.logger.error(msg) raise ValueError(msg) if channel_metadata is not None: if isinstance(channel_metadata, type(self.channel_metadata)): self.channel_metadata.update(channel_metadata) self.logger.debug("Loading from metadata class {0}".format( type(self.channel_metadata))) elif isinstance(channel_metadata, dict): if not channel_type in [ cc.lower() for cc in channel_metadata.keys() ]: channel_metadata = {channel_type: channel_metadata} self.channel_metadata.from_dict(channel_metadata) self.logger.debug("Loading from metadata dict") else: msg = "input metadata must be type %s or dict, not %s" self.logger.error(msg, type(self.channel_metadata), type(channel_metadata)) raise MTTSError( msg % (type(self.channel_metadata), type(channel_metadata))) # add station metadata, this will be important when propogating a single # channel such that it can stand alone. if station_metadata is not None: if isinstance(station_metadata, metadata.Station): self.station_metadata.update(station_metadata) elif isinstance(station_metadata, dict): if not "station" in [ cc.lower() for cc in station_metadata.keys() ]: station_metadata = {"Station": station_metadata} self.station_metadata.from_dict(station_metadata) self.logger.debug("Loading from metadata dict") else: msg = "input metadata must be type {0} or dict, not {1}".format( type(self.station_metadata), type(station_metadata)) self.logger.error(msg) raise MTTSError(msg) # add run metadata, this will be important when propogating a single # channel such that it can stand alone. if run_metadata is not None: if isinstance(run_metadata, metadata.Run): self.run_metadata.update(run_metadata) elif isinstance(run_metadata, dict): if not "run" in [cc.lower() for cc in run_metadata.keys()]: run_metadata = {"Run": run_metadata} self.run_metadata.from_dict(run_metadata) self.logger.debug("Loading from metadata dict") else: msg = "input metadata must be type %s or dict, not %s" self.logger.error(msg, type(self.run_metadata), type(run_metadata)) raise MTTSError(msg % (type(self.run_metadata), type(run_metadata))) # input data if data is not None: self.ts = data self._update_xarray_metadata() for key in list(kwargs.keys()): setattr(self, key, kwargs[key])