diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..bce638805c360b8f2052ba78c866bc95f8ebff69
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,45 @@
+[metadata]
+name = zarr-tools
+description = Convert nd2 to zarr
+long_description = file: README.md
+long_description_content_type = text/markdown
+url = https://gitlab.pasteur.fr/aaristov/zarr-tools
+author = Andrey Aristov
+author_email = aaristov@pasteur.fr
+license = BSD-3-Clause
+license_file = LICENSE
+classifiers =
+    Development Status :: 2 - Pre-Alpha
+    Framework :: napari
+    Intended Audience :: Developers
+    License :: OSI Approved :: BSD License
+    Operating System :: OS Independent
+    Programming Language :: Python
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3 :: Only
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Topic :: Software Development :: Testing
+project_urls =
+    Bug Tracker = https://gitlab.pasteur.fr/aaristov/zarr-tools/issues
+
+[options]
+packages = find:
+install_requires =
+    dask
+    fire
+    nd2
+    zarr
+    
+python_requires = >=3.8
+include_package_data = True
+package_dir =
+    =src
+setup_requires =
+    setuptools-scm
+
+[options.packages.find]
+where = src
+
+
diff --git a/src/zarr_tools/__init__.py b/src/zarr_tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/src/zarr_tools/__init__.py
@@ -0,0 +1 @@
+
diff --git a/src/zarr_tools/__main__.py b/src/zarr_tools/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..afdb8e63d0626d6467ba8253a3a4d8812153790d
--- /dev/null
+++ b/src/zarr_tools/__main__.py
@@ -0,0 +1,21 @@
+from .convert import to_zarr
+import fire
+import nd2
+import os
+
+def main(nd2_path:str, output:str=None, channel_axis:int=1, steps:int=6, dry_run=False):
+    data = (d := nd2.ND2File(nd2_path)).to_dask().rechunk()
+    print(d.sizes)
+    try:
+        channel_axis = list(d.sizes.keys()).index('C')
+    except ValueError:
+        channel_axis = None
+
+    if output is None:
+        output = nd2_path.replace('.nd2', '.zarr')
+    out = to_zarr(data, output, steps=steps, dry_run=dry_run, channel_axis=channel_axis)
+    assert os.path.exists(out), "Failed..."
+    exit(0)
+
+if __name__=="__main__":
+    fire.Fire(main)
diff --git a/src/zarr_tools/convert.py b/src/zarr_tools/convert.py
new file mode 100644
index 0000000000000000000000000000000000000000..5dd27c41d4f1a35bd8a3b199f019dfb6a9b989ea
--- /dev/null
+++ b/src/zarr_tools/convert.py
@@ -0,0 +1,62 @@
+import zarr
+import dask.array as da
+import os
+import nd2
+
+def nd2_to_zarr(path_nd2, out=None, steps=6, dry_run=False):
+    '''
+    Converts nd2 to zarr, multiscale
+    '''
+    data = nd2.ND2File(path_nd2)
+    print(sizes := data.sizes)
+    channel_axis = list(sizes.keys()).index('C')
+    out = (path_nd2.replace('.nd2', '.zarr') if out is None else path_nd2)
+    dask_input = data.to_dask()
+    _ = to_zarr(
+        dask_input=dask_input,
+        path=out,
+        steps=steps,
+        channel_axis=channel_axis,
+        dry_run=dry_run
+    )
+
+
+def to_zarr(dask_input:da.Array, path:str=None, steps=3, channel_axis=1, dry_run=False):
+    store = zarr.DirectoryStore(baseurl := path.replace('.nd2', '.zarr') if path is None else path)
+    grp = zarr.group(store)
+    grp.attrs['multiscales'] = {
+        "multiscales": [
+            {
+                "datasets": [
+                    {
+                        "path": str(i)
+                    } for i in range(steps)
+                ],
+                "name": os.path.basename(baseurl),
+                "type": "nd2",
+                "channel_axis": channel_axis,
+                "version": "0.1"
+            },
+
+        ]
+    }
+    print(baseurl)
+    for i in range(steps):
+        data = dask_input[...,::2,::2]
+        try:
+            data = data.rechunk()
+            print(data.chunksize, data.shape)
+            if not dry_run:
+                data.to_zarr(ppp:=os.path.join(baseurl, str(i)))
+                print(f'saved {ppp}')
+            else:
+                print(f'dry-run `to save to` {os.path.join(baseurl, str(i))}')
+                
+            dask_input = da.from_zarr(ppp)
+        except Exception as e:
+            print(e.args)
+            raise e
+        
+    return baseurl
+    
+