From 92cea08efedd785ff818bb1aaec5acec1340923e Mon Sep 17 00:00:00 2001
From: Jeremy Fee <jmfee@usgs.gov>
Date: Wed, 20 Jan 2016 12:12:16 -0700
Subject: [PATCH] Move algorithms to separate package, abstract algorithm
 arguments

---
 geomagio/Controller.py                        | 33 +++------
 geomagio/__init__.py                          |  5 --
 geomagio/{ => algorithm}/Algorithm.py         | 24 +++++-
 .../{ => algorithm}/AlgorithmException.py     |  0
 geomagio/{ => algorithm}/DeltaFAlgorithm.py   | 37 ++++++++--
 geomagio/{ => algorithm}/XYZAlgorithm.py      | 73 ++++++++++++++-----
 geomagio/algorithm/__init__.py                | 28 +++++++
 test/Controller_test.py                       |  3 +-
 test/__init__.py                              |  0
 test/{ => algorithm_test}/Algorithm_test.py   |  2 +-
 .../{ => algorithm_test}/XYZAlgorithm_test.py |  4 +-
 test/algorithm_test/__init__.py               |  0
 test/edge_test/__init__.py                    |  0
 test/iaga2002_test/__init__.py                |  0
 test/imfv283_test/__init__.py                 |  0
 test/pcdcp_test/__init__.py                   |  0
 16 files changed, 149 insertions(+), 60 deletions(-)
 rename geomagio/{ => algorithm}/Algorithm.py (79%)
 rename geomagio/{ => algorithm}/AlgorithmException.py (100%)
 rename geomagio/{ => algorithm}/DeltaFAlgorithm.py (66%)
 rename geomagio/{ => algorithm}/XYZAlgorithm.py (62%)
 create mode 100644 geomagio/algorithm/__init__.py
 create mode 100644 test/__init__.py
 rename test/{ => algorithm_test}/Algorithm_test.py (95%)
 rename test/{ => algorithm_test}/XYZAlgorithm_test.py (92%)
 create mode 100644 test/algorithm_test/__init__.py
 create mode 100644 test/edge_test/__init__.py
 create mode 100644 test/iaga2002_test/__init__.py
 create mode 100644 test/imfv283_test/__init__.py
 create mode 100644 test/pcdcp_test/__init__.py

diff --git a/geomagio/Controller.py b/geomagio/Controller.py
index 60094a117..f6142619a 100644
--- a/geomagio/Controller.py
+++ b/geomagio/Controller.py
@@ -4,7 +4,7 @@
 import argparse
 import sys
 from obspy.core import UTCDateTime
-from Algorithm import Algorithm
+from algorithm import algorithms
 import TimeseriesUtility
 from TimeseriesFactoryException import TimeseriesFactoryException
 from Util import ObjectView
@@ -14,9 +14,6 @@ import iaga2002
 import pcdcp
 import imfv283
 
