We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
from_PyCMDS:  retrieve units for filling unfinished data
When importing data that isn't filled to the shape reported in header, values of axes are assigned based on header content, but the units are not read.  Example at https://drive.google.com/open?id=1ta8AEOR-Ck3IENRAHkYbyIjumHP0oiii

```
d = wt.data.from_PyCMDS(...)
print(d.w1.points)
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `WrightTools/data/_pycmds.py`
Content:
```
1 """PyCMDS."""
2 
3 
4 # --- import --------------------------------------------------------------------------------------
5 
6 
7 import numpy as np
8 
9 import tidy_headers
10 
11 from ._data import Data
12 from .. import kit as wt_kit
13 
14 
15 # --- define --------------------------------------------------------------------------------------
16 
17 
18 __all__ = ["from_PyCMDS"]
19 
20 
21 # --- from function -------------------------------------------------------------------------------
22 
23 
24 def from_PyCMDS(filepath, name=None, parent=None, verbose=True) -> Data:
25     """Create a data object from a single PyCMDS output file.
26 
27     Parameters
28     ----------
29     filepath : str
30         The file to load. Can accept .data, .fit, or .shots files.
31     name : str or None (optional)
32         The name to be applied to the new data object. If None, name is read
33         from file.
34     parent : WrightTools.Collection (optional)
35         Collection to place new data object within. Default is None.
36     verbose : bool (optional)
37         Toggle talkback. Default is True.
38 
39     Returns
40     -------
41     data
42         A Data instance.
43     """
44     # header
45     headers = tidy_headers.read(filepath)
46     # name
47     if name is None:  # name not given in method arguments
48         data_name = headers["data name"]
49     else:
50         data_name = name
51     if data_name == "":  # name not given in PyCMDS
52         data_name = headers["data origin"]
53     # create data object
54     kwargs = {
55         "name": data_name,
56         "kind": "PyCMDS",
57         "source": filepath,
58         "created": headers["file created"],
59     }
60     if parent is not None:
61         data = parent.create_data(**kwargs)
62     else:
63         data = Data(**kwargs)
64     # array
65     arr = np.genfromtxt(filepath).T
66     # get axes and scanned variables
67     axes = []
68     for name, identity, units in zip(
69         headers["axis names"], headers["axis identities"], headers["axis units"]
70     ):
71         # points and centers
72         points = np.array(headers[name + " points"])
73         if name + " centers" in headers.keys():
74             centers = headers[name + " centers"]
75         else:
76             centers = None
77         # create
78         axis = {
79             "points": points,
80             "units": units,
81             "name": name,
82             "identity": identity,
83             "centers": centers,
84         }
85         axes.append(axis)
86     shape = tuple([a["points"].size for a in axes])
87     for i, ax in enumerate(axes):
88         sh = [1] * len(shape)
89         sh[i] = len(ax["points"])
90         data.create_variable(
91             name=ax["name"] + "_points", values=np.array(ax["points"]).reshape(sh)
92         )
93         if ax["centers"] is not None:
94             sh = list(shape)
95             sh[i] = 1
96             data.create_variable(
97                 name=ax["name"] + "_centers", values=np.array(ax["centers"]).reshape(sh)
98             )
99     # get assorted remaining things
100     # variables and channels
101     for index, kind, name in zip(range(len(arr)), headers["kind"], headers["name"]):
102         values = np.full(np.prod(shape), np.nan)
103         values[: len(arr[index])] = arr[index]
104         values.shape = shape
105         if name == "time":
106             data.create_variable(name="labtime", values=values)
107         if kind == "hardware":
108             # sadly, recorded tolerances are not reliable
109             # so a bit of hard-coded hacking is needed
110             # if this ends up being too fragile, we might have to use the points arrays
111             # ---Blaise 2018-01-09
112             units = headers["units"][index]
113             label = headers["label"][index]
114             if (
115                 "w" in name
116                 and name.startswith(tuple(data.variable_names))
117                 and name not in headers["axis names"]
118             ):
119                 inherited_shape = data[name.split("_")[0]].shape
120                 for i, s in enumerate(inherited_shape):
121                     if s == 1:
122                         values = np.mean(values, axis=i)
123                         values = np.expand_dims(values, i)
124             else:
125                 tolerance = headers["tolerance"][index]
126                 for i in range(len(shape)):
127                     if tolerance is None:
128                         break
129                     if "d" in name:
130                         tolerance = 3.
131                     if "zero" in name:
132                         tolerance = 1e-10
133                     try:
134                         assert i == headers["axis names"].index(name)
135                         tolerance = 0
136                     except (ValueError, AssertionError):
137                         if (
138                             name in headers["axis names"]
139                             and "%s_centers" % name not in data.variable_names
140                         ):
141                             tolerance = np.inf
142                     mean = np.nanmean(values, axis=i)
143                     mean = np.expand_dims(mean, i)
144                     values, meanexp = wt_kit.share_nans(values, mean)
145                     if np.allclose(meanexp, values, atol=tolerance, equal_nan=True):
146                         values = mean
147             if name in headers["axis names"]:
148                 points = np.array(headers[name + " points"])
149                 pointsshape = [1] * values.ndim
150                 for i, ax in enumerate(axes):
151                     if ax["name"] == name:
152                         pointsshape[i] = len(points)
153                         break
154                 points.shape = pointsshape
155                 for i in range(points.ndim):
156                     if points.shape[i] == 1:
157                         points = np.repeat(points, values.shape[i], axis=i)
158                 if points.size <= values.size:
159                     values[np.isnan(values)] = points[np.isnan(values)]
160             data.create_variable(name, values=values, units=units, label=label)
161         if kind == "channel":
162             data.create_channel(name=name, values=values, shape=values.shape)
163     # axes
164     for a in axes:
165         expression = a["identity"]
166         if expression.startswith("D"):
167             expression = expression[1:]
168         expression.replace("=D", "=")
169         a["expression"] = expression
170     data.transform(*[a["expression"] for a in axes])
171     # return
172     if verbose:
173         print("data created at {0}".format(data.fullpath))
174         print("  axes: {0}".format(data.axis_names))
175         print("  shape: {0}".format(data.shape))
176     return data
177
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/WrightTools/data/_pycmds.py b/WrightTools/data/_pycmds.py
--- a/WrightTools/data/_pycmds.py
+++ b/WrightTools/data/_pycmds.py
@@ -10,6 +10,7 @@
 
 from ._data import Data
 from .. import kit as wt_kit
