Repository: greydanus/mnist1d (language: python)

Issue:
Packaging this dataset
Hi, thanks for contributing this dataset as open source. Motivated by "Understanding Deep Learning", I'd love to start playing with it. 

I saw that the repo is not ready for packaging. As the code is rather straight forward, I was wondering if any PRs would be appreciated to package this as a python module. 

This would also make it more easier to use more broadly than by using the requests package. People could just go: `pip install mnist1d` and have the code for augmenting and changing under their fingers. Just a thought!

Right now, I get the following:
``` shell
python -m pip install git+https://github.com/greydanus/mnist1d.git@master
```
``` output
Collecting git+https://github.com/greydanus/mnist1d.git@master
  Cloning https://github.com/greydanus/mnist1d.git (to revision master) to /tmp/pip-req-build-eg1a_6cu
  Running command git clone --filter=blob:none --quiet https://github.com/greydanus/mnist1d.git /tmp/pip-req-build-eg1a_6cu
  Resolved https://github.com/greydanus/mnist1d.git to commit 39dd6c03785eefe60f349af94e61f864fc449644
ERROR: git+https://github.com/greydanus/mnist1d.git@master does not appear to be a Python project: neither 'setup.py' nor 'pyproject.toml' found.
```


Discussion / hints:
Peter, this is a great suggestion. I haven never packaged a library before - can you help me set this up? I'm all ears.

Fix patch:
diff --git a/.gitignore b/.gitignore
index b85f450..766bf51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,6 @@
 *.tar
 *.zip
 # *.pkl # allow pickle files
-__pycache__
\ No newline at end of file
+__pycache__
+.vscode
+*~
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..722b6ce
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,5 @@
+global-exclude notebooks/*
+global-exclude .git*
+global-exclude static/*
+global-exclude *png
+global-exclude *pkl
diff --git a/README.md b/README.md
index 5eb7123..890ee07 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ The original MNIST dataset is supposed to be the [Drosophilia of machine learnin
 Getting the dataset
 --------
 
-Here's a minimal example of how to download the dataset:
+Here's a minimal example of how to download the dataset (available with the github repository):
 
 ```
 import requests, pickle
@@ -76,7 +76,26 @@ data.keys()
 >>> dict_keys(['x', 'x_test', 'y', 'y_test', 't', 'templates'])  # these are NumPy arrays
 ```
 
-A slightly better way to do things is to clone this repo and then use the `get_dataset` method in `data.py` to do essentially the same thing.
+Building the dataset
+---------
+
+A slightly better way to do things is to clone this repo and then use the `get_dataset` method in `data.py` to do essentially the same thing. For this, you can build the repo locally.
+
+``` shell
+python -m pip install git+https://github.com/psteinb/mnist1d.git@master
+```
+
+This allows you to build the default dataset locally:
+
+```python
+from mnist1d.data import make_dataset, get_dataset_args
+
+defaults = get_dataset_args()
+data = make_dataset(defaults)
+x,y,t = data['x'], data['y'], data['t']
+```
+
+If you want to play around with this, see [notebooks/mnist1d_tiny.ipynb](notebooks/mnist1d_tiny.ipynb).
 
 
 Dimensionality reduction
diff --git a/__init__.py b/__init__.py
deleted file mode 100644
index 43a1cc5..0000000
--- a/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# The MNIST-1D dataset | 2020
-# Sam Greydanus
-
-from .data import get_dataset, get_dataset_args, get_templates
-from .utils import to_pickle, from_pickle, ObjectView, set_seed, plot_signals
-from .transform import transform
-from .train import get_model_args, train_model, accuracy
-from .models import LinearBase, MLPBase, ConvBase, GRUBase
\ No newline at end of file
diff --git a/mnist1d/.gitignore b/mnist1d/.gitignore
new file mode 100644
index 0000000..3798a0a
--- /dev/null
+++ b/mnist1d/.gitignore
@@ -0,0 +1,1 @@
+py3*/
diff --git a/mnist1d/__init__.py b/mnist1d/__init__.py
new file mode 100644
index 0000000..04d522f
--- /dev/null
+++ b/mnist1d/__init__.py
@@ -0,0 +1,6 @@
+# The MNIST-1D dataset | 2024
+# Sam Greydanus, Peter Steinbach
+
+from .data import get_dataset, get_dataset_args, get_templates
+from .utils import to_pickle, from_pickle, ObjectView, set_seed, plot_signals
+from .transform import transform
\ No newline at end of file
diff --git a/data.py b/mnist1d/data.py
similarity index 90%
rename from data.py
rename to mnist1d/data.py
index 00e62e6..3f5248b 100644
--- a/data.py
+++ b/mnist1d/data.py
@@ -4,10 +4,22 @@
 import numpy as np
 import os
 import requests
