Repository: refnx/refnx

Issue:
PlatypusNexus.write_spectrum_xml doesn't work.

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-1d0132abf492> in <module>()
----> 1 f.write_spectrum_xml('PLP0024619.spec')

C:\Miniconda3\envs\dev3\lib\site-packages\refnx\reduce\platypusnexus.py in write_spectrum_xml(self, f, scanpoint)
    953         d['runnumber'] = 'PLP{:07d}'.format(self.cat.datafile_number)
    954 
--> 955         d['r'] = string.translate(repr(r[scanpoint].tolist()), None, ',[]')
    956         d['dr'] = string.translate(repr(dr[scanpoint].tolist()), None, ',[]')
    957         d['l'] = string.translate(repr(l[scanpoint].tolist()), None, ',[]')

AttributeError: 'module' object has no attribute 'translate

Discussion:

Patch:
diff --git a/refnx/dataset/data1d.py b/refnx/dataset/data1d.py
index 11737c90..cc231aca 100644
--- a/refnx/dataset/data1d.py
+++ b/refnx/dataset/data1d.py
@@ -1,5 +1,5 @@
 """"
-    A basic representation of a 1D dataset
+A basic representation of a 1D dataset
 """
 from __future__ import division
 
@@ -12,25 +12,24 @@ import refnx.util.nsplice as nsplice
 class Data1D(object):
     """
     A basic representation of a 1D dataset.
-    """
 
+    Parameters
+    ----------
+    data_tuple : tuple of np.ndarray, optional
+        Tuple containing the data. The tuple should have between 2 and 4
+        members.
+        data_tuple[0] - x
+        data_tuple[1] - y
+        data_tuple[2] - standard deviation of y, y_sd
+        data_tuple[3] - standard deviation of x, x_sd
+
+        `data_tuple` must be at least two long, `x` and `y`.
+        If the tuple is at least 3 long then the third member is `y_sd`.
+        If the tuple is 4 long then the fourth member is `x_sd`.
+        All arrays must have the same shape.
+    """
     def __init__(self, data_tuple=None, curvefitter=None):
-        """
-        Parameters
-        ----------
-        dataTuple : tuple of np.ndarray, optional
-            Tuple containing the data. The tuple should have between 2 and 4
-            members.
-            data_tuple[0] - x
-            data_tuple[1] - y
-            data_tuple[2] - standard deviation of y, y_sd
-            data_tuple[3] - standard deviation of x, x_sd
-
-            `data_tuple` must be at least two long, `x` and `y`.
-            If the tuple is at least 3 long then the third member is `y_sd`.
-            If the tuple is 4 long then the fourth member is `x_sd`.
-            All arrays must have the same shape.
-        """
+
         self.filename = None
         self.fit = None
         self.params = None
diff --git a/refnx/dataset/reflectdataset.py b/refnx/dataset/reflectdataset.py
index e6b4fad9..a18958d2 100644
--- a/refnx/dataset/reflectdataset.py
+++ b/refnx/dataset/reflectdataset.py
@@ -52,8 +52,9 @@ class ReflectDataset(Data1D):
 
         Parameters
         ----------
-        f : file-like
-            The file to save the data to.
+        f : str or file-like
+            The file to write the spectrum to, or a str that specifies the file
+            name
         """
         s = string.Template(self._template_ref_xml)
         self.time = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
@@ -66,7 +67,17 @@ class ReflectDataset(Data1D):
         self._xdataSD = repr(self.x_sd.tolist()).strip(',[]')
 
         thefile = s.safe_substitute(self.__dict__)
-        f.write(thefile)
+
+        auto_fh = None
+        g = f
+        if not hasattr(f, 'write'):
+            auto_fh = open(f, 'wb')
+            g = auto_fh
+
+        g.write(thefile.encode('utf-8'))
+
+        if auto_fh is not None:
+            auto_fh.close()
 
     def load(self, f):
         """
@@ -75,13 +86,14 @@ class ReflectDataset(Data1D):
         Parameters
         ----------
         f : str or file-like
-            File to load reflectivity data from.
+            The file to load the spectrum from, or a str that specifies the file
+            name
         """
-        own_fh = None
+        auto_fh = None
         g = f
         if not hasattr(f, 'read'):
-            own_fh = open(f, 'rb')
-            g = own_fh
+            auto_fh = open(f, 'rb')
+            g = auto_fh
         try:
             tree = ET.ElementTree()
             tree.parse(g)
@@ -103,5 +115,5 @@ class ReflectDataset(Data1D):
             g.seek(0)
             super(ReflectDataset, self).load(g)
         finally:
