diff --git a/geomagio/Controller.py b/geomagio/Controller.py
index fcce9de087bf25a145d7fefea453a0202bc0322c..6cd3ae81d76c06c4fa73ad422f9aef2908a1d6e4 100644
--- a/geomagio/Controller.py
+++ b/geomagio/Controller.py
@@ -3,11 +3,9 @@
 
 import argparse
 import sys
-from obspy.core import UTCDateTime
+from obspy.core import Stream, UTCDateTime
 from algorithm import algorithms
 import TimeseriesUtility
-from TimeseriesFactoryException import TimeseriesFactoryException
-from Util import ObjectView
 
 import edge
 import iaga2002
@@ -43,6 +41,97 @@ class Controller(object):
         self._algorithm = algorithm
         self._outputFactory = outputFactory
 
+    def _get_input_timeseries(self, observatory, channels, starttime, endtime):
+        """Get timeseries from the input factory for requested options.
+
+        Parameters
+        ----------
+        observatory : array_like
+            observatories to request.
+        channels : array_like
+            channels to request.
+        starttime : obspy.core.UTCDateTime
+            time of first sample to request.
+        endtime : obspy.core.UTCDateTime
+            time of last sample to request.
+        renames : array_like
+            list of channels to rename
+            each list item should be array_like:
+                the first element is the channel to rename,
+                the last element is the new channel name
+
+        Returns
+        -------
+        timeseries : obspy.core.Stream
+        """
+        timeseries = Stream()
+        for obs in list(observatory):
+            # get input interval for observatory
+            # do this per observatory in case an
+            # algorithm needs different amounts of data
+            input_start, input_end = self._algorithm.get_input_interval(
+                    start=starttime,
+                    end=endtime,
+                    observatory=obs,
+                    channel=channels)
+            timeseries += self._inputFactory.get_timeseries(
+                    observatory=obs,
+                    starttime=input_start,
+                    endtime=input_end,
+                    channels=channels)
+        return timeseries
+
+    def _rename_channels(self, timeseries, renames):
+        """Rename trace channel names.
+
+        Parameters
+        ----------
+        timeseries : obspy.core.Stream
+            stream with channels to rename
+        renames : array_like
+            list of channels to rename
+            each list item should be array_like:
+                the first element is the channel to rename,
+                the last element is the new channel name
+
+        Returns
+        -------
+        timeseries : obspy.core.Stream
+        """
+        for r in renames:
+            from_name, to_name = r[0], r[-1]
+            for t in timeseries.select(channel=from_name):
+                t.stats.channel = to_name
+        return timeseries
+
+    def _get_output_timeseries(self, observatory, channels, starttime,
+            endtime):
+        """Get timeseries from the output factory for requested options.
+
+        Parameters
+        ----------
+        observatory : array_like
+            observatories to request.
+        channels : array_like
+            channels to request.
+        starttime : obspy.core.UTCDateTime
+            time of first sample to request.
+        endtime : obspy.core.UTCDateTime
+            time of last sample to request.
+
+        Returns
+        -------
+        timeseries : obspy.core.Stream
+        """
+        timeseries = Stream()
+        for obs in list(observatory):
+            timeseries += self._outputFactory.get_timeseries(
+                observatory=obs,
+                starttime=starttime,
+                endtime=endtime,
+                channels=channels)
+        return timeseries
+
     def run(self, options):
         """run controller
         Parameters
@@ -52,22 +141,28 @@ class Controller(object):
             contain other options passed in by the controller.
         """
         algorithm = self._algorithm