-from .transform import transform
-from .utils import from_pickle, to_pickle, ObjectView, set_seed
+from mnist1d.transform import transform
+from mnist1d.utils import from_pickle, to_pickle, ObjectView, set_seed
 
 def get_dataset_args(as_dict=False):
+    """ Generate dictionary with dataset properties
+
+    Parameters
+    ----------
+    as_dict : bool, optional
+        if true, return the dataset properties as dictionary; if false, return an ObjectView, by default False
+
+    Returns
+    -------
+    _type_
+        _description_
+    """
     arg_dict = {'num_samples': 5000,
             'train_split': 0.8,
             'template_len': 12,
@@ -51,7 +63,7 @@ def get_templates():
 def make_dataset(args=None, template=None, ):
     templates = get_templates() if template is None else template
     args = get_dataset_args() if args is None else args
-    np.random.seed(args.seed) # reproducibility
+    set_seed(args.seed) # reproducibility
     
     xs, ys = [], []
     samples_per_class = args.num_samples // len(templates['y'])
@@ -84,6 +96,7 @@ def make_dataset(args=None, template=None, ):
 
 
 # we'll cache the dataset so that it doesn't have to be rebuild every time
+# args must not be a dict
 def get_dataset(args, path=None, verbose=True, download=True, regenerate=False, **kwargs):
     if 'args' in kwargs.keys() and kwargs['args'].shuffle_seq:
         shuffle = "_shuffle"
diff --git a/mnist1d/transform.py b/mnist1d/transform.py
new file mode 100644
index 0000000..8745976
--- /dev/null
+++ b/mnist1d/transform.py
@@ -0,0 +1,83 @@
+# The MNIST-1D dataset | 2024
+# Sam Greydanus, Peter Steinbach
+
+import numpy as np
+import scipy
+from scipy.interpolate import interp1d
+from scipy.ndimage import gaussian_filter
+
+
+# transformations of the templates which will make them harder to classify
+def pad(x, padding: tuple):
+    """pad signal x with random number of zeros. Note, the signal is only padded at indices given by the interval in padding
+
+    Parameters
+    ----------
+    x : _type_
+        signal
+    padding : tuple
+        (low, high) corresponds to (start,end) of padding
+
+    Returns
+    -------
+    _type_
+        a padded signal
+    """
+    low, high = padding
+    p = low + int(np.random.rand() * (high - low + 1))
+    if len(x.shape) == 1:
+        return np.concatenate([x, np.zeros((p))])
+    else:
+        padding = np.zeros((x.shape[0], p))
+        return np.concatenate([x, padding], axis=-1)
+
+
+def shear(x, scale=10):
+    # TODO: add docstring
+    coeff = scale * (np.random.rand() - 0.5)
+    return x - coeff * np.linspace(-0.5, 0.5, len(x))
+
+
+def translate(x, max_translation):
+    # TODO: add docstring
+    k = np.random.choice(max_translation)
+    return np.concatenate([x[-k:], x[:-k]])
+
+
+def corr_noise_like(x, scale):
+    # TODO: add docstring
+    noise = scale * np.random.randn(*x.shape)
+    return gaussian_filter(noise, 2)
+
+
+def iid_noise_like(x, scale):
+    # TODO: add docstring
+    noise = scale * np.random.randn(*x.shape)
+    return noise
+
+
+def interpolate(x, N):
+    # TODO: add docstring
+    scale = np.linspace(0, 1, len(x))
+    new_scale = np.linspace(0, 1, N)
+    new_x = interp1d(scale, x, axis=0, kind="linear")(new_scale)
+    return new_x
+
+
+def transform(x, y, args, eps=1e-8):
+    new_x = pad(x + eps, args.padding)  # pad
+    new_x = interpolate(new_x, args.template_len + args.padding[-1])  # dilate
+    new_y = interpolate(y, args.template_len + args.padding[-1])
+    new_x *= 1 + args.scale_coeff * (np.random.rand() - 0.5)  # scale
+    new_x = translate(new_x, args.max_translation)  # translate
+
+    # add noise
+    mask = new_x != 0
+    new_x = mask * new_x + (1 - mask) * corr_noise_like(new_x, args.corr_noise_scale)
+    new_x = new_x + iid_noise_like(new_x, args.iid_noise_scale)
+
+    # shear and interpolate
+    new_x = shear(new_x, args.shear_scale)
+    new_x = interpolate(new_x, args.final_seq_length)  # subsample
+    new_y = interpolate(new_y, args.final_seq_length)
+    return new_x, new_y
diff --git a/utils.py b/mnist1d/utils.py
similarity index 87%
rename from utils.py
rename to mnist1d/utils.py
index a65f5ae..e7f70f3 100644
--- a/utils.py
+++ b/mnist1d/utils.py
@@ -1,18 +1,15 @@
-# The MNIST-1D dataset | 2020
-# Sam Greydanus
+# The MNIST-1D dataset | 2024
+# Sam Greydanus, Peter Steinbach
 
 import numpy as np
-import torch
 import random
 import pickle
 import matplotlib.pyplot as plt
-from .transform import transform
+from mnist1d.transform import transform
 
 def set_seed(seed):
     random.seed(seed)
     np.random.seed(seed)
-    torch.manual_seed(seed)
-    torch.cuda.manual_seed_all(seed)
 
 def to_pickle(thing, path): # save something
     with open(path, 'wb') as handle:
@@ -20,10 +17,10 @@ def to_pickle(thing, path): # save something
 
 
 def from_pickle(path): # load something
-    thing = None
+    value = None
     with open(path, 'rb') as handle:
-        thing = pickle.load(handle)
-    return thing
+        value = pickle.load(handle)
+    return value
 
 class ObjectView(object):
     def __init__(self, d): self.__dict__ = d
diff --git a/notebooks/README.md b/notebooks/README.md
new file mode 100644
index 0000000..19fc2a3
--- /dev/null
+++ b/notebooks/README.md
@@ -0,0 +1,14 @@
+# Try out the MNIST1D dataset
+
+## Installation instructions
+
+1. clone the repo
+``` shell
+git clone https://github.com/psteinb/mnist1d.git
+```
+2. install all additional depdendencies
+``` shell
+cd mnist1d
+python -m pip install '.[all]'
+```
+3. run the notebooks, [quickstart.ipynb](quickstart.ipynb) or [mnist1d_tiny.ipynb](mnist1d_tiny.ipynb) are a good start.
diff --git a/building_mnist1d.ipynb b/notebooks/building_mnist1d.ipynb
similarity index 99%
rename from building_mnist1d.ipynb
rename to notebooks/building_mnist1d.ipynb
index 9645c0b..6d2323f 100644
--- a/building_mnist1d.ipynb
+++ b/notebooks/building_mnist1d.ipynb
@@ -683,7 +683,7 @@
     "datas, guesses, trues = [], [], []\n",
     "accs = []\n",
     "for tag in tags:\n",
-    "    [d, g, t] = from_pickle('mnist1d/static/human_{}.pkl'.format(tag))\n",
+    "    [d, g, t] = from_pickle('./static/human_{}.pkl'.format(tag))\n",
     "    acc = 100.*np.sum(np.asarray(g)==np.asarray(t))/len(g)\n",
     "    print(tag, acc)\n",
     "    accs.append(acc)\n",
@@ -1062,7 +1062,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3 (ipykernel)",
+   "display_name": "Python 3",
    "language": "python",
    "name": "python3"
   },
