Massive rewrite (v 0.6) to base everything on Cython and revamped pypiezo.
[calibcant.git] / calibcant / T_analyze.py
index 219a60388570e04ea65e3ac39849be5edb2a30d8..be9852131e8501e14fc691f8257591b7667f3128 100644 (file)
 # License along with calibcant.  If not, see
 # <http://www.gnu.org/licenses/>.
 
+"""Temperature analysis.
+
+Separate the more general `T_analyze()` from the other `T_*()`
+functions in calibcant.
+
+The relevant physical quantities are:
+
+* `T` Temperature at which thermal vibration measurements were acquired
+
+>>> import os
+>>> import tempfile
+>>> import numpy
+>>> from .config import HDF5_TemperatureConfig
+>>> from pypiezo.config import pprint_HDF5
+
+>>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
+>>> os.close(fd)
+
+>>> temperature_config = HDF5_TemperatureConfig(
+...     filename=filename, group='/T/config/')
+
+>>> raw_T = numpy.array([22, 23.5, 24])
+>>> processed_T = T_analyze(raw_T, temperature_config)
+>>> T_plot(raw_T=raw_T, processed_T=processed_T)
+>>> T_save(filename=filename, group='/T/', raw_T=raw_T,
+...     temperature_config=temperature_config, processed_T=processed_T)
+
+>>> pprint_HDF5(filename)  # doctest: +REPORT_UDIFF
+/
+  /T
+    /T/config
+      <HDF5 dataset "default": shape (), type "|S3">
+        yes
+      <HDF5 dataset "units": shape (), type "|S7">
+        Celsius
+    <HDF5 dataset "processed": shape (3,), type "<f8">
+      [ 295.15  296.65  297.15]
+    <HDF5 dataset "raw": shape (3,), type "<f8">
+      [ 22.   23.5  24. ]
+
+>>> raw_T,temperature_config,processed_T = T_load(
+...     filename=filename, group='/T/')
+>>> print temperature_config.dump()
+units: Celsius
+default: yes
+>>> raw_T
+array([ 22. ,  23.5,  24. ])
+>>> type(raw_T)
+<type 'numpy.ndarray'>
+>>> processed_T
+array([ 295.15,  296.65,  297.15])
+
+>>> os.remove(filename)
 """
-Separate the more general T_analyze() from the other T_*()
-functions in calibcant.  Also provide a command line interface
-for analyzing data acquired through other workflows.
 
-The relevant physical quantities are :
- T Temperature at which thermal vibration measurements were aquired
-"""
-
-import numpy
+import h5py as _h5py
+from scipy.constants import C2K as _C2K
 
-import data_logger
-from splittable_kwargs import splittableKwargsFunction, \
-    make_splittable_kwargs_function
+try:
+    import matplotlib as _matplotlib
+    import matplotlib.pyplot as _matplotlib_pyplot
+    import time as _time  # for timestamping lines on plots
+except (ImportError, RuntimeError), e:
+    _matplotlib = None
+    _matplotlib_import_error = e
 
-from . import common
-from . import config
+from pypiezo.config import h5_create_group as _h5_create_group
 
+from . import LOG as _LOG
+from . import base_config as _base_config
+from .config import Celsius as _Celsius
+from .config import Kelvin as _Kelvin
+from .config import HDF5_TemperatureConfig as _HDF5_TemperatureConfig
 
-def C_to_K(celsius) :
-    "Convert Celsius -> Kelvin."
-    return celsius + 273.15
 
-def K_to_K(kelvin) :
-    "Convert Kelvin -> Kelvin."
-    return kelvin
+def T_analyze(T, temperature_config):
+    """Convert measured temperature to Kelvin.
 