-        input_channels = algorithm.get_input_channels()
-        output_channels = self._get_output_channels(
-                algorithm.get_output_channels(),
-                options.outchannels)
-        # get input
-        start, end = self._algorithm.get_input_interval(
-                start=options.starttime,
-                end=options.endtime)
-        timeseries = self._inputFactory.get_timeseries(
-                starttime=start,
-                endtime=end,
+        input_channels = options.inchannels or \
+                algorithm.get_input_channels()
+        output_channels = options.outchannels or \
+                algorithm.get_output_channels()
+        # input
+        timeseries = self._get_input_timeseries(
+                observatory=options.observatory,
+                starttime=options.starttime,
+                endtime=options.endtime,
                 channels=input_channels)
         if timeseries.count() == 0:
             return
         # process
+        if options.rename_input_channel:
+            timeseries = self._rename_channels(
+                    timeseries=timeseries,
+                    renames=options.rename_input_channel)
         processed = algorithm.process(timeseries)
+        if options.rename_output_channel:
+            processed = self._rename_channels(
+                    timeseries=processed,
+                    renames=options.rename_output_channel)
         # output
         self._outputFactory.put_timeseries(
                 timeseries=processed,
@@ -95,12 +190,13 @@ class Controller(object):
             period, oldest to newest.
         """
         algorithm = self._algorithm
-        input_channels = algorithm.get_input_channels()
-        output_channels = self._get_output_channels(
-                algorithm.get_output_channels(),
-                options.outchannels)
+        input_channels = options.inchannels or \
+                algorithm.get_input_channels()
+        output_channels = options.outchannels or \
+                algorithm.get_output_channels()
         # request output to see what has already been generated
-        output_timeseries = self._outputFactory.get_timeseries(
+        output_timeseries = self._get_output_timeseries(
+                observatory=options.observatory,
                 starttime=options.starttime,
                 endtime=options.endtime,
                 channels=output_channels)
@@ -109,12 +205,10 @@ class Controller(object):
         output_gaps = TimeseriesUtility.get_merged_gaps(
                 TimeseriesUtility.get_stream_gaps(output_timeseries))
         for output_gap in output_gaps:
-            input_start, input_end = algorithm.get_input_interval(
-                    start=output_gap[0],
-                    end=output_gap[1])
-            input_timeseries = self._inputFactory.get_timeseries(
-                    starttime=input_start,
-                    endtime=input_end,
+            input_timeseries = self._get_input_timeseries(
+                    observatory=options.observatory,
+                    starttime=output_gap[0],
+                    endtime=output_gap[1],
                     channels=input_channels)
             if not algorithm.can_produce_data(
                     starttime=output_gap[0],
@@ -125,40 +219,13 @@ class Controller(object):
             if output_gap[0] == options.starttime:
                 # found fillable gap at start, recurse to previous interval
                 interval = options.endtime - options.starttime
-                self.run_as_update(ObjectView({
-                    'outchannels': options.outchannels,
-                    'starttime': options.starttime - interval - delta,
-                    'endtime': options.starttime - delta
-                }))
+                options.starttime = options.starttime - interval - delta
+                options.endtime = options.starttime - delta
+                self.run_as_update(options)
             # fill gap
-            self.run(ObjectView({
-                'outchannels': options.outchannels,
-                'starttime': output_gap[0],
-                'endtime': output_gap[1]
-            }))
-
-    def _get_output_channels(self, algorithm_channels, commandline_channels):
-        """get output channels
-
-        Parameters
-        ----------
-        algorithm_channels: array_like
-            list of channels required by the algorithm
-        commandline_channels: array_like
-            list of channels requested by the user
-        Notes
-        -----
-        We want to return the channels requested by the user, but we require
-            that they be in the list of channels for the algorithm.
-        """
-        if commandline_channels is not None:
-            for channel in commandline_channels:
-                if channel not in algorithm_channels:
-                    raise TimeseriesFactoryException(
-                        'Output "%s" Channel not in Algorithm'
-                            % channel)
-            return commandline_channels
-        return algorithm_channels
+            options.starttime = output_gap[0]
+            options.endtime = output_gap[1]
+            self.run(options)
 
 
 def main(args):
@@ -347,7 +414,11 @@ def parse_args(args):
             help='UTC date YYYY-MM-DD HH:MM:SS')
 
     parser.add_argument('--observatory',
-            help='Observatory code ie BOU, CMO, etc')
+            help='Observatory code ie BOU, CMO, etc.' +
+                    ' CAUTION: Using multiple observatories is not' +
+                    ' recommended in most cases; especially with' +
+                    ' single observatory formats like IAGA and PCDCP.',
+            nargs='*')
     parser.add_argument('--inchannels',
             nargs='*',
             help='Channels H, E, Z, etc')
@@ -358,6 +429,16 @@ def parse_args(args):
     parser.add_argument('--type',
             default='variation',
             choices=['variation', 'quasi-definitive', 'definitive'])
+    parser.add_argument('--rename-input-channel',
+            action='append',
+            help='Rename an input channel after it is read',
+            metavar=('FROM', 'TO'),
+            nargs=2)
+    parser.add_argument('--rename-output-channel',
+            action='append',
+            help='Rename an output channel before it is written',
+            metavar=('FROM', 'TO'),
+            nargs=2)
     parser.add_argument('--locationcode',
             choices=['R0', 'R1', 'RM', 'Q0', 'D0', 'C0'])
     parser.add_argument('--outlocationcode',