+from .. import units as wt_units
 
 
 # --- define --------------------------------------------------------------------------------------
@@ -152,6 +153,7 @@
                         pointsshape[i] = len(points)
                         break
                 points.shape = pointsshape
+                points = wt_units.converter(points, headers["axis units"][i], units)
                 for i in range(points.ndim):
                     if points.shape[i] == 1:
                         points = np.repeat(points, values.shape[i], axis=i)
@@ -168,6 +170,8 @@
         expression.replace("=D", "=")
         a["expression"] = expression
     data.transform(*[a["expression"] for a in axes])
+    for a, u in zip(data.axes, headers["axis units"]):
+        a.convert(u)
     # return
     if verbose:
         print("data created at {0}".format(data.fullpath))

{"golden_diff": "diff --git a/WrightTools/data/_pycmds.py b/WrightTools/data/_pycmds.py\n--- a/WrightTools/data/_pycmds.py\n+++ b/WrightTools/data/_pycmds.py\n@@ -10,6 +10,7 @@\n \n from ._data import Data\n from .. import kit as wt_kit\n+from .. import units as wt_units\n \n \n # --- define --------------------------------------------------------------------------------------\n@@ -152,6 +153,7 @@\n                         pointsshape[i] = len(points)\n                         break\n                 points.shape = pointsshape\n+                points = wt_units.converter(points, headers[\"axis units\"][i], units)\n                 for i in range(points.ndim):\n                     if points.shape[i] == 1:\n                         points = np.repeat(points, values.shape[i], axis=i)\n@@ -168,6 +170,8 @@\n         expression.replace(\"=D\", \"=\")\n         a[\"expression\"] = expression\n     data.transform(*[a[\"expression\"] for a in axes])\n+    for a, u in zip(data.axes, headers[\"axis units\"]):\n+        a.convert(u)\n     # return\n     if verbose:\n         print(\"data created at {0}\".format(data.fullpath))\n", "issue": "from_PyCMDS:  retrieve units for filling unfinished data\nWhen importing data that isn't filled to the shape reported in header, values of axes are assigned based on header content, but the units are not read.  Example at https://drive.google.com/open?id=1ta8AEOR-Ck3IENRAHkYbyIjumHP0oiii\r\n\r\n```\r\nd = wt.data.from_PyCMDS(...)\r\nprint(d.w1.points)\r\n```\n", "before_files": [{"content": "\"\"\"PyCMDS.\"\"\"\n\n\n# --- import --------------------------------------------------------------------------------------\n\n\nimport numpy as np\n\nimport tidy_headers\n\nfrom ._data import Data\nfrom .. import kit as wt_kit\n\n\n# --- define --------------------------------------------------------------------------------------\n\n\n__all__ = [\"from_PyCMDS\"]\n\n\n# --- from function -------------------------------------------------------------------------------\n\n\ndef from_PyCMDS(filepath, name=None, parent=None, verbose=True) -> Data:\n    \"\"\"Create a data object from a single PyCMDS output file.\n\n    Parameters\n    ----------\n    filepath : str\n        The file to load. Can accept .data, .fit, or .shots files.\n    name : str or None (optional)\n        The name to be applied to the new data object. If None, name is read\n        from file.\n    parent : WrightTools.Collection (optional)\n        Collection to place new data object within. Default is None.\n    verbose : bool (optional)\n        Toggle talkback. Default is True.\n\n    Returns\n    -------\n    data\n        A Data instance.\n    \"\"\"\n    # header\n    headers = tidy_headers.read(filepath)\n    # name\n    if name is None:  # name not given in method arguments\n        data_name = headers[\"data name\"]\n    else:\n        data_name = name\n    if data_name == \"\":  # name not given in PyCMDS\n        data_name = headers[\"data origin\"]\n    # create data object\n    kwargs = {\n        \"name\": data_name,\n        \"kind\": \"PyCMDS\",\n        \"source\": filepath,\n        \"created\": headers[\"file created\"],\n    }\n    if parent is not None:\n        data = parent.create_data(**kwargs)\n    else:\n        data = Data(**kwargs)\n    # array\n    arr = np.genfromtxt(filepath).T\n    # get axes and scanned variables\n    axes = []\n    for name, identity, units in zip(\n        headers[\"axis names\"], headers[\"axis identities\"], headers[\"axis units\"]\n    ):\n        # points and centers\n        points = np.array(headers[name + \" points\"])\n        if name + \" centers\" in headers.keys():\n            centers = headers[name + \" centers\"]\n        else:\n            centers = None\n        # create\n        axis = {\n            \"points\": points,\n            \"units\": units,\n            \"name\": name,\n            \"identity\": identity,\n            \"centers\": centers,\n        }\n        axes.append(axis)\n    shape = tuple([a[\"points\"].size for a in axes])\n    for i, ax in enumerate(axes):\n        sh = [1] * len(shape)\n        sh[i] = len(ax[\"points\"])\n        data.create_variable(\n            name=ax[\"name\"] + \"_points\", values=np.array(ax[\"points\"]).reshape(sh)\n        )\n        if ax[\"centers\"] is not None:\n            sh = list(shape)\n            sh[i] = 1\n            data.create_variable(\n                name=ax[\"name\"] + \"_centers\", values=np.array(ax[\"centers\"]).reshape(sh)\n            )\n    # get assorted remaining things\n    # variables and channels\n    for index, kind, name in zip(range(len(arr)), headers[\"kind\"], headers[\"name\"]):\n        values = np.full(np.prod(shape), np.nan)\n        values[: len(arr[index])] = arr[index]\n        values.shape = shape\n        if name == \"time\":\n            data.create_variable(name=\"labtime\", values=values)\n        if kind == \"hardware\":\n            # sadly, recorded tolerances are not reliable\n            # so a bit of hard-coded hacking is needed\n            # if this ends up being too fragile, we might have to use the points arrays\n            # ---Blaise 2018-01-09\n            units = headers[\"units\"][index]\n            label = headers[\"label\"][index]\n            if (\n                \"w\" in name\n                and name.startswith(tuple(data.variable_names))\n                and name not in headers[\"axis names\"]\n            ):\n                inherited_shape = data[name.split(\"_\")[0]].shape\n                for i, s in enumerate(inherited_shape):\n                    if s == 1:\n                        values = np.mean(values, axis=i)\n                        values = np.expand_dims(values, i)\n            else:\n                tolerance = headers[\"tolerance\"][index]\n                for i in range(len(shape)):\n                    if tolerance is None:\n                        break\n                    if \"d\" in name:\n                        tolerance = 3.\n                    if \"zero\" in name:\n                        tolerance = 1e-10\n                    try:\n                        assert i == headers[\"axis names\"].index(name)\n                        tolerance = 0\n                    except (ValueError, AssertionError):\n                        if (\n                            name in headers[\"axis names\"]\n                            and \"%s_centers\" % name not in data.variable_names\n                        ):\n                            tolerance = np.inf\n                    mean = np.nanmean(values, axis=i)\n                    mean = np.expand_dims(mean, i)\n                    values, meanexp = wt_kit.share_nans(values, mean)\n                    if np.allclose(meanexp, values, atol=tolerance, equal_nan=True):\n                        values = mean\n            if name in headers[\"axis names\"]:\n                points = np.array(headers[name + \" points\"])\n                pointsshape = [1] * values.ndim\n                for i, ax in enumerate(axes):\n                    if ax[\"name\"] == name:\n                        pointsshape[i] = len(points)\n                        break\n                points.shape = pointsshape\n                for i in range(points.ndim):\n                    if points.shape[i] == 1:\n                        points = np.repeat(points, values.shape[i], axis=i)\n                if points.size <= values.size:\n                    values[np.isnan(values)] = points[np.isnan(values)]\n            data.create_variable(name, values=values, units=units, label=label)\n        if kind == \"channel\":\n            data.create_channel(name=name, values=values, shape=values.shape)\n    # axes\n    for a in axes:\n        expression = a[\"identity\"]\n        if expression.startswith(\"D\"):\n            expression = expression[1:]\n        expression.replace(\"=D\", \"=\")\n        a[\"expression\"] = expression\n    data.transform(*[a[\"expression\"] for a in axes])\n    # return\n    if verbose:\n        print(\"data created at {0}\".format(data.fullpath))\n        print(\"  axes: {0}\".format(data.axis_names))\n        print(\"  shape: {0}\".format(data.shape))\n    return data\n", "path": "WrightTools/data/_pycmds.py"}], "after_files": [{"content": "\"\"\"PyCMDS.\"\"\"\n\n\n# --- import --------------------------------------------------------------------------------------\n\n\nimport numpy as np\n\nimport tidy_headers\n\nfrom ._data import Data\nfrom .. import kit as wt_kit\nfrom .. import units as wt_units\n\n\n# --- define --------------------------------------------------------------------------------------\n\n\n__all__ = [\"from_PyCMDS\"]\n\n\n# --- from function -------------------------------------------------------------------------------\n\n\ndef from_PyCMDS(filepath, name=None, parent=None, verbose=True) -> Data:\n    \"\"\"Create a data object from a single PyCMDS output file.\n\n    Parameters\n    ----------\n    filepath : str\n        The file to load. Can accept .data, .fit, or .shots files.\n    name : str or None (optional)\n        The name to be applied to the new data object. If None, name is read\n        from file.\n    parent : WrightTools.Collection (optional)\n        Collection to place new data object within. Default is None.\n    verbose : bool (optional)\n        Toggle talkback. Default is True.\n\n    Returns\n    -------\n    data\n        A Data instance.\n    \"\"\"\n    # header\n    headers = tidy_headers.read(filepath)\n    # name\n    if name is None:  # name not given in method arguments\n        data_name = headers[\"data name\"]\n    else:\n        data_name = name\n    if data_name == \"\":  # name not given in PyCMDS\n        data_name = headers[\"data origin\"]\n    # create data object\n    kwargs = {\n        \"name\": data_name,\n        \"kind\": \"PyCMDS\",\n        \"source\": filepath,\n        \"created\": headers[\"file created\"],\n    }\n    if parent is not None:\n        data = parent.create_data(**kwargs)\n    else:\n        data = Data(**kwargs)\n    # array\n    arr = np.genfromtxt(filepath).T\n    # get axes and scanned variables\n    axes = []\n    for name, identity, units in zip(\n        headers[\"axis names\"], headers[\"axis identities\"], headers[\"axis units\"]\n    ):\n        # points and centers\n        points = np.array(headers[name + \" points\"])\n        if name + \" centers\" in headers.keys():\n            centers = headers[name + \" centers\"]\n        else:\n            centers = None\n        # create\n        axis = {\n            \"points\": points,\n            \"units\": units,\n            \"name\": name,\n            \"identity\": identity,\n            \"centers\": centers,\n        }\n        axes.append(axis)\n    shape = tuple([a[\"points\"].size for a in axes])\n    for i, ax in enumerate(axes):\n        sh = [1] * len(shape)\n        sh[i] = len(ax[\"points\"])\n        data.create_variable(\n            name=ax[\"name\"] + \"_points\", values=np.array(ax[\"points\"]).reshape(sh)\n        )\n        if ax[\"centers\"] is not None:\n            sh = list(shape)\n            sh[i] = 1\n            data.create_variable(\n                name=ax[\"name\"] + \"_centers\", values=np.array(ax[\"centers\"]).reshape(sh)\n            )\n    # get assorted remaining things\n    # variables and channels\n    for index, kind, name in zip(range(len(arr)), headers[\"kind\"], headers[\"name\"]):\n        values = np.full(np.prod(shape), np.nan)\n        values[: len(arr[index])] = arr[index]\n        values.shape = shape\n        if name == \"time\":\n            data.create_variable(name=\"labtime\", values=values)\n        if kind == \"hardware\":\n            # sadly, recorded tolerances are not reliable\n            # so a bit of hard-coded hacking is needed\n            # if this ends up being too fragile, we might have to use the points arrays\n            # ---Blaise 2018-01-09\n            units = headers[\"units\"][index]\n            label = headers[\"label\"][index]\n            if (\n                \"w\" in name\n                and name.startswith(tuple(data.variable_names))\n                and name not in headers[\"axis names\"]\n            ):\n                inherited_shape = data[name.split(\"_\")[0]].shape\n                for i, s in enumerate(inherited_shape):\n                    if s == 1:\n                        values = np.mean(values, axis=i)\n                        values = np.expand_dims(values, i)\n            else:\n                tolerance = headers[\"tolerance\"][index]\n                for i in range(len(shape)):\n                    if tolerance is None:\n                        break\n                    if \"d\" in name:\n                        tolerance = 3.\n                    if \"zero\" in name:\n                        tolerance = 1e-10\n                    try:\n                        assert i == headers[\"axis names\"].index(name)\n                        tolerance = 0\n                    except (ValueError, AssertionError):\n                        if (\n                            name in headers[\"axis names\"]\n                            and \"%s_centers\" % name not in data.variable_names\n                        ):\n                            tolerance = np.inf\n                    mean = np.nanmean(values, axis=i)\n                    mean = np.expand_dims(mean, i)\n                    values, meanexp = wt_kit.share_nans(values, mean)\n                    if np.allclose(meanexp, values, atol=tolerance, equal_nan=True):\n                        values = mean\n            if name in headers[\"axis names\"]:\n                points = np.array(headers[name + \" points\"])\n                pointsshape = [1] * values.ndim\n                for i, ax in enumerate(axes):\n                    if ax[\"name\"] == name:\n                        pointsshape[i] = len(points)\n                        break\n                points.shape = pointsshape\n                points = wt_units.converter(points, headers[\"axis units\"][i], units)\n                for i in range(points.ndim):\n                    if points.shape[i] == 1:\n                        points = np.repeat(points, values.shape[i], axis=i)\n                if points.size <= values.size:\n                    values[np.isnan(values)] = points[np.isnan(values)]\n            data.create_variable(name, values=values, units=units, label=label)\n        if kind == \"channel\":\n            data.create_channel(name=name, values=values, shape=values.shape)\n    # axes\n    for a in axes:\n        expression = a[\"identity\"]\n        if expression.startswith(\"D\"):\n            expression = expression[1:]\n        expression.replace(\"=D\", \"=\")\n        a[\"expression\"] = expression\n    data.transform(*[a[\"expression\"] for a in axes])\n    for a, u in zip(data.axes, headers[\"axis units\"]):\n        a.convert(u)\n    # return\n    if verbose:\n        print(\"data created at {0}\".format(data.fullpath))\n        print(\"  axes: {0}\".format(data.axis_names))\n        print(\"  shape: {0}\".format(data.shape))\n    return data\n", "path": "WrightTools/data/_pycmds.py"}]}