Add 'scaled column addition' command.
authorW. Trevor King <wking@drexel.edu>
Fri, 11 Nov 2011 08:01:00 +0000 (03:01 -0500)
committerW. Trevor King <wking@drexel.edu>
Fri, 11 Nov 2011 08:01:00 +0000 (03:01 -0500)
Example usage: removing linear drifts with

  scaled_column_addition --block retract
    --input_column_1 'surface deflection (m)'
    --input_column_2 'surface distance (m)'
    --output_column 'flattened surface deflection (m)
    --scale_1 1 --scale_2 -1
    --scale_2_name 'surface deflection offset|non-contact slope'

Also:
* Allow `None` as a column name in ColumnAccessCommand._get_column().
* Add 'force zero non-contact slope' option to SurfaceContactCommand.
* Convert slopes from surface contact fit from deflection/point to
  deflection/distance.
* Fix the returned deflection offset so it is a float and not a
  length-one tuple (by removing the trailing comma).

hooke/plugin/curve.py
hooke/plugin/vclamp.py

index 821d33370e6051657cfdb298403e344d86430e5e..f6e5892691df4227dcfdf6cea8740c780020158b 100644 (file)
@@ -210,6 +210,8 @@ class ColumnAccessCommand (BlockCommand):
         if column_name == None:
             column_name = self._column_arguments[0].name
         column_name = params[column_name]
+        if column_name is None:
+            return None
         block = self._block(hooke, params, block_name)
         columns = block.info['columns']
         try:
@@ -269,7 +271,7 @@ class CurvePlugin (Builtin):
             GetCommand(self), InfoCommand(self), BlockInfoCommand(self),
             DeltaCommand(self), ExportCommand(self), DifferenceCommand(self),
             DerivativeCommand(self), PowerSpectrumCommand(self),
-            ClearStackCommand(self)]
+            ScaledColumnAdditionCommand(self), ClearStackCommand(self)]
 
 
 # Define commands
@@ -686,6 +688,92 @@ Otherwise, the chunks are end-to-end, and not overlapping.
         return params
 
 
+class ScaledColumnAdditionCommand (ColumnAddingCommand):
+    """Add one affine transformed column to another: `o=A*i1+B*i2+C`.
+    """
+    def __init__(self, plugin):
+        super(ScaledColumnAdditionCommand, self).__init__(
+            name='scaled column addition',
+            columns=[
+                ('input column 1', 'input column (m)', """
+Name of the first column to use as the transform input.
+""".strip()),
+                ('input column 2', None, """
+Name of the second column to use as the transform input.
+""".strip()),
+                ],
+            new_columns=[
+                ('output column', 'output column (m)', """
+Name of the column to use as the transform output.
+""".strip()),
+                ],
+            arguments=[
+                Argument(name='scale 1', type='float', default=None,
+                         help="""
+A float value for the first scale constant.
+""".strip()),
+                Argument(name='scale 1 name', type='string', default=None,
+                         help="""
+The name of the first scale constant in the `.info` dictionary.
+""".strip()),
+                Argument(name='scale 2', type='float', default=None,
+                         help="""
+A float value for the second scale constant.
+""".strip()),
+                Argument(name='scale 2 name', type='string', default=None,
+                         help="""
+The name of the second scale constant in the `.info` dictionary.
+""".strip()),
+                Argument(name='constant', type='float', default=None,
+                         help="""
+A float value for the offset constant.
+""".strip()),
+                Argument(name='constant name', type='string', default=None,
+                         help="""
+The name of the offset constant in the `.info` dictionary.
+""".strip()),
+                ],
+            help=self.__doc__, plugin=plugin)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        self._add_to_command_stack(params)
+        i1 = self._get_column(hooke=hooke, params=params,
+                                    column_name='input column 1')
+        i2 = self._get_column(hooke=hooke, params=params,
+                                    column_name='input column 2')
+        if i1 is None:
+            i1 = 0
+        if i2 is None:
+            i2 = 0
+        # what if both i1 and i2 are None?
+        a = self._get_constant(params, i1.info, 'scale 1')
+        b = self._get_constant(params, i2.info, 'scale 2')
+        c = self._get_constant(params, i1.info, 'constant')
+        out = a*i1 + b*i2 + c
+        i1.tofile('1.dat', sep='\n', format='%r')
+        i2.tofile('2.dat', sep='\n', format='%r')
+        out.tofile('3.dat', sep='\n', format='%r')
+        self._set_column(hooke=hooke, params=params,
+                         column_name='output column', values=out)
+
+    def _get_constant(self, params, info, name):
+        a = params[name]
+        pname = params[name + ' name']
+        b = None
+        if pname is not None:
+            pname_entries = pname.split('|')
+            b = info
+            for entry in pname_entries:
+                b = b[entry]
+        if a is None and b is None:
+            return 0
+        if a is None:
+            a = 1
+        if b is None:
+            b = 1
+        return a*b
+
+
 class ClearStackCommand (CurveCommand):
     """Empty a curve's command stack.
     """