-from DeltaFAlgorithm import DeltaFAlgorithm
-from XYZAlgorithm import XYZAlgorithm
-
 
 class Controller(object):
     """Controller for geomag algorithms.
@@ -301,17 +298,8 @@ def main(args):
     else:
             print >> sys.stderr, "Missing required output directive"
 
-    if args.xyz is not None:
-        algorithm = XYZAlgorithm(informat=args.xyz[0],
-                outformat=args.xyz[1])
-    elif args.deltaf is not None:
-        algorithm = DeltaFAlgorithm(informat=args.deltaf)
-    else:
-        # TODO get smarter on inchannels/outchannels since input doesn't always
-        # need to use the --inchannels argument, but might (as in iaga2002),
-        # get it from the file.
-        algorithm = Algorithm(inchannels=args.inchannels,
-                outchannels=args.outchannels or args.inchannels)
+    algorithm = algorithms[args.algorithm]()
+    algorithm.configure(args)
 
     # TODO check for unused arguments.
 
@@ -481,14 +469,11 @@ def parse_args(args):
             help='Edge IP #. See --output-edge-* for other optional arguments')
 
     # Algorithms group
-    algorithm_group = parser.add_mutually_exclusive_group()
-    algorithm_group.add_argument('--xyz',
-            nargs=2,
-            choices=['geo', 'mag', 'obs', 'obsd'],
-            help='Enter the geomagnetic orientation(s) you want to read from' +
-                    ' and to respectfully.')
-    algorithm_group.add_argument('--deltaf',
-            choices=['geo', 'obs', 'obsd'],
-            help='Enter the geomagnetic orientation you want to read from')
+    parser.add_argument('--algorithm',
+            choices=[k for k in algorithms],
+            default='default')
+
+    for k in algorithms:
+        algorithms[k].add_arguments(parser)
 
     return parser.parse_args(args)
diff --git a/geomagio/__init__.py b/geomagio/__init__.py
index 4b0f6a79f..9e086ee63 100644
--- a/geomagio/__init__.py
+++ b/geomagio/__init__.py
@@ -4,19 +4,14 @@ Geomag Algorithm Module
 import ChannelConverter
 import StreamConverter
 
-from Algorithm import Algorithm
-from AlgorithmException import AlgorithmException
 from Controller import Controller
 from ObservatoryMetadata import ObservatoryMetadata
 from TimeseriesFactory import TimeseriesFactory
 from TimeseriesFactoryException import TimeseriesFactoryException
 import TimeseriesUtility
 import Util
-from XYZAlgorithm import XYZAlgorithm
 
 __all__ = [
-    'Algorithm',
-    'AlgorithmException',
     'ChannelConverter',
     'Controller',
     'DeltaFAlgorithm',
diff --git a/geomagio/Algorithm.py b/geomagio/algorithm/Algorithm.py
similarity index 79%
rename from geomagio/Algorithm.py
rename to geomagio/algorithm/Algorithm.py
index bd7699089..fed7196b9 100644
--- a/geomagio/Algorithm.py
+++ b/geomagio/algorithm/Algorithm.py
@@ -1,6 +1,6 @@
 """Algorithm Interface."""
 
-import TimeseriesUtility
+from .. import TimeseriesUtility
 
 
 class Algorithm(object):
@@ -91,3 +91,25 @@ class Algorithm(object):
                     endtime < input_gap[2]):
                 return False
         return True
+
+    @classmethod
+    def add_arguments(cls, parser):
+        """Add command line arguments to argparse parser.
+
+        Parameters
+        ----------
+        parser: ArgumentParser
+            command line argument parser
+        """
+        pass
+
+    def configure(self, arguments):
+        """Configure algorithm using comand line arguments.
+
+        Parameters
+        ----------
+        arguments: Namespace
+            parsed command line arguments
+        """
+        self._inchannels = arguments.inchannels
+        self._outchannels = arguments.outchannels or arguments.inchannels
diff --git a/geomagio/AlgorithmException.py b/geomagio/algorithm/AlgorithmException.py
similarity index 100%
rename from geomagio/AlgorithmException.py
rename to geomagio/algorithm/AlgorithmException.py
diff --git a/geomagio/DeltaFAlgorithm.py b/geomagio/algorithm/DeltaFAlgorithm.py
similarity index 66%
rename from geomagio/DeltaFAlgorithm.py
rename to geomagio/algorithm/DeltaFAlgorithm.py
index d0619dee6..616c83d87 100644
--- a/geomagio/DeltaFAlgorithm.py
+++ b/geomagio/algorithm/DeltaFAlgorithm.py
@@ -4,7 +4,7 @@
 
 from Algorithm import Algorithm
 from AlgorithmException import AlgorithmException
-import StreamConverter as StreamConverter
+from .. import StreamConverter
 
 # List of channels by geomagnetic observatory orientation.
 # geo represents a geographic north/south orientation
@@ -27,10 +27,10 @@ class DeltaFAlgorithm(Algorithm):
         will be converting from.
     """
 
-    def __init__(self, informat=None):
+    def __init__(self, informat='obs'):
         Algorithm.__init__(self, inchannels=CHANNELS[informat],
                 outchannels=['G'])
-        self.informat = informat
+        self._informat = informat
 
     def check_stream(self, timeseries):
         """checks a stream to make certain all the required channels
@@ -61,10 +61,35 @@ class DeltaFAlgorithm(Algorithm):
         """
         self.check_stream(timeseries)
         out_stream = None
