Repository: napari/napari

Issue:
Can't open zarr contains which contains any other than the expected files
### 🐛 Bug Report

Originally reported by @tibuch here: https://github.com/ome/napari-ome-zarr/issues/106

### 💡 Steps to Reproduce

> I have a zarr container with the following structure:
> 
> ```
> container.zarr
>     - 0
>     - .zattrs
>     - .zgroup
> ```
> 
> I can drag and drop this on napari and the data is opened.
> 
> However, if I add just another file like:
> 
> ```
> container.zarr
>     - 0
>     - test.txt
>     - .zattrs
>     - .zgroup
> ```
> 
> It fails with:
> 
> ```
> ValueError: Not a zarr dataset or group: /path/to/container.zarr/test.txt
> ```
> 
> In my opinion the plugin should only look at the files which are defined by the spec and ignore any other files.



### 💡 Expected Behavior

_No response_

### 🌎 Environment

napari: 0.4.19

### 💡 Additional Context

_No response_


Discussion:
Thanks for reporting. I agree with the suggested expected behavior.
There's a couple `builtins` issues, looks like that part of napari needs a closer look. 
The problem code is [here](https://github.com/napari/napari/blob/main/napari_builtins/io/_read.py#L129-L131). It tries to read everything in the directory that doesn't start with a `.`. We should either `try..except` this and only raise later if the `image` is empty, or find a better way to identify the arrays than paths that don't start with a `.`. @jni thoughts?
> thoughts?

I actually have no memory of supporting multi-arrays at all, that's neat! 😜 

I think we can be a little more proactive in the iteration — we should only iterate *directories* in that list comprehension. So:

```
for subpath is sorted(os.listdir(path))
if not subpath.startswith('.')
and os.path.isdir(path)
and (os.path.exists(os.path.join(path, '.zarray'))
     or os.path.exists(os.path.join(path, '.zgroup')))
```

At this point I think wrap `path` in a `pathlib.Path` to make that whole thing less of a mouthful, but you get my drift. Basically, only recurse subdirectories and only if they are actually zarr arrays or nested groups.
Great well, that should be an easy PR! 

@imagejan if you are at all interested in contributing this fix yourself, let us know and we can guide you through it. Otherwise one of the core team will get to it as soon as we can!

Resolving patch:
diff --git a/napari_builtins/io/_read.py b/napari_builtins/io/_read.py
--- a/napari_builtins/io/_read.py
+++ b/napari_builtins/io/_read.py
@@ -103,7 +103,7 @@ def _guess_zarr_path(path: str) -> bool:
     return any(part.endswith('.zarr') for part in Path(path).parts)
 
 
-def read_zarr_dataset(path):
+def read_zarr_dataset(path: str):
     """Read a zarr dataset, including an array or a group of arrays.
 
     Parameters
@@ -119,16 +119,17 @@ def read_zarr_dataset(path):
     shape : tuple
         Shape of array or first array in list
     """
-    if os.path.exists(os.path.join(path, '.zarray')):
+    path = Path(path)
+    if (path / '.zarray').exists():
         # load zarr array
         image = da.from_zarr(path)
         shape = image.shape
-    elif os.path.exists(os.path.join(path, '.zgroup')):
+    elif (path / '.zgroup').exists():
         # else load zarr all arrays inside file, useful for multiscale data
         image = [
-            read_zarr_dataset(os.path.join(path, subpath))[0]
-            for subpath in sorted(os.listdir(path))
-            if not subpath.startswith('.')
+            read_zarr_dataset(subpath)[0]
+            for subpath in sorted(path.iterdir())
+            if not subpath.name.startswith('.') and subpath.is_dir()
         ]
         assert image, 'No arrays found in zarr group'
         shape = image[0].shape


Test patch:
diff --git a/napari_builtins/_tests/test_io.py b/napari_builtins/_tests/test_io.py
--- a/napari_builtins/_tests/test_io.py
+++ b/napari_builtins/_tests/test_io.py
@@ -91,6 +91,20 @@ def test_zarr_nested(tmp_path):
     np.testing.assert_array_equal(image, image_in)
 
 
+def test_zarr_with_unrelated_file(tmp_path):
+    image = np.random.random((10, 20, 20))
+    image_name = 'my_image'
+    root_path = tmp_path / 'dataset.zarr'
+    grp = zarr.open(str(root_path), mode='a')
+    grp.create_dataset(image_name, data=image)
+
+    txt_file_path = root_path / 'unrelated.txt'
+    txt_file_path.touch()
+
+    image_in = magic_imread([str(root_path)])
+    np.testing.assert_array_equal(image, image_in[0])
+
+
 def test_zarr_multiscale():
     multiscale = [
         np.random.random((20, 20)),