-@splittableKwargsFunction()
-def T_analyze(T, convert_to_K=C_to_K) :
-    """
-    Not much to do here, just convert to Kelvin.
-    Uses C_to_K (defined above) by default.
-    """
-    try : # if T is an array, convert element by element
-        for i in range(len(T)) :
-            T[i] = convert_to_K(T[i])
-    except TypeError : # otherwise, make an array from a single T
-        T = [convert_to_K(T)]
-    return T
-
-@splittableKwargsFunction()
-def T_save(T, log_dir=None) :
-    """
-    Save either a single T (if you are crazy :p),
-    or an array of them (more reasonable)
-    """
-    T = numpy.array(T, dtype=numpy.float)
-    if log_dir != None :
-        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
-                                   log_name="T_float")
-        log.write_binary(T.tostring())
-    if config.LOG_DATA != None :
-        log = data_logger.data_log(config.LOG_DIR, noclobber_logsubdir=False,
-                                   log_name="T_float")
-        log.write_binary(T.tostring())
-        
-def T_load(datafile=None) :
-    """
-    Load the saved T array (possibly of length 1), and return it.  If
-    datafile == None, return an array of one config.DEFAULT_TEMP
-    instead.
-    """
-    if datafile == None :
-        return numpy.array([config.DEFAULT_TEMP], dtype=numpy.float)
-    else :
-        dl = data_logger.data_load()
-        return dl.read_binary(datafile)
-
-@splittableKwargsFunction()
-def T_plot(T, plotVerbose=False) :
-    """
-    Umm, just print the temperature?
+    `T` should be a numpy ndarray or scalar.  `temperature_config`
+    should be a `config._TemperatureConfig` instance.
     """