@@ -1076,7 +1076,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.18"
+   "version": "3.8.2"
   }
  },
  "nbformat": 4,
diff --git a/notebooks/mnist1d_tiny.ipynb b/notebooks/mnist1d_tiny.ipynb
new file mode 100644
index 0000000..a2693dc
--- /dev/null
+++ b/notebooks/mnist1d_tiny.ipynb
@@ -0,0 +1,33 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "87fa18b4-7d57-46e5-aa42-694ac83ef4e6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from mnist1d.data import make_dataset, get_dataset_args\n",
+    "\n",
+    "defaults = get_dataset_args()\n",
+    "data = make_dataset(defaults)\n",
+    "x,y,t = data['x'], data['y'], data['t']\n",
+    "\n",
+    "x.shape, y.shape, t.shape"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "64f2699a-9642-4b5e-92a0-a13f0460b23e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert x.shape[0] == y.shape[0]"
+   ]
+  }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/models.py b/notebooks/models.py
similarity index 100%
rename from models.py
rename to notebooks/models.py
diff --git a/quickstart.ipynb b/notebooks/quickstart.ipynb
similarity index 100%
rename from quickstart.ipynb
rename to notebooks/quickstart.ipynb
diff --git a/train.py b/notebooks/train.py
similarity index 92%
rename from train.py
rename to notebooks/train.py
index 91bbd8f..aa17bb4 100644
--- a/train.py
+++ b/notebooks/train.py
@@ -1,5 +1,5 @@
-# The MNIST-1D dataset | 2020
-# Sam Greydanus
+# The MNIST-1D dataset | 2024
+# Peter Steinbach
 
 import time, copy
 import numpy as np
