Repository: wright-group/WrightTools

Issue:
`from_andor` method
generate `Data` object from ascii exports of CCD


Discussion:
@p770193 this is a good opportunity to "field test" our new docs page [Writing a New From Method](http://wright.tools/en/master/write_from_method.html)

please let us know what pain points there are and/or make edits to that doc page

Resolving patch:
diff --git a/WrightTools/data/__init__.py b/WrightTools/data/__init__.py
--- a/WrightTools/data/__init__.py
+++ b/WrightTools/data/__init__.py
@@ -7,6 +7,7 @@
 from ._join import *
 from ._variable import *
 
+from ._solis import *
 from ._brunold import *
 from ._colors import *
 from ._data import *
@@ -33,6 +34,7 @@
     "from_PyCMDS",
     "from_ocean_optics",
     "from_shimadzu",
+    "from_Solis",
     "from_spcm",
     "from_Tensor27",
 ]
diff --git a/WrightTools/data/_solis.py b/WrightTools/data/_solis.py
new file mode 100644
--- /dev/null
+++ b/WrightTools/data/_solis.py
@@ -0,0 +1,113 @@
+"""Andor."""
+
+
+# --- import --------------------------------------------------------------------------------------
+
+
+import os
+import time
+
+import numpy as np
+
+from ._data import Axis, Channel, Data
+from .. import exceptions as wt_exceptions
+from ..kit import _timestamp as timestamp
+
+
+# --- define --------------------------------------------------------------------------------------
+
+
+__all__ = ["from_Solis"]
+
+
+# --- from function -------------------------------------------------------------------------------
+
+
+def from_Solis(filepath, name=None, parent=None, verbose=True):
+    """Create a data object from Andor Solis software (ascii exports).
+
+    Parameters
+    ----------
+    filepath : string, list of strings, or array of strings
+        Path to .txt file.
+    name : string (optional)
+        Name to give to the created data object. If None, filename is used.
+        Default is None.
+    parent : WrightTools.Collection (optional)
+        Collection to place new data object within. Default is None.
+    verbose : boolean (optional)
+        Toggle talkback. Default is True.
+
+    Returns
+    -------
+    data
+        New data object.
+    """
+    # parse filepath
+    if not filepath.endswith("asc"):
+        wt_exceptions.WrongFileTypeWarning.warn(filepath, "asc")
+    # parse name
+    if not name:
+        name = os.path.basename(filepath).split(".")[0]
+    # create data
+    with open(filepath) as f:
+        axis0 = []
+        arr = []
+        attrs = {}
+        while True:
+            line = f.readline().strip()[:-1]
+            if len(line) == 0:
+                break
+            else:
+                line = line.split(",")
+                line = [float(x) for x in line]
+                axis0.append(line.pop(0))
+                arr.append(line)
+
+        i = 0
+        while i < 3:
+            line = f.readline().strip()
+            if len(line) == 0:
+                i += 1
+            else:
+                try:
+                    key, val = line.split(":", 1)
+                except ValueError:
+                    val = ""
+                attrs[key.strip()] = val.strip()
+
+    created = attrs["Date and Time"]  # is this UTC?
+    created = time.strptime(created, "%a %b %d %H:%M:%S %Y")
+    created = timestamp.TimeStamp(time.mktime(created)).RFC3339
+
+    kwargs = {"name": name, "kind": "Solis", "source": filepath, "created": created}
+    if parent is None:
+        data = Data(**kwargs)
+    else:
+        data = parent.create_data(**kwargs)
+    arr = np.array(arr)
+    arr /= float(attrs["Exposure Time (secs)"])
+    # signal has units of Hz because time normalized
+    arr = data.create_channel(name="signal", values=arr, signed=False, units="Hz")
+    axis0 = np.array(axis0)
+    if float(attrs["Grating Groove Density (l/mm)"]) == 0:
+        xname = "xpos"
+        xunits = None
+    else:
+        xname = "wm"
+        xunits = "nm"
+    data.create_variable(name=xname, values=axis0[:, None], units=xunits)
+    data.create_variable(
+        name="ypos", values=np.arange(arr.shape[1])[None, :], units=None
+    )
+    data.transform(data.variables[0].natural_name, "ypos")
+
+    for key, val in attrs.items():
+        data.attrs[key] = val
+
+    # finish
+    if verbose:
+        print("data created at {0}".format(data.fullpath))
+        print("  axes: {0}".format(data.axis_names))
+        print("  shape: {0}".format(data.shape))
+    return data
diff --git a/WrightTools/datasets/__init__.py b/WrightTools/datasets/__init__.py
--- a/WrightTools/datasets/__init__.py
+++ b/WrightTools/datasets/__init__.py
@@ -79,6 +79,9 @@ def _from_directory(self, dirname, prefix=""):
 Shimadzu = DatasetContainer()
 Shimadzu._from_files("Shimadzu")
 
+Solis = DatasetContainer()
+Solis._from_files("Solis")
+
 spcm = DatasetContainer()
 spcm._from_files("spcm")
 
@@ -94,13 +97,14 @@ def _from_directory(self, dirname, prefix=""):
 
 
 __all__ = [
+    "BrunoldrRaman",
+    "Cary",
     "COLORS",
     "JASCO",
     "KENT",
     "ocean_optics",
     "PyCMDS",
-    "BrunoldrRaman",
-    "Cary",
+    "Solis",
     "Tensor27",
     "wt5",
 ]


Test patch:
diff --git a/tests/data/from_Solis.py b/tests/data/from_Solis.py
new file mode 100644
--- /dev/null
+++ b/tests/data/from_Solis.py
@@ -0,0 +1,33 @@
+"""test from_JASCO"""
+
+
+# --- import --------------------------------------------------------------------------------------
+
+
+import WrightTools as wt
+from WrightTools import datasets
+
+
+# --- test ----------------------------------------------------------------------------------------
+
+
+def test_wm_ypos_fluorescence_with_filter():
+    p = datasets.Solis.wm_ypos_fluorescence_with_filter
+    data = wt.data.from_Solis(p)
+    assert data.shape == (2560, 2160)
+    assert data.axis_expressions == ("wm", "ypos")
+    assert data.units == ("nm", None)
+    data.close()
+
+
+def test_xpos_ypos_fluorescence():
+    p = datasets.Solis.xpos_ypos_fluorescence
+    data = wt.data.from_Solis(p)
+    assert data.shape == (2560, 2160)
+    assert data.axis_expressions == ("xpos", "ypos")
+    assert data.units == (None, None)
+    data.close()
+
+if __name__ == "__main__":
+    test_wm_ypos_fluorescence_with_filter()
+    test_xpos_ypos_fluorescence()