-            if own_fh is not None:
-                own_fh.close()
\ No newline at end of file
+            if auto_fh is not None:
+                auto_fh.close()
diff --git a/refnx/reduce/event.py b/refnx/reduce/event.py
index 038341dc..1718255d 100644
--- a/refnx/reduce/event.py
+++ b/refnx/reduce/event.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 """
-unpack streaming file
-@author: andrew
+Unpack event files
 """
 import numpy as np
 
@@ -70,7 +69,7 @@ def process_event_stream(events, frame_bins, t_bins, y_bins, x_bins):
     return detector, localframe_bins
 
 
-def events(f, endoflastevent=127, max_frames=np.inf):
+def events(f, end_last_event=127, max_frames=np.inf):
     """
     Unpacks event data from packedbinary format for the ANSTO Platypus
     instrument
@@ -78,27 +77,29 @@ def events(f, endoflastevent=127, max_frames=np.inf):
     Parameters
     ----------
     
-    f : file
-        The file to read the data from.
-    endoflastevent : uint
-        The file position to start the read from. The data starts from byte
-        127.
+    f : file-like or str
+        The file to read the data from. If `f` is not file-like then f is
+        assumed to be a path pointing to the event file.
+    end_last_event : uint
+        The reading of event data starts from `end_last_event + 1`. The default
+        of 127 corresponds to a file header that is 128 bytes long.
     max_frames : int
-        Stop reading the event file when you get to this many frames.
+        Stop reading the event file when have read this many frames.
         
     Returns
     -------
-    (f_events, t_events, y_events, x_events), endoflastevent:
+    (f_events, t_events, y_events, x_events), end_last_event:
         x_events, y_events, t_events and f_events are numpy arrays containing
-        the events. endoflastevent is a byte offset to the end of the last
+        the events. end_last_event is a byte offset to the end of the last
         successful event read from the file. Use this value to extract more
         events from the same file at a future date.
     """
-    if not f:
-        return None
+    fi = f
+    auto_f = None
+    if not hasattr(fi, 'read'):
+        auto_f = open(f, 'rb')
+        fi = auto_f
 
-    state = 0
-    event_ended = 0
     frame_number = -1
     dt = 0
     t = 0
@@ -110,7 +111,7 @@ def events(f, endoflastevent=127, max_frames=np.inf):
     t_events = np.array((), dtype='uint32')
     f_events = np.array((), dtype='int32')
 
-    BUFSIZE = 32768
+    bufsize = 32768
 
     while True and frame_number < max_frames:
         x_neutrons = []
@@ -118,10 +119,10 @@ def events(f, endoflastevent=127, max_frames=np.inf):
         t_neutrons = []
         f_neutrons = []
 
-        f.seek(endoflastevent + 1)
-        buf = f.read(BUFSIZE)
+        fi.seek(end_last_event + 1)
+        buf = fi.read(bufsize)
 
-        filepos = endoflastevent + 1
+        filepos = end_last_event + 1
 
         if not len(buf):
             break
@@ -142,7 +143,7 @@ def events(f, endoflastevent=127, max_frames=np.inf):
                 state += 1
             else:
                 if state == 2:
-                    y = y | ((c & 0xF) * 64)
+                    y |= (c & 0xF) * 64
 
                     if y & 0x200:
                         y = -(0x100000000 - (y | 0xFFFFFC00))
@@ -153,14 +154,14 @@ def events(f, endoflastevent=127, max_frames=np.inf):
                 if state == 2:
                     dt = c >> 4
                 else:
-                    dt |= (c) << (2 + 6 * (state - 3))
+                    dt |= c << 2 + 6 * (state - 3)
 
                 if not event_ended:
                     state += 1
                 else:
-                    #print "got to state", state, event_ended, x, y, frame_number, t, dt
+                    # print "got to state", state, event_ended, x, y, frame_number, t, dt
                     state = 0
-                    endoflastevent = filepos + i
+                    end_last_event = filepos + i
                     if x == 0 and y == 0 and dt == 0xFFFFFFFF:
                         t = 0
                         frame_number += 1
@@ -181,5 +182,9 @@ def events(f, endoflastevent=127, max_frames=np.inf):
             t_events = np.append(t_events, t_neutrons)
             f_events = np.append(f_events, f_neutrons)
     
-    t_events = t_events // 1000
-    return (f_events, t_events, y_events, x_events), endoflastevent
\ No newline at end of file
+    t_events //= 1000
+
+    if auto_f:
+        auto_f.close()
+
+    return (f_events, t_events, y_events, x_events), end_last_event
diff --git a/refnx/reduce/platypusnexus.py b/refnx/reduce/platypusnexus.py
index 51026ba3..e85c6cce 100644
--- a/refnx/reduce/platypusnexus.py
+++ b/refnx/reduce/platypusnexus.py
@@ -886,20 +886,21 @@ class PlatypusNexus(object):
 
         Parameters
         ----------
