diff --git a/bin/geomag.py b/bin/geomag.py index 6f5a6573f5b70c79a764bc309b8a0f1fd1e22c51..7d055eeee0b6949d7d4736c46eeba9bfec776a54 100755 --- a/bin/geomag.py +++ b/bin/geomag.py @@ -1,26 +1,5 @@ #! /usr/bin/env python -"""Converts iaga2002 files from one coordinate system to another. - - Inputs - ------ - informat: string - The input format/coordinate system of the input file. - geo: geographic coordinate system (xyzf) - mag: magnetic north coordinate system (hdzf) - obs: observatory coordinate system (hezf) - obsd: observatory coordinate system (hdzf) - outformat: string - The ouput format/coordinate system of the output file. - geo: geographic coordinate system (xyzf) - mag: magnetic north coordinate system (hdzf) - obs: observatory coordinate system (hezf or hdzf) - infile: string - the filename of the Iaga2002 file to be read from - outfile: string - the filename of a new Iaga2002 file to be read to -""" - import argparse import sys @@ -42,6 +21,71 @@ from obspy.core.utcdatetime import UTCDateTime def main(): + """command line factory for geomag algorithms + + Inputs + ------ + --input: string + the type of data for input + currently either iaga or edge. + --output: string + the type of data for ouput + currently either iaga or edge. + --starttime: string + formatted as a obspy.core.UTCDateTime object + the starttime for data input/output + --endtime: string + formatted as a obspy.core.UTCDateTime object + the endtime for data input/output + --observatory:string + + --channels: array_like + list of channels + --type: string + data type + --invterval: string + data interval. + --algorithm: string + name of an algorithm to use. + --xyz-informat: string + The input format/coordinate system of the input file. + geo: geographic coordinate system (xyzf) + mag: magnetic north coordinate system (hdzf) + obs: observatory coordinate system (hezf) + obsd: observatory coordinate system (hdzf) + --xyz-outformat: string + The ouput format/coordinate system of the output file. + geo: geographic coordinate system (xyzf) + mag: magnetic north coordinate system (hdzf) + obs: observatory coordinate system (hezf or hdzf) + --input_iaga_magweb: boolean + indicates to use http://magweb.cr.usgs.gov/data/magnetometer/ as the + source of iaga2002 files. + --input_iaga_url: string + url of iaga2002 files to use as the data source. + --input-iaga-urltemplate: string + template for the subdirectories that files are found in. + example: %(OBS)s/%(interval)s%(type)s/ + --input-iaga-filetemplate: string + template for the file name + example: %(obs)s%(ymd)s%(t)s%(i)s.%(i)s + --input-iaga-file: string + the filename of the Iaga2002 file to be read from + --input-iaga-stdin: boolean + indicates the file will be coming from stdin + --output_iaga_file: string + the filename of a new Iaga2002 file to be read to + --output-iaga-url: string + url of directory to write output files in. + --output-iaga-urltemplate: string + template for the subdirectories that files are to be written in. + example: %(OBS)s/%(interval)s%(type)s/ + --output-iaga-filetemplate: string + template for the file name + example: %(obs)s%(ymd)s%(t)s%(i)s.%(i)s + --output-iaga-stdout: boolen + indicates output will go to stdout + """ args = parse_args() @@ -119,6 +163,13 @@ def main(): controller.run(UTCDateTime(args.starttime), UTCDateTime(args.endtime)) def parse_args(): + """parse input arguments + + Returns + ------- + argparse.Namespace + dictionary like object containing arguments. + """ parser = argparse.ArgumentParser( description='Use @ to read commands from a file.', fromfile_prefix_chars='@',) diff --git a/geomagio/Algorithm.py b/geomagio/Algorithm.py index a66e7c40bb86e6a209dd5bab84fc6d7ddf64c46b..2af8b10343223fafa3a12c34bff6fa39c160b950 100644 --- a/geomagio/Algorithm.py +++ b/geomagio/Algorithm.py @@ -1,7 +1,17 @@ +"""Algorithm Interface.""" class Algorithm(object): - """An algorithm processes a stream of timeseries to produce new timeseries. + """Base class for geomag algorithms + + Parameters + ---------- + channels: array_like + the list of channels to be processed. + + Notes + ----- + An algorithm processes a stream of timeseries to produce new timeseries. """ def __init__(self, channels=None): @@ -9,7 +19,7 @@ class Algorithm(object): pass def process(self, stream): - """Process a chunk of data. + """Process a stream of data. Parameters ---------- @@ -24,7 +34,21 @@ class Algorithm(object): return stream.copy() def get_input_channels(self): + """Get input channels + + Returns + ------- + array_like + list of channels the algorithm needs to operate. + """ return self._channels def get_output_channels(self): + """Get output channels + + Returns + ------- + array_like + list of channels the algorithm will be returning. + """ return self._channels diff --git a/geomagio/Controller.py b/geomagio/Controller.py index 928fe6297eb669af97a75cddae1f9f3ba1f71b0b..93e4ca65ef273b12ca317321d976ed7451f94f87 100644 --- a/geomagio/Controller.py +++ b/geomagio/Controller.py @@ -1,16 +1,17 @@ -#! /usr/bin/env python +"""Conrtoller class for geomag algorithms""" -"""Converts iaga2002 files from one coordinate system to another. - Inputs - ------ +class Controller(object): + """Controller for geomag algorithms. + + Parameters + ---------- inputFactory: TimeseriesFactory + the factory that will read in timeseries data outputFactory: TimeseriesFactory - algorithm: Algorithm -""" - - -class Controller(object): + the factory that will output the timeseries data + algorithm: the algorithm(s) that will take procees the timeseries data + """ def __init__(self, inputFactory, outputFactory, algorithm=None): self._inputFactory = inputFactory @@ -18,6 +19,15 @@ class Controller(object): self._outputFactory = outputFactory def run(self, starttime, endtime): + """run an algorithm as setup up by the main script. + + Parameters + ---------- + starttime : UTCDateTime + time of first sample to be worked on. + endtime : UTCDateTime + time of last sample to be worked on. + """ input_channels = self._algorithm.get_input_channels() timeseries = self._inputFactory.get_timeseries(starttime, endtime, channels=input_channels) diff --git a/geomagio/XYZAlgorithm.py b/geomagio/XYZAlgorithm.py index c43b4bd6026d063e0908a773abd906404a627709..d71f989e01dee541fc7ba1f81321be595ebf9ea0 100644 --- a/geomagio/XYZAlgorithm.py +++ b/geomagio/XYZAlgorithm.py @@ -1,13 +1,16 @@ -#! /usr/bin/env python - -"""Takes a timeseries stream in, and returns a converted timeseries stream out +"""Algorithm that converts from one geomagnetic coordinate system to a + related coordinate system. """ from Algorithm import Algorithm import StreamConverter as StreamConverter -# static containing the standard output types for iaga2002 files. +# List of channels by geomagnetic observatory orientation. +# geo represents a geographic north/south orientation +# mag represents the (calculated)instantaneous mangnetic north orientation +# obs represents the sensor orientation aligned close to the mag orientation +# obsd is the same as obs, but with D(declination) instead of E (e/w vector) CHANNELS = { 'geo': ['X', 'Y', 'Z', 'F'], 'mag': ['H', 'D', 'Z', 'F'], @@ -17,6 +20,17 @@ CHANNELS = { class XYZAlgorithm(Algorithm): + """Algorithm for converting data, probably inapproprately named XYZ. + + Parameters + ---------- + informat: str + the code that represents the incoming data form that the Algorithm + will be converting from. + outformat: str + the code that represents what form the incoming data will + be converting to. + """ def __init__(self, informat=None, outformat=None): Algorithm.__init__(self) @@ -24,14 +38,14 @@ class XYZAlgorithm(Algorithm): self.outformat = outformat def check_stream(self, timeseries, channels): - """checks an input stream to make certain all the required channels + """checks an stream to make certain all the required channels exist. Parameters ---------- timeseries: obspy.core.Stream - stream that was read in. - channels: array + stream to be checked. + channels: array_like channels that are expected in stream. """ for channel in channels: @@ -41,9 +55,23 @@ class XYZAlgorithm(Algorithm): return True def get_input_channels(self): + """Get input channels + + Returns + ------- + array_like + list of channels the algorithm needs to operate. + """ return CHANNELS[self.informat] def get_output_channels(self): + """Get output channels + + Returns + ------- + array_like + list of channels the algorithm will be returning. + """ return CHANNELS[self.outformat] def process(self, timeseries): diff --git a/geomagio/edge/EdgeFactory.py b/geomagio/edge/EdgeFactory.py index e451b7e9352b7fd8f6cdd3809c663a07266e44e9..9ce07c5b6ef76c39d982059f7314c3828441785f 100644 --- a/geomagio/edge/EdgeFactory.py +++ b/geomagio/edge/EdgeFactory.py @@ -1,4 +1,13 @@ -"""Factory that loads data from earthworm and writes to Edge.""" +"""Factory that loads data from earthworm and writes to Edge. + +EdgeFactory uses obspy earthworm class to read data from any +earthworm standard Waveserver using the obspy getWaveform call. + +Writing will be implemented with Edge specific capabilities, +to take advantage of it's newer realtime abilities. + +Edge is the USGS earthquake hazard centers replacement for earthworm. +""" import obspy.core from obspy.core.utcdatetime import UTCDateTime @@ -9,6 +18,22 @@ from ObservatoryMetadata import ObservatoryMetadata class EdgeFactory(TimeseriesFactory): + """TimeseriesFactory for Edge related data. + + Parameters + ---------- + host: str + a string representing the IP number of the host to connect to. + port: integer + the port number the waveserver is listening on. + observatoryMetadata: ObservatoryMetadata object + an ObservatoryMetadata object used to replace the default + ObservatoryMetadata. + + See Also + -------- + TimeseriesFactory + """ def __init__(self, host=None, port=None, observatory=None, channels=None, type=None, interval=None, @@ -24,12 +49,12 @@ class EdgeFactory(TimeseriesFactory): Parameters ---------- - observatory : str - observatory code. starttime : obspy.core.UTCDateTime time of first sample. endtime : obspy.core.UTCDateTime time of last sample. + observatory : str + observatory code. channels : array_like list of channels to load type : {'variation', 'quasi-definitive'} @@ -294,7 +319,22 @@ class EdgeFactory(TimeseriesFactory): observatory, channel, type, interval) return data - def _clean_timeseries(self, timeseries, starttime, endtime, channels): + def _clean_timeseries(self, timeseries, starttime, endtime): + """Realigns timeseries data so the start and endtimes are the same + as what was originally asked for, even if the data was during + a gap. + + Parameters + ---------- + timeseries: obspy.core.stream + The timeseries stream as returned by the call to getWaveform + starttime: obspy.core.UTCDateTime + the starttime of the requested data + endtime: obspy.core.UTCDateTime + the endtime of the requested data + + Notes: the original timeseries object is changed. + """ for trace in timeseries: trace_starttime = UTCDateTime(trace.stats.starttime) trace_endtime = UTCDateTime(trace.stats.endtime) @@ -312,13 +352,32 @@ class EdgeFactory(TimeseriesFactory): numpy.full(cnt, numpy.nan, dtype=numpy.float64)]) trace.stats.endttime = endtime - def _post_process(self, stream, starttime, endtime, channels): - for trace in stream: + def _post_process(self, timeseries, starttime, endtime, channels): + """Post process a timeseries stream after the raw data is + is fetched from a waveserver. Specifically changes + any MaskedArray to a ndarray with nans representing gaps. + Then calls _clean_timeseries to deal with gaps at the + beggining or end of the streams. + + Parameters + ---------- + timeseries: obspy.core.stream + The timeseries stream as returned by the call to getWaveform + starttime: obspy.core.UTCDateTime + the starttime of the requested data + endtime: obspy.core.UTCDateTime + the endtime of the requested data + channels: array_like + list of channels to load + + Notes: the original timeseries object is changed. + """ + for trace in timeseries: if isinstance(trace.data, numpy.ma.MaskedArray): trace.data.set_fill_value(numpy.nan) trace.data = trace.data.filled() - self._clean_timeseries(stream, starttime, endtime, channels) + self._clean_timeseries(timeseries, starttime, endtime) # TODO add in test for missing channel, if so, make it all nans? def _set_metadata(self, stream, observatory, channel, type, interval): diff --git a/geomagio/iaga2002/IAGA2002Writer.py b/geomagio/iaga2002/IAGA2002Writer.py index 308da3d1e1c2bd86d70132ebd97d88bda3d151e2..3a2b25882570e016981f1a827e0941bd22cd84a0 100644 --- a/geomagio/iaga2002/IAGA2002Writer.py +++ b/geomagio/iaga2002/IAGA2002Writer.py @@ -15,14 +15,38 @@ class IAGA2002Writer(object): self.empty_value = empty_value def write(self, out, timeseries, channels): + """write timeseries to iaga file + + Parameters + ---------- + out: file object + file object to be written to. could be stdout + timeseries: obspy.core.stream + timeseries object with data to be written + channels: array_like + channels to be written from timeseries object + """ stats = timeseries[0].stats out.write(self._format_headers(stats, channels)) out.write(self._format_comments(stats)) out.write(self._format_channels(channels, stats.station)) out.write(self._format_data(timeseries, channels)) - pass def _format_headers(self, stats, channels): + """format headers for IAGA2002 file + + Parameters + ---------- + stats: obspy.core.trace.stats + holds the observatory metadata + channels: array_like + channels to be reported. + + Returns + ------- + array_like + an array containing formatted strings of header data. + """ buf = [] buf.append(self._format_header('Format', 'IAGA-2002')) buf.append(self._format_header('Source of Data', stats.agency_name)) @@ -44,7 +68,18 @@ class IAGA2002Writer(object): return ''.join(buf) def _format_comments(self, stats): - # build comments + """format comments for IAGA2002 file + + Parameters + ---------- + stats: obspy.core.trace.stats + holds the observatory metadata + + Returns + ------- + array_like + an array containing formatted strings of header data. + """ comments = [] if 'declination_base' in stats: comments.append('DECBAS {:<8d}' @@ -71,11 +106,36 @@ class IAGA2002Writer(object): return ''.join(buf) def _format_header(self, name, value): + """format headers for IAGA2002 file + + Parameters + ---------- + name: str + the name to be written + value: str + the value to written. + + Returns + ------- + str + a string formatted to be a single header line in an IAGA2002 file + """ prefix = ' ' suffix = ' |\n' return ''.join((prefix, name.ljust(23), value.ljust(44), suffix)) def _format_comment(self, comment): + """format header for IAGA2002 file + + Parameters + ---------- + comment: str + a single comment to be broken formatted if needed. + Returns + ------- + str + a string formatted to be a single comment in an IAGA2002 file. + """ buf = [] prefix = ' # ' suffix = ' |\n' diff --git a/geomagio/iaga2002/StreamIAGA2002Factory.py b/geomagio/iaga2002/StreamIAGA2002Factory.py index 81b7cd127d381621ebefde5d02198b041e83c9f6..80066167d9879da7340d48b071084ce15d25488d 100644 --- a/geomagio/iaga2002/StreamIAGA2002Factory.py +++ b/geomagio/iaga2002/StreamIAGA2002Factory.py @@ -1,11 +1,22 @@ -"""Factory that loads IAGA2002 Files.""" +"""Factory to load IAGA2002 files from an input StreamIAGA2002Factory.""" from IAGA2002Factory import IAGA2002Factory -# pattern for iaga 2002 file names - class StreamIAGA2002Factory(IAGA2002Factory): + """Timeseries Factory for IAGA2002 formatted files loaded via a stream. + normally either a single file, or stdio. + + Parameters + ---------- + stream: file object + io stream, normally either a file, or stdio + + See Also + -------- + IAGA2002Factory + Timeseriesfactory + """ def __init__(self, stream, observatory=None, channels=None, type=None, interval=None): IAGA2002Factory.__init__(self, None, observatory, channels, @@ -14,8 +25,20 @@ class StreamIAGA2002Factory(IAGA2002Factory): def get_timeseries(self, starttime, endtime, observatory=None, channels=None, type=None, interval=None): + """Implements get_timeseries + + Notes: Calls IAGA2002Factory.parse_string in place of + IAGA2002Factory.get_timeseries. + """ return IAGA2002Factory.parse_string(self, self._stream) def put_timeseries(self, timeseries, starttime=None, endtime=None, channels=None, type=None, interval=None): + """Implements put_timeseries + + Notes: Calls IAGA2002Factory.write_file in place of + IAGA2002Factory.get_timeseries. This can result in a + non-standard IAGA2002 file, specifically one of longer then + expected length. + """ IAGA2002Factory.write_file(self, self._stream, timeseries, channels)