-
-        if self.informat == 'geo':
+        informat = self._informat
+        if informat == 'geo':
             out_stream = StreamConverter.get_deltaf_from_geo(timeseries)
-        elif self.informat == 'obs' or self.informat == 'obsd':
+        elif informat == 'obs' or informat == 'obsd':
             out_stream = StreamConverter.get_deltaf_from_obs(timeseries)
 
         return out_stream
+
+    @classmethod
+    def add_arguments(cls, parser):
+        """Add command line arguments to argparse parser.
+
+        Parameters
+        ----------
+        parser: ArgumentParser
+            command line argument parser
+        """
+        parser.add_argument('--deltaf-from',
+                choices=['geo', 'obs', 'obsd'],
+                default='obs',
+                help='Geomagnetic orientation to read from')
+
+    def configure(self, arguments):
+        """Configure algorithm using comand line arguments.
+
+        Parameters
+        ----------
+        arguments: Namespace
+            parsed command line arguments
+        """
+        self._informat = arguments.deltaf_from
+        self._inchannels = CHANNELS[self._informat]
diff --git a/geomagio/XYZAlgorithm.py b/geomagio/algorithm/XYZAlgorithm.py
similarity index 62%
rename from geomagio/XYZAlgorithm.py
rename to geomagio/algorithm/XYZAlgorithm.py
index 12c3a1e0c..64d1e9158 100644
--- a/geomagio/XYZAlgorithm.py
+++ b/geomagio/algorithm/XYZAlgorithm.py
@@ -5,7 +5,7 @@
 
 from Algorithm import Algorithm
 from AlgorithmException import AlgorithmException
-import StreamConverter as StreamConverter
+from .. import StreamConverter
 
 # List of channels by geomagnetic observatory orientation.
 # geo represents a geographic north/south orientation
@@ -33,11 +33,11 @@ class XYZAlgorithm(Algorithm):
         be converting to.
     """
 
-    def __init__(self, informat=None, outformat=None):
+    def __init__(self, informat='obs', outformat='geo'):
         Algorithm.__init__(self, inchannels=CHANNELS[informat],
                 outchannels=CHANNELS[outformat])
-        self.informat = informat
-        self.outformat = outformat
+        self._informat = informat
+        self._outformat = outformat
 
     def check_stream(self, timeseries):
         """checks an stream to make certain all the required channels