@@ -9,7 +9,13 @@
 import torch.nn.functional as F
 import torch.optim as optim
 
-from .utils import ObjectView
+from mnist1d.utils import ObjectView
+
+def set_seed(seed):
+    random.seed(seed)
+    np.random.seed(seed)
+    torch.manual_seed(seed)
+    torch.cuda.manual_seed_all(seed)
 
 def get_model_args(as_dict=False):
   arg_dict = {'input_size': 40,
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..4994540
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,51 @@
+[build-system]
+requires = ["setuptools", "setuptools-scm"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "mnist1d"
+authors = [
+    {name = "Sam Greydanus", email = "greydanus@users.noreply.github.com"},
+    {name = "Peter Steinbach", email = "psteinb@users.noreply.github.com"},
+    {name = "Karim", email = "karim-53@users.noreply.github.com"},
+    {name = "Dmitry Kobak", email= "dmitry.kobak@uni-tuebingen.de"},
+    {name = "Jakob Jordan", email= "jakobjordan@posteo.de"}
+]
+description = "A 1D analogue of the MNIST dataset for measuring spatial biases and answering Science of Deep Learning questions"
+readme = "README.md"
+requires-python = ">=3.7"
+keywords = ["dataset", "mnist", "machine learning", "deep learning", "1D"]
+license = {text = "Apache-2.0"}
+classifiers = [
+    "License :: OSI Approved :: Apache 2.0 License",
+    "Programming Language :: Python",
+    "Framework :: Numpy",
+    "Framework :: Scipy",
+    "Programming Language :: Python :: 3",
+]
+dependencies = [
+    'requests',
+    'numpy',
+    'matplotlib',
+    'scipy'
+]
+dynamic = ["version"]
+
+[project.optional-dependencies]
+dev = ["pytest"]
+all = ["torch","notebook"]
+
+[tool.setuptools]
+packages = ["mnist1d"]
+
+# [tool.setuptools.packages.find]
+# where = ["mnist1d"]
+
+# [tool.setuptools.exclude-package-data]
+# mnist1d = ["notebooks/*", "static/*", "*pkl"]
+
+[project.urls]
+Homepage = "https://github.com/greydanus/mnist1d"
+
+# [project.scripts]
+# my-script = "my_package.module:function"
\ No newline at end of file
diff --git a/transform.py b/transform.py
deleted file mode 100644
index b7ee93b..0000000
--- a/transform.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# The MNIST-1D dataset | 2020
-# Sam Greydanus
-
-import numpy as np
-import scipy
-from scipy.ndimage.filters import gaussian_filter
-from scipy.interpolate import interp1d
-
-# transformations of the templates which will make them harder to classify
-def pad(x, padding):
-    low, high = padding
-    p = low + int(np.random.rand()*(high-low+1))
-    return np.concatenate([x, np.zeros((p))])
-
-def shear(x, scale=10):
-    coeff = scale*(np.random.rand() - 0.5)
-    return x - coeff*np.linspace(-0.5,.5,len(x))
-
-def translate(x, max_translation):
-    k = np.random.choice(max_translation)
-    return np.concatenate([x[-k:], x[:-k]])
-
-def corr_noise_like(x, scale):
-    noise = scale * np.random.randn(*x.shape)
-    return gaussian_filter(noise, 2)
-
-def iid_noise_like(x, scale):
-    noise = scale * np.random.randn(*x.shape)
-    return noise
-
-def interpolate(x, N):
-    scale = np.linspace(0,1,len(x))
-    new_scale = np.linspace(0,1,N)
-    new_x = interp1d(scale, x, axis=0, kind='linear')(new_scale)
-    return new_x
-
-def transform(x, y, args, eps=1e-8):
-    new_x = pad(x+eps, args.padding) # pad
-    new_x = interpolate(new_x, args.template_len + args.padding[-1])  # dilate
-    new_y = interpolate(y, args.template_len + args.padding[-1])
-    new_x *= (1 + args.scale_coeff*(np.random.rand() - 0.5))  # scale
-    new_x = translate(new_x, args.max_translation)  #translate
-    
-    # add noise
-    mask = new_x != 0
-    new_x = mask*new_x + (1-mask)*corr_noise_like(new_x, args.corr_noise_scale)
-    new_x = new_x + iid_noise_like(new_x, args.iid_noise_scale)
-    
-    # shear and interpolate
-    new_x = shear(new_x, args.shear_scale)
-    new_x = interpolate(new_x, args.final_seq_length) # subsample
-    new_y = interpolate(new_y, args.final_seq_length)
-    return new_x, new_y
\ No newline at end of file


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..229795a
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,48 @@
+name: MNIST1D tests
+
+on: [push]
+
+jobs:
+  build: #building the library locally
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      # You can test your matrix by printing the current Python version
+      - name: Display Python version
+        run: python -c "import sys; print(sys.version)"
+      - name: Install dev version locally
+        run: python -m pip install '.[dev]'
+      - name: test dev version locally
+        run: python -m pytest .
+      - name: install notebooks
+        run: python -m pip install notebook jupyter
+      - name: run minimal example notebooks
+        run: jupyter nbconvert --execute --to html notebooks/mnist1d_tiny.ipynb
+      
+  install: #installing the library as if one would not want to git-clone it
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Display Python version
+        run: python -c "import sys; print(sys.version)"
+      - name: Install dev version locally
+        run: python -m pip install git+https://github.com/psteinb/mnist1d.git@psteinb-pyprojtoml
+
diff --git a/tests/test_data.py b/tests/test_data.py
new file mode 100644
index 0000000..39302c6
--- /dev/null
+++ b/tests/test_data.py
@@ -0,0 +1,67 @@
+# The MNIST-1D dataset | 2024
+# Peter Steinbach
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+from mnist1d.data import get_templates, make_dataset, get_dataset, get_dataset_args
+import numpy as np
+import pytest
+
+@pytest.fixture
+def tmpfile():
+    value = NamedTemporaryFile(delete=True)
+    print(value, value.file)
+    yield value
+    value.close()
+
+def test_get_templates():
+    obs = get_templates()
+    assert isinstance(obs, dict)
+    assert 'x' in obs.keys()
+    assert 'y' in obs.keys()
+    assert 't' in obs.keys()
+
+    x,y,t = obs['x'], obs['y'], obs['t']
+    assert isinstance(y,np.ndarray)
+    assert (y == np.asarray(list(range(10)))).all()
+
+    assert x.shape == (10,12)
+    assert y.shape == (10,)
+
+def test_make_dataset():
+
+    obs = make_dataset()
+    assert isinstance(obs, dict)
+    assert 'x' in obs.keys()
+    assert 'y' in obs.keys()
+    assert 't' in obs.keys()
+    assert 'x_test' in obs.keys()
+    assert 'y_test' in obs.keys()
+    assert 'templates' in obs.keys()
+
+    x,y,t = obs['x'], obs['y'], obs['t']
+    x_test,y_test = obs['x_test'], obs['y_test']
+
+    assert x.shape == (4000,40)
+    assert y.shape == (4000,)
+
+    assert x_test.shape == (1000,40)
+    assert y_test.shape == (1000,)
+
+def test_get_dataset_args():
+    defaults = get_dataset_args(as_dict=True)
+    assert 'seed' in defaults.keys()
+    assert defaults['seed'] == 42
+
+def test_get_dataset(tmpfile):
+
+    defaults = get_dataset_args(as_dict=False)
+    tmp = str(tmpfile)
+
+    obs = get_dataset(args=defaults,path=tmp)
+    assert isinstance(obs, dict)
+    assert 'x' in obs.keys()
+    assert 'y' in obs.keys()
+    assert 't' in obs.keys()
+    assert 'x_test' in obs.keys()
+    assert 'y_test' in obs.keys()
+    assert 'templates' in obs.keys()
diff --git a/tests/test_transform.py b/tests/test_transform.py
new file mode 100644
index 0000000..c85e2f4
--- /dev/null
+++ b/tests/test_transform.py
@@ -0,0 +1,31 @@
+# The MNIST-1D dataset | 2024
+# Peter Steinbach
+
+from mnist1d.data import get_dataset, get_dataset_args
+from mnist1d.utils import set_seed
+from mnist1d.transform import pad
+
+from tempfile import NamedTemporaryFile
+import pytest
+
+@pytest.fixture
+def tmpfile():
+    value = NamedTemporaryFile(delete=True)
+    print(value, value.file)
+    yield value
+    value.close()
+
+def test_padding(tmpfile):
+    set_seed(13)
+    
+    defaults = get_dataset_args()
+    data = get_dataset(args=defaults,download=False)
+    x = data['x']
+    obs = pad(x[0,...],(36,44))
+    assert x.shape != obs.shape
+    assert obs.shape == (82,)
+
+    obs = pad(x,(36,44))
+    assert x.shape != obs.shape
+    assert obs.shape == (4000,78)
+
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..cadfb80
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,44 @@
+# The MNIST-1D dataset | 2024
+# Peter Steinbach
+
+from mnist1d.data import get_dataset, get_dataset_args
+from mnist1d.utils import set_seed, to_pickle, from_pickle, ObjectView
+import numpy as np
+
+from tempfile import mkstemp
+import pytest
+import os
+
+@pytest.fixture
+def tmpfile():
+    handle, path = mkstemp()
+    print("temporarily using",path)
+    yield path
+    os.unlink(path)
+
+def test_seed_is_fixed():
+
+    set_seed(42)
+    exp = np.random.randn(8)
+    set_seed(42)
+    obs = np.random.randn(8)
+
+    assert np.allclose(exp,obs)    
+
+def test_pickle_roundtrip(tmpfile):
+
+    exp = dict(a=3,b=11.01,c="d",d="foobar")
+    _ = to_pickle(exp,tmpfile)
+    obs = from_pickle(tmpfile)
+
+    assert type(exp) == type(obs)
+    for k,v in exp.items():
+        assert v == obs[k]
+
+def test_object_view():
+    #TODO: IIRC there are better ways to do this
+    exp = dict(a=3,b=11.01,c="d",d="foobar")
+    obs = ObjectView(exp)
+
+    assert type(obs) != type(exp)
+    assert obs.a == exp['a']
\ No newline at end of file