-    if plotVerbose or config.PYLAB_VERBOSE or config.TEXT_VERBOSE :
-        print "Temperature ", T
-
-@splittableKwargsFunction((T_analyze, 'T', 'convert_to_K'),
-                          (T_plot, 'T'))
-def T_load_analyze_tweaked(tweak_file, convert_to_K=C_to_K, textVerboseFile=None, **kwargs) :
-    "Load all the T array files from a tweak file and return a single array"
-    T_analyze_kwargs,T_plot_kwargs = \
-        T_load_analyze_tweaked._splitargs(T_load_analyze_tweaked, kwargs)
-    Ts = []
-    for line in file(tweak_file, 'r') :
-        parsed = line.split()
-        path = parsed[0].strip()
-        if path[0] == '#': # a comment
-            continue
-        if textVerboseFile != None :
-            print >> textVerboseFile, "Reading data from %s" % (path)
-        # read the data
-        data = T_load(path)
-        Ts.extend(data)
-    T_analyze(Ts, convert_to_K=convert_to_K)
-    return numpy.array(Ts, dtype=numpy.float)
-
-# commandline interface functions
-import scipy.io, sys
-
-def read_data(ifile):
-    "ifile can be a filename string or open (seekable) file object"
-    if ifile == None :  ifile = sys.stdin
-    unlabeled_data=scipy.io.read_array(ifile)
-    return unlabeled_data
-
-if __name__ == '__main__' :
-    # command line interface
-    from optparse import OptionParser
-    
-    usage_string = ('%prog <input-file>\n'
-                    '2008, W. Trevor King.\n'
-                    '\n'
-                    'There are two operation modes, one to analyze a single T (temperature) file,\n'
-                    'and one to analyze tweak files.\n'
-                    '\n'
-                    'Single file mode (the default) :\n'
-                    'Reads in single column ASCII file of temperatures and... prints them back out.\n'
-                    'No need to do this, but the option is available for consistency with the other\n'
-                    'calibcant modules.\n'
-                    '\n'
-                    'Tweak file mode:\n'
-                    'Runs the same analysis as in single file mode for each T file in\n'
-                    'a tweak file.  Each line in the tweak file specifies a single T file.\n'
-                    'Blank lines and those beginning with a pound sign (#) are ignored.\n'
-                    'A T file contains a sequence of 32 bit floats representing temperature in K.\n'
-                    )
-    parser = OptionParser(usage=usage_string, version='%prog 0.1')
-    parser.add_option('-C', '--celsius', dest='celsius',
-                      help='Use Celsius input temperatures instead of Kelvin (default %default)\n',
-                      action='store_true', default=False)
-    parser.add_option('-o', '--output-file', dest='ofilename',
-                      help='write output to FILE (default stdout)',
-                      type='string', metavar='FILE')
-    parser.add_option('-c', '--comma-out', dest='comma_out', action='store_true',
-                      help='Output comma-seperated values (default %default)',
-                      default=False)
-    parser.add_option('-t', '--tweak-mode', dest='tweakmode', action='store_true',
-                      help='Run in tweak-file mode',
-                      default=False)
-    parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
-                      help='Print lots of debugging information',
-                      default=False)
-
-    options,args = parser.parse_args()
-    parser.destroy()
-    assert len(args) >= 1, "Need an input file"
-        
-    ifilename = args[0]
-
-    if options.ofilename != None :
-        ofile = file(options.ofilename, 'w')
-    else :
-        ofile = sys.stdout
-    if options.verbose == True :
-        vfile = sys.stderr
-    else :
-        vfile = None
-    config.TEXT_VERBOSE = options.verbose
-    config.PYLAB_VERBOSE = False
-    config.GNUPLOT_VERBOSE = False
-    if options.celsius :
-        convert_to_K = C_to_K
-    else :
-        convert_to_K = K_to_K
-    
-    if options.tweakmode == False :
-        data = read_data(ifilename)
-        Ts = T_analyze(data, convert_to_K)
-    else : # tweak file mode
-        Ts = T_load_analyze_tweaked(ifilename, convert_to_K, textVerboseFile=vfile)
-
-    if options.comma_out :
-        sep = ','
-    else :
-        sep = '\n'
-    common.write_array(ofile, Ts, sep)
-    
-    if options.ofilename != None :
-        ofile.close()
+    if temperature_config['units'] == _Celsius:
+        return _C2K(T)
+    else:
+        return T
+
+def T_save(filename, group='/', raw_T=None, temperature_config=None,
+           processed_T=None):
+    with _h5py.File(filename, 'a') as f:
+        cwg = _h5_create_group(f, group)
+        if raw_T is not None:
+            try:
+                del cwg['raw']
+            except KeyError:
+                pass
+            cwg['raw'] = raw_T
+        if temperature_config:
+            config_cwg = _h5_create_group(cwg, 'config')
+            temperature_config.save(group=config_cwg)
+        if processed_T is not None:
+            try:
+                del cwg['processed']
+            except KeyError:
+                pass
+            cwg['processed'] = processed_T
+
+def T_load(filename, group='/'):
+    assert group.endswith('/')
+    raw_T = processed_T = None
+    with _h5py.File(filename, 'a') as f:
+        try:
+            raw_T = f[group+'raw'][...]
+        except KeyError:
+            pass
+        temperature_config = _HDF5_TemperatureConfig(
+            filename=filename, group=group+'config/')
+        try:
+            processed_T = f[group+'processed'][...]
+        except KeyError:
+            pass
+    temperature_config.load()
+    return (raw_T, temperature_config, processed_T)
+
+def T_plot(raw_T=None, processed_T=None):
+    if not _matplotlib:
+        raise _matplotlib_import_error
+    figure = _matplotlib_pyplot.figure()
+    timestamp = _time.strftime('%H%M%S')
+    if raw_T is None:
+        if processed_T is None:
+            return  # nothing to plot
+        axes1 = None
+        axes2 = figure.add_subplot(1, 1, 1)
+    elif processed_T is None:
+        axes1 = figure.add_subplot(1, 1, 1)
+        axes2 = None
+    else:
+        axes1 = figure.add_subplot(2, 1, 1)
+        axes2 = figure.add_subplot(2, 1, 2)
+    if axes1:
+        axes1.set_title('Raw Temperatures %s' % timestamp)
+        axes1.plot(raw_T, label='raw')
+    if axes2:
+        axes2.set_title('Processed Temperatures %s' % timestamp)
+        axes2.plot(processed_T, label='processed')
+    figure.show()