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',
diff --git a/geomagio/TimeseriesUtility.py b/geomagio/TimeseriesUtility.py
index 20aa4974bbb5d94db587a74030278a2ef063b20e..e547e63c9578b2ff053ad45ddb5e12841dddd3fa 100644
--- a/geomagio/TimeseriesUtility.py
+++ b/geomagio/TimeseriesUtility.py
@@ -110,3 +110,22 @@ def get_merged_gaps(gaps):
     if merged_gap is not None:
         merged_gaps.append(merged_gap)
     return merged_gaps
+
+
+def get_channels(stream):
+    """Get a list of channels in a stream.
+
+    Parameters
+    ----------
+    stream : obspy.core.Stream
+
+    Returns
+    -------
+    channels : array_like
+    """
+    channels = {}
+    for trace in stream:
+        channel = trace.stats.channel
+        if channel:
+            channels[channel] = True
+    return [ch for ch in channels]
diff --git a/geomagio/algorithm/Algorithm.py b/geomagio/algorithm/Algorithm.py
index e3fdc3f471bd90e7160c21a70884d9504f33cea6..1aa1b7b6c2ea523fca1879e635088733402599f5 100644
--- a/geomagio/algorithm/Algorithm.py
+++ b/geomagio/algorithm/Algorithm.py
@@ -56,18 +56,24 @@ class Algorithm(object):
         """
         return self._outchannels
 
-    def get_input_interval(self, start, end):
+    def get_input_interval(self, start, end, observatory=None, channels=None):
         """Get Input Interval
 
         start : UTCDateTime
-            start time of requested output
+            start time of requested output.
         end : UTCDateTime
-            end time of requested output
+            end time of requested output.
+        observatory : string
+            observatory code.
+        channels : string
+            input channels.
 
         Returns
         -------
-        tuple : (input_start, input_end)
-            start and end of required input to generate requested output.
+        input_start : UTCDateTime
+            start of input required to generate requested output
+        input_end : UTCDateTime
+            end of input required to generate requested output.
         """
         return (start, end)
 
diff --git a/geomagio/edge/EdgeFactory.py b/geomagio/edge/EdgeFactory.py
index 7b92a33ea51715e3a2702c0acf8d1d00995c82e8..307275a9cd826e1a22b8d876030a89419ee31341 100644
--- a/geomagio/edge/EdgeFactory.py
+++ b/geomagio/edge/EdgeFactory.py
@@ -17,7 +17,7 @@ import obspy.core
 from datetime import datetime
 from obspy import earthworm
 from obspy.core import UTCDateTime
-from .. import ChannelConverter
+from .. import ChannelConverter, TimeseriesUtility
 from ..TimeseriesFactory import TimeseriesFactory
 from ..TimeseriesFactoryException import TimeseriesFactoryException
 from ..ObservatoryMetadata import ObservatoryMetadata
@@ -183,12 +183,11 @@ class EdgeFactory(TimeseriesFactory):
 
         if (starttime is None or endtime is None):
             starttime, endtime = self._get_stream_start_end_times(timeseries)
-
         for channel in channels:
             if timeseries.select(channel=channel).count() == 0:
                 raise TimeseriesFactoryException(
-                    'Missing channel "%s" for output' % channel)
-
+                    'Missing channel "%s" for output, available channels %s' %
+                    (channel, str(TimeseriesUtility.get_channels(timeseries))))
         for channel in channels:
             self._put_channel(timeseries, observatory, channel, type,
                     interval, starttime, endtime)
diff --git a/geomagio/iaga2002/IAGA2002Writer.py b/geomagio/iaga2002/IAGA2002Writer.py
index 56c510f3d0dbc273eea1dc9e06cb8723fb5da909..cd070075a3c5e046044ea54586301c142d857793 100644
--- a/geomagio/iaga2002/IAGA2002Writer.py
+++ b/geomagio/iaga2002/IAGA2002Writer.py
@@ -3,7 +3,7 @@ from cStringIO import StringIO
 from datetime import datetime
 import numpy
 import textwrap
-from .. import ChannelConverter
+from .. import ChannelConverter, TimeseriesUtility
 from ..TimeseriesFactoryException import TimeseriesFactoryException
 from ..Util import create_empty_trace
 import IAGA2002Parser
@@ -30,6 +30,11 @@ class IAGA2002Writer(object):
         channels: array_like
             channels to be written from timeseries object
         """
+        for channel in channels:
+            if timeseries.select(channel=channel).count() == 0:
+                raise TimeseriesFactoryException(
+                    'Missing channel "%s" for output, available channels %s' %
+                    (channel, str(TimeseriesUtility.get_channels(timeseries))))
         stats = timeseries[0].stats
         if len(channels) != 4:
             self._pad_to_four_channels(timeseries, channels)
diff --git a/geomagio/pcdcp/PCDCPWriter.py b/geomagio/pcdcp/PCDCPWriter.py
index e26c29075ae4b3320619ac617ce5190babe4f767..3042df76e476185449be5b5cf288cc8aa20fa280 100644
--- a/geomagio/pcdcp/PCDCPWriter.py
+++ b/geomagio/pcdcp/PCDCPWriter.py
@@ -3,7 +3,8 @@ import numpy
 import PCDCPParser
 from cStringIO import StringIO
 from datetime import datetime
-from geomagio import ChannelConverter
+from .. import ChannelConverter, TimeseriesUtility
+from ..TimeseriesFactoryException import TimeseriesFactoryException
 from obspy.core import Stream
 
 
@@ -26,6 +27,11 @@ class PCDCPWriter(object):
             channels : array_like
                 Channels to be written from timeseries object.
         """
+        for channel in channels:
+            if timeseries.select(channel=channel).count() == 0:
+                raise TimeseriesFactoryException(
+                    'Missing channel "%s" for output, available channels %s' %
+                    (channel, str(TimeseriesUtility.get_channels(timeseries))))
         stats = timeseries[0].stats
         out.write(self._format_header(stats))
         out.write(self._format_data(timeseries, channels))