-        f : file-like object
-            The file to write the spectrum to
+        f : file-like or str
+            The file to write the spectrum to, or a str that specifies the file
+            name
         scanpoint : int
-            Which scanpoint to write.
+            Which scanpoint to write
         """
         if self.processed_spectrum is None:
             return False
 
         m_lambda = self.processed_spectrum['m_lambda'][scanpoint]
         m_spec = self.processed_spectrum['m_spec'][scanpoint]
-        m_spec_sd = self.processed_spectrum['m_spec'][scanpoint]
-        m_lambda_sd = self.processed_spectrum['m_lambda_sd'][scanpoint]
+        m_spec_sd = self.processed_spectrum['m_spec_sd'][scanpoint]
+        m_lambda_fwhm = self.processed_spectrum['m_lambda_fwhm'][scanpoint]
 
-        stacked_data = np.c_[m_lambda, m_spec, m_spec_sd, m_lambda_sd]
+        stacked_data = np.c_[m_lambda, m_spec, m_spec_sd, m_lambda_fwhm]
         np.savetxt(f, stacked_data, delimiter='\t')
 
         return True
@@ -911,12 +912,12 @@ class PlatypusNexus(object):
 
         Parameters
         ----------
-        f : file-like object
-            The file to write the spectrum to
+        f : file-like or str
+            The file to write the spectrum to, or a str that specifies the file
+            name
         scanpoint : int
-            Which scanpoint to write.
+            Which scanpoint to write
         """
-
         spectrum_template = """<?xml version="1.0"?>
         <REFroot xmlns="">
         <REFentry time="$time">
@@ -930,7 +931,6 @@ class PlatypusNexus(object):
         </REFdata>
         </REFentry>
         </REFroot>"""
-
         if self.processed_spectrum is None:
             return
 
@@ -941,26 +941,37 @@ class PlatypusNexus(object):
 
         m_lambda = self.processed_spectrum['m_lambda']
         m_spec = self.processed_spectrum['m_spec']
-        m_spec_sd = self.processed_spectrum['m_spec']
-        m_lambda_sd = self.processed_spectrum['m_lambda_sd']
+        m_spec_sd = self.processed_spectrum['m_spec_sd']
+        m_lambda_fwhm = self.processed_spectrum['m_lambda_fwhm']
 
         # sort the data
         sorted = np.argsort(self.m_lambda[0])
 
         r = m_spec[:, sorted]
         l = m_lambda[:, sorted]
-        dl = m_lambda_sd[:, sorted]
+        dl = m_lambda_fwhm [:, sorted]
         dr = m_spec_sd[:, sorted]
         d['n_spectra'] = self.processed_spectrum['n_spectra']
         d['runnumber'] = 'PLP{:07d}'.format(self.cat.datafile_number)
 
-        d['r'] = string.translate(repr(r[scanpoint].tolist()), None, ',[]')
-        d['dr'] = string.translate(repr(dr[scanpoint].tolist()), None, ',[]')
-        d['l'] = string.translate(repr(l[scanpoint].tolist()), None, ',[]')
-        d['dl'] = string.translate(repr(dl[scanpoint].tolist()), None, ',[]')
+        d['r'] = repr(r[scanpoint].tolist()).strip(',[]')
+        d['dr'] = repr(dr[scanpoint].tolist()).strip(',[]')
+        d['l'] = repr(l[scanpoint].tolist()).strip(',[]')
+        d['dl'] = repr(dl[scanpoint].tolist()).strip(',[]')
         thefile = s.safe_substitute(d)
-        f.write(thefile)
-        f.truncate()
+
+        g = f
+        auto_fh = None
+
+        if not hasattr(f, 'write'):
+            auto_fh = open(f, 'wb')
+            g = auto_fh
+
+        g.write(thefile.encode('utf-8'))
+        g.truncate()
+
+        if auto_fh is not None:
+            auto_fh.close()
 
         return True
 
@@ -968,8 +979,8 @@ class PlatypusNexus(object):
     def spectrum(self):
         return (self.processed_spectrum['m_lambda'],
                 self.processed_spectrum['m_spec'],
-                self.processed_spectrum['m_spec'],
-                self.processed_spectrum['m_lambda_sd'])
+                self.processed_spectrum['m_spec_sd'],
+                self.processed_spectrum['m_lambda_fwhm'])
 
 
 def create_detector_norm(h5norm, x_min, x_max):