@@ -69,36 +69,69 @@ class XYZAlgorithm(Algorithm):
         """
         self.check_stream(timeseries)
         out_stream = None
-        if self.outformat == 'geo':
-            if self.informat == 'geo':
+        informat = self._informat
+        outformat = self._outformat
+        if outformat == 'geo':
+            if informat == 'geo':
                 out_stream = timeseries
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = StreamConverter.get_geo_from_mag(timeseries)
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_geo_from_obs(timeseries)
-        elif self.outformat == 'mag':
-            if self.informat == 'geo':
+        elif outformat == 'mag':
+            if informat == 'geo':
                 out_stream = StreamConverter.get_mag_from_geo(timeseries)
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = timeseries
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_mag_from_obs(timeseries)
-        elif self.outformat == 'obs':
-            if self.informat == 'geo':
+        elif outformat == 'obs':
+            if informat == 'geo':
                 out_stream = StreamConverter.get_obs_from_geo(timeseries)
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = StreamConverter.get_obs_from_mag(timeseries)
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_obs_from_obs(timeseries,
                         include_e=True)
-        elif self.outformat == 'obsd':
-            if self.informat == 'geo':
+        elif outformat == 'obsd':
+            if informat == 'geo':
                 out_stream = StreamConverter.get_obs_from_geo(timeseries,
                         include_d=True)
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = StreamConverter.get_obs_from_mag(timeseries,
                         include_d=True)
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_obs_from_obs(timeseries,
                         include_d=True)
         return out_stream
+
+    @classmethod
+    def add_arguments(cls, parser):
+        """Add command line arguments to argparse parser.
+
+        Parameters
+        ----------
+        parser: ArgumentParser
+            command line argument parser
+        """
+        parser.add_argument('--xyz-from',
+                choices=['geo', 'mag', 'obs', 'obsd'],
+                default='obs',
+                help='Geomagnetic orientation to read from')
+        parser.add_argument('--xyz-to',
+                choices=['geo', 'mag', 'obs', 'obsd'],
+                default='geo',
+                help='Geomagnetic orientation to convert to')
+
+    def configure(self, arguments):
+        """Configure algorithm using comand line arguments.
+
+        Parameters
+        ----------
+        arguments: Namespace
+            parsed command line arguments
+        """
+        self._informat = arguments.xyz_from
+        self._outformat = arguments.xyz_to
+        self._inchannels = CHANNELS[self._informat]
+        self._outchannels = CHANNELS[self._outformat]
diff --git a/geomagio/algorithm/__init__.py b/geomagio/algorithm/__init__.py
new file mode 100644
index 000000000..caaabb0c7
--- /dev/null
+++ b/geomagio/algorithm/__init__.py
@@ -0,0 +1,28 @@
+"""
+Geomag Algorithms module
+"""
+
+# base classes
+from Algorithm import Algorithm
+from AlgorithmException import AlgorithmException
+# algorithms
+from DeltaFAlgorithm import DeltaFAlgorithm
+from XYZAlgorithm import XYZAlgorithm
+
+
+# algorithms is used by Controller to auto generate arguments
+algorithms = {
+    'identity': Algorithm,
+    'deltaf': DeltaFAlgorithm,
+    'xyz': XYZAlgorithm
+}
+
+
+__all__ = [
+    # base classes
+    'Algorithm',
+    'AlgorithmException',
+    # algorithms
+    'DeltaFAlgorithm',
+    'XYZAlgorithm'
+]
diff --git a/test/Controller_test.py b/test/Controller_test.py
index e407a9eca..4b1992c98 100644
--- a/test/Controller_test.py
+++ b/test/Controller_test.py
@@ -1,5 +1,6 @@
 #! /usr/bin/env python
-from geomagio import Algorithm, Controller, TimeseriesFactory
+from geomagio import Controller, TimeseriesFactory
+from geomagio.algorithm import Algorithm
 from nose.tools import assert_is_instance
 
 
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/Algorithm_test.py b/test/algorithm_test/Algorithm_test.py
similarity index 95%
rename from test/Algorithm_test.py
rename to test/algorithm_test/Algorithm_test.py
index cf4d2e3cb..8ce878f1d 100644
--- a/test/Algorithm_test.py
+++ b/test/algorithm_test/Algorithm_test.py
@@ -2,7 +2,7 @@
 from obspy.core.stream import Stream
 from nose.tools import assert_equals
 from nose.tools import assert_is_instance
-from geomagio import Algorithm
+from geomagio.algorithm import Algorithm
 
 
 def test_algorithm_process():
diff --git a/test/XYZAlgorithm_test.py b/test/algorithm_test/XYZAlgorithm_test.py
similarity index 92%
rename from test/XYZAlgorithm_test.py
rename to test/algorithm_test/XYZAlgorithm_test.py
index b84f87198..0ffcfb3ec 100644
--- a/test/XYZAlgorithm_test.py
+++ b/test/algorithm_test/XYZAlgorithm_test.py
@@ -2,8 +2,8 @@
 from obspy.core.stream import Stream
 from nose.tools import assert_equals
 from nose.tools import assert_is
-from geomagio import XYZAlgorithm
-from StreamConverter_test import __create_trace
+from geomagio.algorithm import XYZAlgorithm
+from ..StreamConverter_test import __create_trace
 
 
 def test_xyzalgorithm_process():
diff --git a/test/algorithm_test/__init__.py b/test/algorithm_test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/edge_test/__init__.py b/test/edge_test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/iaga2002_test/__init__.py b/test/iaga2002_test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/imfv283_test/__init__.py b/test/imfv283_test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/pcdcp_test/__init__.py b/test/pcdcp_test/__init__.py
new file mode 100644
index 000000000..e69de29bb
-- 
GitLab