index d741173da573ebf2dcc6fad033d239e7cf766b55..c6c57344c685b1345851ae6fe077b61f37dc2c96 100644 (file)
@@ -102,7 +102,7 @@ Minimum `fit-contact-slope/guessed-contact-slope` ratio for a "good" fit.
         """
         p = params  # convenient alias
         rNC_ignore = self.info['ignore non-contact before index']
-        if self.info['force zero non-contact slope'] == True:
+        if self.info['force zero non-contact slope'] is True:
             p = list(p)
             p.append(0.)  # restore the non-contact slope parameter
         r2 = numpy.round(abs(p[2]))
@@ -296,6 +296,11 @@ point (for the `wtk` algorithm).
                          help="""
 As an alternative to 'ignore index', ignore after the last peak in the
 peak list stored in the `.info` dictionary.
+""".strip()),
+                Argument(name='force zero non-contact slope', type='bool',
+                         default=False, count=1,
+                         help="""
+Fix the fitted non-contact slope at zero.
 """.strip()),
                 Argument(name='distance info name', type='string',
                          default='surface distance offset',
@@ -476,7 +481,8 @@ Name (without units) for storing fit parameters in the `.info` dictionary.
         if reverse == True:    # approaching, contact region on the right
             d_data = d_data[::-1]
         s = SurfacePositionModel(d_data, info={
-                'force zero non-contact slope':True},
+                'force zero non-contact slope':
+                    params['force zero non-contact slope']},
                                  rescale=True)
         for argument in self._wtk_fit_check_arguments:
             s.info[argument.name] = params[argument.name]
@@ -494,6 +500,10 @@ Name (without units) for storing fit parameters in the `.info` dictionary.
             s.info['ignore non-contact before index'] = ignore_index
         offset,contact_slope,surface_index,non_contact_slope = s.fit(
             outqueue=outqueue)
+        deflection_offset = offset + contact_slope*surface_index
+        delta_pos_per_point = z_data[1] - z_data[0]
+        contact_slope /= delta_pos_per_point  # ddef/point -> ddev/dpos
+        non_contact_slope /= delta_pos_per_point
         info = {
             'offset': offset,
             'contact slope': contact_slope,
@@ -501,7 +511,6 @@ Name (without units) for storing fit parameters in the `.info` dictionary.
             'non-contact slope': non_contact_slope,
             'reversed': reverse,
             }
-        deflection_offset = offset + contact_slope*surface_index,
         if reverse == True:
             surface_index = len(d_data)-1-surface_index
         return (numpy.round(surface_index), deflection_offset, info)
@@ -535,6 +544,7 @@ Name of the spring constant in the `.info` dictionary.
     def _run(self, hooke, inqueue, outqueue, params):
         self._add_to_command_stack(params)
         params = self._setup_params(hooke=hooke, params=params)
+        # TODO: call .curve.ScaledColumnAdditionCommand
         def_data = self._get_column(hooke=hooke, params=params,
                                     column_name='deflection column')
         out = def_data * def_data.info[params['spring constant info name']]