diff --git a/refnx/reduce/reduce.py b/refnx/reduce/reduce.py
index 3f9b7f21..b487a5a5 100644
--- a/refnx/reduce/reduce.py
+++ b/refnx/reduce/reduce.py
@@ -402,7 +402,7 @@ class ReducePlatypus(object):
                 with open(fname, 'wb') as f:
                     dataset.save(f)
                 fname = 'PLP{0:07d}_{1}.xml'.format(self.datafile_number, i)
-                with open(fname, 'w') as f:
+                with open(fname, 'wb') as f:
                     dataset.save_xml(f)
 
         reduction['fname'] = fnames
@@ -481,7 +481,7 @@ def reduce_stitch(reflect_list, direct_list, norm_file_num=None,
         with open(fname, 'wb') as f:
             combined_dataset.save(f)
         fname = 'c_PLP{0:07d}.xml'.format(reflect_list[0])
-        with open(fname, 'w') as f:
+        with open(fname, 'wb') as f:
             combined_dataset.save_xml(f)
 
     return combined_dataset, fname
diff --git a/refnx/util/general.py b/refnx/util/general.py
index 122e55fc..66fd567a 100644
--- a/refnx/util/general.py
+++ b/refnx/util/general.py
@@ -1,4 +1,7 @@
 #!/usr/bin/python
+"""
+Functions for various calculations related to reflectometry
+"""
 from __future__ import division
 
 import numpy as np


Test patch:
diff --git a/refnx/dataset/test/test_reflectdataset.py b/refnx/dataset/test/test_reflectdataset.py
index 2bfbab6e..36cfa16f 100644
--- a/refnx/dataset/test/test_reflectdataset.py
+++ b/refnx/dataset/test/test_reflectdataset.py
@@ -10,8 +10,15 @@ path = os.path.dirname(os.path.abspath(__file__))
 class TestReflectDataset(unittest.TestCase):
 
     def setUp(self):
-        pass
-             
+        data = ReflectDataset()
+
+        x1 = np.linspace(0, 10, 5)
+        y1 = 2 * x1
+        e1 = np.ones_like(x1)
+        dx1 = np.ones_like(x1)
+        data.add_data((x1, y1, e1, dx1))
+        self.data = data
+
     def test_load(self):
         # test reflectivity calculation with values generated from Motofit
         dataset = ReflectDataset()
@@ -77,6 +84,10 @@ class TestReflectDataset(unittest.TestCase):
 
         assert_(data.npoints==13)
 
+    def test_save_xml(self):
+        self.data.save_xml('test.xml')
+        with open('test.xml', 'wb') as f:
+            self.data.save_xml(f)
         
 if __name__ == '__main__':
     unittest.main()
\ No newline at end of file
diff --git a/refnx/reduce/test/test_event.py b/refnx/reduce/test/test_event.py
index 072b0815..25257484 100644
--- a/refnx/reduce/test/test_event.py
+++ b/refnx/reduce/test/test_event.py
@@ -32,6 +32,13 @@ class TestEvent(unittest.TestCase):
         max_f = np.max(f)
         assert_equal(9, max_f)
 
+    def test_open_with_path(self):
+        # give the event reader a file path
+        event_list, fpos = event.events(self.event_file_path, max_frames=10)
+        f, t, y, x = event_list
+        max_f = np.max(f)
+        assert_equal(9, max_f)
+
     def test_values(self):
         # We know the values of all the events in the file from another program
         # test that a set of random events are correct.
diff --git a/refnx/reduce/test/test_platypusnexus.py b/refnx/reduce/test/test_platypusnexus.py
index 706121ae..62bc03ab 100644
--- a/refnx/reduce/test/test_platypusnexus.py
+++ b/refnx/reduce/test/test_platypusnexus.py
@@ -144,6 +144,22 @@ class TestPlatypusNexus(unittest.TestCase):
         assert_array_less(res, np.ones_like(res) * 0.08)
         assert_array_less(np.ones_like(res) * 0.07, res)
 
+    def test_save_spectrum(self):
+        # test saving spectrum
+        self.f113.process()
+
+        # can save the spectra by supplying a filename
+        self.f113.write_spectrum_xml('test.xml')
+        self.f113.write_spectrum_dat('test.dat')
+
+        # can save by supplying file handle:
+        with open('test.xml', 'wb') as f:
+            self.f113.write_spectrum_xml(f)
+
+        # can save by supplying file handle:
+        with open('test.dat', 'wb') as f:
+            self.f113.write_spectrum_xml(f)
+
 
 if __name__ == '__main__':
     unittest.main()