napari-file2folder 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- napari_file2folder-0.0.1/LICENSE +22 -0
- napari_file2folder-0.0.1/MANIFEST.in +5 -0
- napari_file2folder-0.0.1/PKG-INFO +156 -0
- napari_file2folder-0.0.1/README.md +94 -0
- napari_file2folder-0.0.1/pyproject.toml +119 -0
- napari_file2folder-0.0.1/setup.cfg +4 -0
- napari_file2folder-0.0.1/src/napari_file2folder/__init__.py +6 -0
- napari_file2folder-0.0.1/src/napari_file2folder/_custom_widgets.py +95 -0
- napari_file2folder-0.0.1/src/napari_file2folder/_widget.py +459 -0
- napari_file2folder-0.0.1/src/napari_file2folder/napari.yaml +14 -0
- napari_file2folder-0.0.1/src/napari_file2folder.egg-info/PKG-INFO +156 -0
- napari_file2folder-0.0.1/src/napari_file2folder.egg-info/SOURCES.txt +14 -0
- napari_file2folder-0.0.1/src/napari_file2folder.egg-info/dependency_links.txt +1 -0
- napari_file2folder-0.0.1/src/napari_file2folder.egg-info/entry_points.txt +2 -0
- napari_file2folder-0.0.1/src/napari_file2folder.egg-info/requires.txt +17 -0
- napari_file2folder-0.0.1/src/napari_file2folder.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024 Jules Vanaret
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: napari-file2folder
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Save multidimensional file as folder of tifs
|
|
5
|
+
Author: Jules Vanaret
|
|
6
|
+
Author-email: jules.vanaret@univ-amu.fr
|
|
7
|
+
License:
|
|
8
|
+
The MIT License (MIT)
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2024 Jules Vanaret
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in
|
|
20
|
+
all copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
|
+
THE SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
31
|
+
Classifier: Framework :: napari
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
43
|
+
Requires-Python: >=3.9
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Requires-Dist: numpy
|
|
47
|
+
Requires-Dist: qtpy
|
|
48
|
+
Requires-Dist: magicgui
|
|
49
|
+
Requires-Dist: tifffile
|
|
50
|
+
Requires-Dist: bioio
|
|
51
|
+
Requires-Dist: bioio-ome-tiff
|
|
52
|
+
Requires-Dist: bioio-ome-zarr
|
|
53
|
+
Requires-Dist: bioio-nd2
|
|
54
|
+
Requires-Dist: bioio-czi
|
|
55
|
+
Provides-Extra: testing
|
|
56
|
+
Requires-Dist: tox; extra == "testing"
|
|
57
|
+
Requires-Dist: pytest; extra == "testing"
|
|
58
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
59
|
+
Requires-Dist: pytest-qt; extra == "testing"
|
|
60
|
+
Requires-Dist: napari; extra == "testing"
|
|
61
|
+
Requires-Dist: pyqt5; extra == "testing"
|
|
62
|
+
|
|
63
|
+
# napari-file2folder
|
|
64
|
+
|
|
65
|
+
[](https://github.com/jules-vanaret/napari-file2folder/raw/main/LICENSE)
|
|
66
|
+
[](https://pypi.org/project/napari-file2folder)
|
|
67
|
+
[](https://python.org)
|
|
68
|
+
[](https://github.com/jules-vanaret/napari-file2folder/actions)
|
|
69
|
+
[](https://codecov.io/gh/jules-vanaret/napari-file2folder)
|
|
70
|
+
[](https://napari-hub.org/plugins/napari-file2folder)
|
|
71
|
+
|
|
72
|
+
<img src="https://github.com/GuignardLab/tapenade/blob/main/imgs/tapenade3.png" width="100">
|
|
73
|
+
|
|
74
|
+
A plugin to inspect bioimages (e.g. .tif, .czi, .nd2, .lsm...) and save them as individual .tif files in a folder.
|
|
75
|
+
|
|
76
|
+
`napari-file2folder` is a [napari] plugin that is part of the [Tapenade](https://github.com/GuignardLab/tapenade) project. Tapenade is a tool for the analysis of dense 3D tissues acquired with deep imaging microscopy. It is designed to be user-friendly and to provide a comprehensive analysis of the data.
|
|
77
|
+
|
|
78
|
+
If you use this plugin for your research, please [cite us](https://github.com/GuignardLab/tapenade/blob/main/README.md#how-to-cite).
|
|
79
|
+
|
|
80
|
+
## Overview
|
|
81
|
+
|
|
82
|
+
<img src="imgs/napari-file2folder-demo.gif"/>
|
|
83
|
+
|
|
84
|
+
This plugin allows you to inspect (possibly large) bioimages by displaying their shape (number of elements in each dimension), and allowing you to save each element along a chosen dimension as a separate .tif file in a folder. This is useful when you have a large movie or stack of images and you want to save each frame or slice as a separate file. Optionally, the plugin allows the user to visualize the middle element of a given dimension to help the user decide which dimension to save as separate files.
|
|
85
|
+
|
|
86
|
+
The plugin currently supports the following file formats:
|
|
87
|
+
- .tif
|
|
88
|
+
- .ome.tiff
|
|
89
|
+
- .zarr
|
|
90
|
+
- .ome.zarr
|
|
91
|
+
- .nd2
|
|
92
|
+
- .lsm
|
|
93
|
+
- .czi
|
|
94
|
+
|
|
95
|
+
This plugin leverages [tifffile], [bioio], and [zarr] to circumvent loading the entire images in memory, which allows inspection of very large images.
|
|
96
|
+
|
|
97
|
+
> [!CAUTION]
|
|
98
|
+
> When inspecting the middle element of a dimension, or when saving one element of a dimension as a separate file, the plugin loads the element in memory, which means that at least this lone element must fit in memory.
|
|
99
|
+
|
|
100
|
+
## Installation
|
|
101
|
+
|
|
102
|
+
The plugin obviously requires [napari] to run. If you don't have it yet, follow the instructions [here](https://napari.org/stable/tutorials/fundamentals/installation.html).
|
|
103
|
+
|
|
104
|
+
The simplest way to install `napari-file2folder` is via the [napari] plugin manager. Open Napari, go to `Plugins > Install/Uninstall Packages...` and search for `napari-file2folder`. Click on the install button and you are ready to go!
|
|
105
|
+
|
|
106
|
+
You can install `napari-file2folder` via [pip]:
|
|
107
|
+
|
|
108
|
+
pip install napari-file2folder
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## Contributing
|
|
114
|
+
|
|
115
|
+
Contributions are very welcome. Tests can be run with [tox], please ensure
|
|
116
|
+
the coverage at least stays the same before you submit a pull request.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
Distributed under the terms of the [MIT] license,
|
|
121
|
+
"napari-file2folder" is free and open source software
|
|
122
|
+
|
|
123
|
+
## Issues
|
|
124
|
+
|
|
125
|
+
If you encounter any problems, please [file an issue] along with a detailed description.
|
|
126
|
+
|
|
127
|
+
----------------------------------
|
|
128
|
+
|
|
129
|
+
This [napari] plugin was generated with [Cookiecutter] using [@napari]'s [cookiecutter-napari-plugin] template.
|
|
130
|
+
|
|
131
|
+
<!--
|
|
132
|
+
Don't miss the full getting started guide to set up your new package:
|
|
133
|
+
https://github.com/napari/cookiecutter-napari-plugin#getting-started
|
|
134
|
+
|
|
135
|
+
and review the napari docs for plugin developers:
|
|
136
|
+
https://napari.org/stable/plugins/index.html
|
|
137
|
+
-->
|
|
138
|
+
|
|
139
|
+
[napari]: https://github.com/napari/napari
|
|
140
|
+
[Cookiecutter]: https://github.com/audreyr/cookiecutter
|
|
141
|
+
[@napari]: https://github.com/napari
|
|
142
|
+
[MIT]: http://opensource.org/licenses/MIT
|
|
143
|
+
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
|
|
144
|
+
[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt
|
|
145
|
+
[GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
146
|
+
[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
|
|
147
|
+
[Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt
|
|
148
|
+
[cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin
|
|
149
|
+
|
|
150
|
+
[napari]: https://github.com/napari/napari
|
|
151
|
+
[tox]: https://tox.readthedocs.io/en/latest/
|
|
152
|
+
[pip]: https://pypi.org/project/pip/
|
|
153
|
+
[PyPI]: https://pypi.org/
|
|
154
|
+
[tifffile]: https://github.com/cgohlke/tifffile
|
|
155
|
+
[bioio]: https://github.com/bioio-devs/bioio
|
|
156
|
+
[zarr]: https://github.com/zarr-developers/zarr-python
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# napari-file2folder
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jules-vanaret/napari-file2folder/raw/main/LICENSE)
|
|
4
|
+
[](https://pypi.org/project/napari-file2folder)
|
|
5
|
+
[](https://python.org)
|
|
6
|
+
[](https://github.com/jules-vanaret/napari-file2folder/actions)
|
|
7
|
+
[](https://codecov.io/gh/jules-vanaret/napari-file2folder)
|
|
8
|
+
[](https://napari-hub.org/plugins/napari-file2folder)
|
|
9
|
+
|
|
10
|
+
<img src="https://github.com/GuignardLab/tapenade/blob/main/imgs/tapenade3.png" width="100">
|
|
11
|
+
|
|
12
|
+
A plugin to inspect bioimages (e.g. .tif, .czi, .nd2, .lsm...) and save them as individual .tif files in a folder.
|
|
13
|
+
|
|
14
|
+
`napari-file2folder` is a [napari] plugin that is part of the [Tapenade](https://github.com/GuignardLab/tapenade) project. Tapenade is a tool for the analysis of dense 3D tissues acquired with deep imaging microscopy. It is designed to be user-friendly and to provide a comprehensive analysis of the data.
|
|
15
|
+
|
|
16
|
+
If you use this plugin for your research, please [cite us](https://github.com/GuignardLab/tapenade/blob/main/README.md#how-to-cite).
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
<img src="imgs/napari-file2folder-demo.gif"/>
|
|
21
|
+
|
|
22
|
+
This plugin allows you to inspect (possibly large) bioimages by displaying their shape (number of elements in each dimension), and allowing you to save each element along a chosen dimension as a separate .tif file in a folder. This is useful when you have a large movie or stack of images and you want to save each frame or slice as a separate file. Optionally, the plugin allows the user to visualize the middle element of a given dimension to help the user decide which dimension to save as separate files.
|
|
23
|
+
|
|
24
|
+
The plugin currently supports the following file formats:
|
|
25
|
+
- .tif
|
|
26
|
+
- .ome.tiff
|
|
27
|
+
- .zarr
|
|
28
|
+
- .ome.zarr
|
|
29
|
+
- .nd2
|
|
30
|
+
- .lsm
|
|
31
|
+
- .czi
|
|
32
|
+
|
|
33
|
+
This plugin leverages [tifffile], [bioio], and [zarr] to circumvent loading the entire images in memory, which allows inspection of very large images.
|
|
34
|
+
|
|
35
|
+
> [!CAUTION]
|
|
36
|
+
> When inspecting the middle element of a dimension, or when saving one element of a dimension as a separate file, the plugin loads the element in memory, which means that at least this lone element must fit in memory.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
The plugin obviously requires [napari] to run. If you don't have it yet, follow the instructions [here](https://napari.org/stable/tutorials/fundamentals/installation.html).
|
|
41
|
+
|
|
42
|
+
The simplest way to install `napari-file2folder` is via the [napari] plugin manager. Open Napari, go to `Plugins > Install/Uninstall Packages...` and search for `napari-file2folder`. Click on the install button and you are ready to go!
|
|
43
|
+
|
|
44
|
+
You can install `napari-file2folder` via [pip]:
|
|
45
|
+
|
|
46
|
+
pip install napari-file2folder
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Contributing
|
|
52
|
+
|
|
53
|
+
Contributions are very welcome. Tests can be run with [tox], please ensure
|
|
54
|
+
the coverage at least stays the same before you submit a pull request.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
Distributed under the terms of the [MIT] license,
|
|
59
|
+
"napari-file2folder" is free and open source software
|
|
60
|
+
|
|
61
|
+
## Issues
|
|
62
|
+
|
|
63
|
+
If you encounter any problems, please [file an issue] along with a detailed description.
|
|
64
|
+
|
|
65
|
+
----------------------------------
|
|
66
|
+
|
|
67
|
+
This [napari] plugin was generated with [Cookiecutter] using [@napari]'s [cookiecutter-napari-plugin] template.
|
|
68
|
+
|
|
69
|
+
<!--
|
|
70
|
+
Don't miss the full getting started guide to set up your new package:
|
|
71
|
+
https://github.com/napari/cookiecutter-napari-plugin#getting-started
|
|
72
|
+
|
|
73
|
+
and review the napari docs for plugin developers:
|
|
74
|
+
https://napari.org/stable/plugins/index.html
|
|
75
|
+
-->
|
|
76
|
+
|
|
77
|
+
[napari]: https://github.com/napari/napari
|
|
78
|
+
[Cookiecutter]: https://github.com/audreyr/cookiecutter
|
|
79
|
+
[@napari]: https://github.com/napari
|
|
80
|
+
[MIT]: http://opensource.org/licenses/MIT
|
|
81
|
+
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
|
|
82
|
+
[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt
|
|
83
|
+
[GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
84
|
+
[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
|
|
85
|
+
[Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt
|
|
86
|
+
[cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin
|
|
87
|
+
|
|
88
|
+
[napari]: https://github.com/napari/napari
|
|
89
|
+
[tox]: https://tox.readthedocs.io/en/latest/
|
|
90
|
+
[pip]: https://pypi.org/project/pip/
|
|
91
|
+
[PyPI]: https://pypi.org/
|
|
92
|
+
[tifffile]: https://github.com/cgohlke/tifffile
|
|
93
|
+
[bioio]: https://github.com/bioio-devs/bioio
|
|
94
|
+
[zarr]: https://github.com/zarr-developers/zarr-python
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "napari-file2folder"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Save multidimensional file as folder of tifs"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {file = "LICENSE"}
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "Jules Vanaret"},
|
|
9
|
+
{email = "jules.vanaret@univ-amu.fr"},
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
13
|
+
"Framework :: napari",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.9"
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy",
|
|
29
|
+
"qtpy",
|
|
30
|
+
"magicgui",
|
|
31
|
+
"tifffile",
|
|
32
|
+
"bioio",
|
|
33
|
+
"bioio-ome-tiff",
|
|
34
|
+
"bioio-ome-zarr",
|
|
35
|
+
"bioio-nd2",
|
|
36
|
+
"bioio-czi",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
testing = [
|
|
41
|
+
"tox",
|
|
42
|
+
"pytest", # https://docs.pytest.org/en/latest/contents.html
|
|
43
|
+
"pytest-cov", # https://pytest-cov.readthedocs.io/en/latest/
|
|
44
|
+
"pytest-qt", # https://pytest-qt.readthedocs.io/en/latest/
|
|
45
|
+
"napari",
|
|
46
|
+
"pyqt5",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.entry-points."napari.manifest"]
|
|
50
|
+
napari-file2folder = "napari_file2folder:napari.yaml"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["setuptools>=42.0.0", "wheel"]
|
|
56
|
+
build-backend = "setuptools.build_meta"
|
|
57
|
+
|
|
58
|
+
[tool.setuptools]
|
|
59
|
+
include-package-data = true
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.packages.find]
|
|
62
|
+
where = ["src"]
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.package-data]
|
|
65
|
+
"*" = ["*.yaml"]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.dynamic]
|
|
69
|
+
version = {attr = "napari_file2folder.__init__.__version__"}
|
|
70
|
+
|
|
71
|
+
[tool.black]
|
|
72
|
+
line-length = 79
|
|
73
|
+
target-version = ['py38', 'py39', 'py310']
|
|
74
|
+
|
|
75
|
+
[tool.ruff]
|
|
76
|
+
line-length = 79
|
|
77
|
+
lint.select = [
|
|
78
|
+
"E", "F", "W", #flake8
|
|
79
|
+
"UP", # pyupgrade
|
|
80
|
+
"I", # isort
|
|
81
|
+
"BLE", # flake8-blind-exception
|
|
82
|
+
"B", # flake8-bugbear
|
|
83
|
+
"A", # flake8-builtins
|
|
84
|
+
"C4", # flake8-comprehensions
|
|
85
|
+
"ISC", # flake8-implicit-str-concat
|
|
86
|
+
"G", # flake8-logging-format
|
|
87
|
+
"PIE", # flake8-pie
|
|
88
|
+
"SIM", # flake8-simplify
|
|
89
|
+
]
|
|
90
|
+
lint.ignore = [
|
|
91
|
+
"E501", # line too long. let black handle this
|
|
92
|
+
"UP006", "UP007", # type annotation. As using magicgui require runtime type annotation then we disable this.
|
|
93
|
+
"SIM117", # flake8-simplify - some of merged with statements are not looking great with black, reanble after drop python 3.9
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
exclude = [
|
|
97
|
+
".bzr",
|
|
98
|
+
".direnv",
|
|
99
|
+
".eggs",
|
|
100
|
+
".git",
|
|
101
|
+
".mypy_cache",
|
|
102
|
+
".pants.d",
|
|
103
|
+
".ruff_cache",
|
|
104
|
+
".svn",
|
|
105
|
+
".tox",
|
|
106
|
+
".venv",
|
|
107
|
+
"__pypackages__",
|
|
108
|
+
"_build",
|
|
109
|
+
"buck-out",
|
|
110
|
+
"build",
|
|
111
|
+
"dist",
|
|
112
|
+
"node_modules",
|
|
113
|
+
"venv",
|
|
114
|
+
"*vendored*",
|
|
115
|
+
"*_vendor*",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
target-version = "py38"
|
|
119
|
+
fix = true
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from qtpy.QtCore import QPoint, Qt, QRect
|
|
2
|
+
from qtpy.QtWidgets import (
|
|
3
|
+
QApplication,
|
|
4
|
+
QLabel,
|
|
5
|
+
QPushButton,
|
|
6
|
+
)
|
|
7
|
+
from qtpy import QtGui
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HoverTooltipButton(QPushButton):
|
|
11
|
+
def __init__(self, text, parent=None):
|
|
12
|
+
super().__init__(text, parent)
|
|
13
|
+
self.setFixedSize(20, 20) # Small square button
|
|
14
|
+
self.setText("?") # Add interrogation mark inside the button
|
|
15
|
+
self.setCheckable(True) # Toggle button to show/hide the tooltip
|
|
16
|
+
|
|
17
|
+
# Create the custom tooltip as a top-level widget (not a child of the button)
|
|
18
|
+
self.text_box = QLabel()
|
|
19
|
+
self.text_box.setText(text)
|
|
20
|
+
self.text_box.setStyleSheet(
|
|
21
|
+
"background-color: yellow; border: 1px solid black; padding: 5px;"
|
|
22
|
+
)
|
|
23
|
+
self.text_box.setWindowFlags(Qt.ToolTip) # Make it look like a tooltip
|
|
24
|
+
self.text_box.hide()
|
|
25
|
+
|
|
26
|
+
# Enable mouse tracking to track mouse movement inside the button
|
|
27
|
+
self.setMouseTracking(True)
|
|
28
|
+
|
|
29
|
+
def mousePressEvent(self, event):
|
|
30
|
+
if event.button() == Qt.LeftButton:
|
|
31
|
+
if self.isChecked():
|
|
32
|
+
self.setChecked(False) # Uncheck to hide the tooltip
|
|
33
|
+
self.text_box.hide()
|
|
34
|
+
else:
|
|
35
|
+
self.setChecked(True) # Check to show the tooltip
|
|
36
|
+
# move the tooltip to the mouse position
|
|
37
|
+
self.adjust_tooltip_position(event.globalPos())
|
|
38
|
+
self.text_box.show() # Immediately show tooltip on click
|
|
39
|
+
super().mousePressEvent(event)
|
|
40
|
+
|
|
41
|
+
def leaveEvent(self, event):
|
|
42
|
+
# Hide the text box when mouse leaves the button
|
|
43
|
+
self.text_box.hide()
|
|
44
|
+
self.setChecked(False) # Uncheck to hide the tooltip
|
|
45
|
+
super().leaveEvent(event)
|
|
46
|
+
|
|
47
|
+
def mouseMoveEvent(self, event):
|
|
48
|
+
# Update tooltip position to follow the mouse using global coordinates
|
|
49
|
+
if self.isChecked():
|
|
50
|
+
# self.text_box.move(event.globalPos() + QPoint(10, 10)) # Offset for better visibility
|
|
51
|
+
self.adjust_tooltip_position(event.globalPos())
|
|
52
|
+
super().mouseMoveEvent(event)
|
|
53
|
+
|
|
54
|
+
def adjust_tooltip_position(self, cursor_pos):
|
|
55
|
+
screen = QApplication.screenAt(cursor_pos)
|
|
56
|
+
|
|
57
|
+
if screen is not None:
|
|
58
|
+
screen_rect = (
|
|
59
|
+
screen.availableGeometry()
|
|
60
|
+
) # Get the geometry of the screen with the cursor
|
|
61
|
+
else:
|
|
62
|
+
screen_rect = (
|
|
63
|
+
QApplication.desktop().availableGeometry()
|
|
64
|
+
) # Fallback to primary screen
|
|
65
|
+
|
|
66
|
+
# Get the size of the tooltip
|
|
67
|
+
tooltip_size = self.text_box.sizeHint()
|
|
68
|
+
|
|
69
|
+
# Calculate the desired position of the tooltip
|
|
70
|
+
new_x = cursor_pos.x() + 10 # Offset for better visibility
|
|
71
|
+
new_y = cursor_pos.y() + 10
|
|
72
|
+
|
|
73
|
+
# Adjust the position if the tooltip goes beyond the screen's right edge
|
|
74
|
+
if new_x + tooltip_size.width() > screen_rect.right():
|
|
75
|
+
new_x = (
|
|
76
|
+
screen_rect.right() - tooltip_size.width() - 10
|
|
77
|
+
) # Shift left
|
|
78
|
+
|
|
79
|
+
# Adjust the position if the tooltip goes beyond the screen's bottom edge
|
|
80
|
+
if new_y + tooltip_size.height() > screen_rect.bottom():
|
|
81
|
+
new_y = (
|
|
82
|
+
screen_rect.bottom() - tooltip_size.height() - 10
|
|
83
|
+
) # Shift up
|
|
84
|
+
|
|
85
|
+
# Adjust the position if the tooltip goes beyond the screen's left edge
|
|
86
|
+
if new_x < screen_rect.left():
|
|
87
|
+
new_x = screen_rect.left() + 10 # Shift right
|
|
88
|
+
|
|
89
|
+
# Adjust the position if the tooltip goes beyond the screen's top edge
|
|
90
|
+
if new_y < screen_rect.top():
|
|
91
|
+
new_y = screen_rect.top() + 10 # Shift down
|
|
92
|
+
|
|
93
|
+
# Move the tooltip to the new adjusted position
|
|
94
|
+
self.text_box.move(QPoint(new_x, new_y))
|
|
95
|
+
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from qtpy.QtWidgets import QVBoxLayout, QPushButton, QWidget, QLabel, QProgressBar
|
|
5
|
+
from napari_file2folder._custom_widgets import HoverTooltipButton
|
|
6
|
+
from magicgui.widgets import (
|
|
7
|
+
CheckBox,
|
|
8
|
+
ComboBox,
|
|
9
|
+
Container,
|
|
10
|
+
EmptyWidget,
|
|
11
|
+
Label,
|
|
12
|
+
create_widget,
|
|
13
|
+
PushButton,
|
|
14
|
+
)
|
|
15
|
+
import os
|
|
16
|
+
import tifffile
|
|
17
|
+
from bioio import BioImage
|
|
18
|
+
import zarr
|
|
19
|
+
|
|
20
|
+
import napari
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class File2FolderWidget(QWidget):
|
|
24
|
+
|
|
25
|
+
def __init__(self, viewer: "napari.viewer.Viewer"):
|
|
26
|
+
super().__init__()
|
|
27
|
+
|
|
28
|
+
self._viewer = viewer
|
|
29
|
+
|
|
30
|
+
self._array_file_path = create_widget(
|
|
31
|
+
widget_type="FileEdit",
|
|
32
|
+
label="Array file",
|
|
33
|
+
options={"mode": "r"},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self._array_file_path.changed.connect(self._update_dimensions)
|
|
37
|
+
self._array_file_path.changed.connect(self._update_dimension_choices)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
self._refresh_button = create_widget(
|
|
41
|
+
widget_type="PushButton",
|
|
42
|
+
label="Refresh",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
refresh_container = Container(
|
|
46
|
+
widgets=[self._refresh_button],
|
|
47
|
+
labels=False,
|
|
48
|
+
layout="horizontal",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self._add_tooltip_button_to_container(
|
|
52
|
+
refresh_container, "Refresh the dimensions"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self._refresh_button.native.clicked.connect(self._update_dimensions)
|
|
56
|
+
|
|
57
|
+
self._default_shape_text = f"Shape: <a style=color:#D41159;>None</a>"
|
|
58
|
+
self._shape_text = QLabel(self._default_shape_text)
|
|
59
|
+
|
|
60
|
+
self._dimension_choice_combo = create_widget(
|
|
61
|
+
widget_type="ComboBox",
|
|
62
|
+
options={"nullable": False}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
dimension_choice_container = Container(
|
|
66
|
+
widgets=[self._dimension_choice_combo],
|
|
67
|
+
labels=False,
|
|
68
|
+
layout="horizontal",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self._add_tooltip_button_to_container(
|
|
72
|
+
dimension_choice_container,
|
|
73
|
+
"Choose dimension along which to either\n"
|
|
74
|
+
" (i) select the element at the midpoint along the dimension (e.g for inspection)\n"
|
|
75
|
+
"or (ii) save each element as a separate tif file in the provided folder"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
self._load_middle_element_button = create_widget(
|
|
79
|
+
widget_type="PushButton",
|
|
80
|
+
label="Load middle element in Napari",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self._load_middle_element_button.clicked.connect(
|
|
84
|
+
self._load_middle_element
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
middle_elem_button_container = Container(
|
|
88
|
+
widgets=[self._load_middle_element_button],
|
|
89
|
+
labels=False,
|
|
90
|
+
layout="horizontal",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
self._add_tooltip_button_to_container(
|
|
94
|
+
middle_elem_button_container,
|
|
95
|
+
(
|
|
96
|
+
"Load middle element as Napari layer,\n"
|
|
97
|
+
"e.g to check if dimensions match your expectations"
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
###
|
|
102
|
+
self._save_to_folder_path = create_widget(
|
|
103
|
+
widget_type="FileEdit",
|
|
104
|
+
label="Folder path",
|
|
105
|
+
options={"mode": "d"},
|
|
106
|
+
)
|
|
107
|
+
###
|
|
108
|
+
|
|
109
|
+
self._save_to_folder_compress_checkbox = create_widget(
|
|
110
|
+
widget_type="CheckBox",
|
|
111
|
+
label="Compress when saving",
|
|
112
|
+
options={"value": False},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
self._save_to_folder_button = create_widget(
|
|
116
|
+
widget_type="PushButton",
|
|
117
|
+
label="Save elements along dimension to folder",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
self._save_to_folder_button.clicked.connect(
|
|
121
|
+
self._save_to_folder
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
save_to_folder_container = Container(
|
|
125
|
+
widgets=[self._save_to_folder_button],
|
|
126
|
+
labels=False,
|
|
127
|
+
layout="horizontal",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
self._add_tooltip_button_to_container(
|
|
131
|
+
save_to_folder_container,
|
|
132
|
+
(
|
|
133
|
+
"Save all elements of the specified dimension\n"
|
|
134
|
+
"independently to a folder as tifs."
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
self._progress_bar = QProgressBar()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
self.setLayout(QVBoxLayout())
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# self.layout().addWidget(self._array_layer_combo.native)
|
|
145
|
+
self.layout().addWidget(QLabel("<u>Select path to tif file:</u>"))
|
|
146
|
+
self.layout().addWidget(self._array_file_path.native)
|
|
147
|
+
self.layout().addWidget(refresh_container.native)
|
|
148
|
+
self.layout().addWidget(QLabel(f"Dimensions of currently selected layer:"))
|
|
149
|
+
self.layout().addWidget(self._shape_text)
|
|
150
|
+
self.layout().addWidget(QLabel(""))
|
|
151
|
+
|
|
152
|
+
self.layout().addWidget(QLabel("<u>Select dimension:</u>"))
|
|
153
|
+
self.layout().addWidget(dimension_choice_container.native)
|
|
154
|
+
self.layout().addWidget(QLabel(""))
|
|
155
|
+
|
|
156
|
+
self.layout().addWidget(QLabel("<u>(optional) Inspect middle element:</u>"))
|
|
157
|
+
self.layout().addWidget(middle_elem_button_container.native)
|
|
158
|
+
self.layout().addWidget(QLabel(""))
|
|
159
|
+
|
|
160
|
+
self.layout().addWidget(QLabel("<u>Select path where the folder will be created:</u>"))
|
|
161
|
+
# self.layout().addWidget(QLabel(f"Path at which to create folder for saving:"))
|
|
162
|
+
self.layout().addWidget(self._save_to_folder_path.native)
|
|
163
|
+
self.layout().addWidget(self._save_to_folder_compress_checkbox.native)
|
|
164
|
+
self.layout().addWidget(save_to_folder_container.native)
|
|
165
|
+
self.layout().addWidget(self._progress_bar)
|
|
166
|
+
|
|
167
|
+
# self._update_layer_combos()
|
|
168
|
+
self._update_dimensions()
|
|
169
|
+
self._update_dimension_choices()
|
|
170
|
+
|
|
171
|
+
self.layout().addStretch(1)
|
|
172
|
+
|
|
173
|
+
def _save_to_folder(self):
|
|
174
|
+
save_path = self._save_to_folder_path.value
|
|
175
|
+
if str(save_path) != "." and os.path.isdir(save_path):
|
|
176
|
+
path = self._array_file_path.value
|
|
177
|
+
if str(path) != "." and os.path.isfile(path):
|
|
178
|
+
|
|
179
|
+
dimension_index, dimension_shape = self._dimension_index_from_str(
|
|
180
|
+
self._dimension_choice_combo.value
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
path_to_folder = f"{save_path}/{path.stem}_dim{dimension_index}"
|
|
184
|
+
self._create_folder_if_needed(path_to_folder)
|
|
185
|
+
|
|
186
|
+
compress_params = {}
|
|
187
|
+
if self._save_to_folder_compress_checkbox.value:
|
|
188
|
+
compress_params.update({"compression": ("zlib", 1)})
|
|
189
|
+
|
|
190
|
+
self._lazy_save_slices(
|
|
191
|
+
slice_dim=dimension_index,
|
|
192
|
+
file=str(path),
|
|
193
|
+
path_to_save=path_to_folder,
|
|
194
|
+
compress_args=compress_params
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self._progress_bar.reset()
|
|
198
|
+
|
|
199
|
+
napari.utils.notifications.show_info(
|
|
200
|
+
f"Finished saving files!\n"
|
|
201
|
+
f"Saved to {path_to_folder}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _create_folder_if_needed(self, folder_path):
|
|
207
|
+
if not os.path.exists(folder_path):
|
|
208
|
+
os.makedirs(folder_path)
|
|
209
|
+
|
|
210
|
+
def _update_dimension_choices(self):
|
|
211
|
+
path = self._array_file_path.value
|
|
212
|
+
if str(path) != "." and os.path.isfile(path):
|
|
213
|
+
shape = self._lazy_shape(path)
|
|
214
|
+
dimensions_as_str = [f"dim {i} ({s})" for i,s in enumerate(shape)]
|
|
215
|
+
self._dimension_choice_combo.choices = dimensions_as_str
|
|
216
|
+
else:
|
|
217
|
+
self._dimension_choice_combo.choices = ["None"]
|
|
218
|
+
|
|
219
|
+
def _dimension_index_from_str(self, string):
|
|
220
|
+
dim_index = int(string.split(' ')[1])
|
|
221
|
+
dim_shape = int(string.split(' ')[2][1:-1])
|
|
222
|
+
|
|
223
|
+
return dim_index, dim_shape
|
|
224
|
+
|
|
225
|
+
def _load_middle_element(self):
|
|
226
|
+
path = self._array_file_path.value
|
|
227
|
+
if str(path) != "." and os.path.isfile(path):
|
|
228
|
+
dimension_index, dimension_shape = self._dimension_index_from_str(
|
|
229
|
+
self._dimension_choice_combo.value
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
middle_slice = self._lazy_grab_slice(
|
|
233
|
+
element_index=int(dimension_shape/2),
|
|
234
|
+
slice_dim=dimension_index,
|
|
235
|
+
file=str(path)
|
|
236
|
+
)
|
|
237
|
+
if middle_slice is None:
|
|
238
|
+
napari.utils.notifications.show_warning(
|
|
239
|
+
"Please choose a compatible TIF file"
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
self._viewer.add_image(middle_slice)
|
|
243
|
+
else:
|
|
244
|
+
napari.utils.notifications.show_warning(
|
|
245
|
+
"Please choose a compatible TIF file"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _lazy_save_slices(self, slice_dim: int, file: str, path_to_save: str,
|
|
250
|
+
compress_args: dict = {}):
|
|
251
|
+
"""
|
|
252
|
+
Lazily slice a multidimensional file along a specified dimension.
|
|
253
|
+
|
|
254
|
+
Parameters:
|
|
255
|
+
- file: path to the image file.
|
|
256
|
+
- slice_dim: the dimension (axis) along which to slice.
|
|
257
|
+
- path_to_save: path to the folder where to save the slices.
|
|
258
|
+
- compress_args: dictionary of arguments to pass to tifffile.imwrite.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
- The element of the sliced array at the specified index.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
file = str(file)
|
|
265
|
+
if file.endswith(".tif") or file.endswith(".tiff") or file.endswith("lsm"):
|
|
266
|
+
with tifffile.TiffFile(file) as tif:
|
|
267
|
+
shape = tif.series[0].shape
|
|
268
|
+
|
|
269
|
+
slices = [slice(None) for _ in range(len(shape))]
|
|
270
|
+
|
|
271
|
+
zarr_store = tif.series[0].aszarr()
|
|
272
|
+
zarr_array = zarr.open(zarr_store)
|
|
273
|
+
|
|
274
|
+
for element_index in range(shape[slice_dim]):
|
|
275
|
+
slices[slice_dim] = element_index
|
|
276
|
+
|
|
277
|
+
stack = zarr_array[tuple(slices)]
|
|
278
|
+
|
|
279
|
+
name_tif = file.split(os.sep)[-1].split('.')[0]
|
|
280
|
+
|
|
281
|
+
tifffile.imwrite(
|
|
282
|
+
f"{path_to_save}/{name_tif}_slice{element_index:03d}.tif",
|
|
283
|
+
stack,
|
|
284
|
+
**compress_args
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
self._progress_bar.setValue(
|
|
288
|
+
int((element_index+1)/shape[slice_dim]*100)
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
img = BioImage(file)
|
|
292
|
+
shape = tuple([elem for elem in img.shape if elem != 1])
|
|
293
|
+
lazy_array = img.dask_data.reshape(shape)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
slices = [slice(None) for _ in range(len(shape))]
|
|
297
|
+
|
|
298
|
+
for element_index in range(shape[slice_dim]):
|
|
299
|
+
slices[slice_dim] = element_index
|
|
300
|
+
|
|
301
|
+
stack = lazy_array[tuple(slices)].compute()
|
|
302
|
+
|
|
303
|
+
name_tif = file.split(os.sep)[-1].split('.')[0]
|
|
304
|
+
|
|
305
|
+
tifffile.imwrite(
|
|
306
|
+
f"{path_to_save}/{name_tif}_slice{element_index:03d}.tif",
|
|
307
|
+
stack,
|
|
308
|
+
**compress_args
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
self._progress_bar.setValue(
|
|
312
|
+
int((element_index+1)/shape[slice_dim]*100)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _lazy_grab_slice(self, slice_dim, file, element_index: int = None):
|
|
320
|
+
"""
|
|
321
|
+
Lazily slice a multidimensional file along a specified dimension.
|
|
322
|
+
|
|
323
|
+
Parameters:
|
|
324
|
+
- file: path to the image file.
|
|
325
|
+
- slice_dim: the dimension (axis) along which to slice.
|
|
326
|
+
- element_index: the index of the element to retrieve along the slice dimension.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
- The element of the sliced array at the specified index.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
file = str(file)
|
|
333
|
+
if file.endswith(".tif") or file.endswith(".tiff") or file.endswith("lsm"):
|
|
334
|
+
with tifffile.TiffFile(file) as tif:
|
|
335
|
+
shape = tif.series[0].shape
|
|
336
|
+
slices = [slice(None) for _ in range(len(shape))]
|
|
337
|
+
zarr_array = zarr.open(tif.series[0].aszarr())
|
|
338
|
+
slices[slice_dim] = element_index
|
|
339
|
+
stack = zarr_array[tuple(slices)]
|
|
340
|
+
|
|
341
|
+
else:
|
|
342
|
+
img = BioImage(file)
|
|
343
|
+
shape = tuple([elem for elem in img.shape if elem != 1])
|
|
344
|
+
lazy_array = img.dask_data.reshape(shape)
|
|
345
|
+
|
|
346
|
+
slices = [slice(None) for _ in range(len(shape))]
|
|
347
|
+
|
|
348
|
+
slices[slice_dim] = element_index
|
|
349
|
+
|
|
350
|
+
stack = lazy_array[tuple(slices)].compute()
|
|
351
|
+
|
|
352
|
+
return stack
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _lazy_shape(self, file):
|
|
356
|
+
"""
|
|
357
|
+
Lazily retrieve the shape of a multidimensional file.
|
|
358
|
+
|
|
359
|
+
Parameters:
|
|
360
|
+
- file: path to the image file.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
- The shape of the image file.
|
|
364
|
+
"""
|
|
365
|
+
file = str(file)
|
|
366
|
+
if file.endswith(".tif") or file.endswith(".tiff") or file.endswith("lsm"):
|
|
367
|
+
with tifffile.TiffFile(file) as tif:
|
|
368
|
+
shape = tif.series[0].shape
|
|
369
|
+
else:
|
|
370
|
+
img = BioImage(file)
|
|
371
|
+
shape = tuple([elem for elem in img.shape if elem != 1])
|
|
372
|
+
|
|
373
|
+
return shape
|
|
374
|
+
|
|
375
|
+
def _update_dimensions(self):
|
|
376
|
+
# layer = self._array_layer_combo.value
|
|
377
|
+
path = self._array_file_path.value
|
|
378
|
+
if str(path) != "." and os.path.isfile(path):
|
|
379
|
+
shape = self._lazy_shape(path)
|
|
380
|
+
|
|
381
|
+
self._shape_text.setText(f"Shape: <a style=color:#1A85FF;>{shape}</a>")
|
|
382
|
+
else:
|
|
383
|
+
self._shape_text.setText(self._default_shape_text)
|
|
384
|
+
|
|
385
|
+
def _bind_layer_combo(self, obj):
|
|
386
|
+
"""
|
|
387
|
+
This used so that when calling layer_combo.value, we get the layer object,
|
|
388
|
+
not the name of the layer
|
|
389
|
+
"""
|
|
390
|
+
name = obj.native.currentText()
|
|
391
|
+
if name not in ("", "-----"):
|
|
392
|
+
return self._viewer.layers[name]
|
|
393
|
+
else:
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
def _update_layer_combos(self):
|
|
397
|
+
|
|
398
|
+
### 1. Clear all combos but keep the previous choice if possible
|
|
399
|
+
previous_text = self._array_layer_combo.native.currentText()
|
|
400
|
+
self._array_layer_combo.native.clear()
|
|
401
|
+
|
|
402
|
+
### 2. Add layers to combos
|
|
403
|
+
# add layers to compatible combos
|
|
404
|
+
for layer in self._viewer.layers:
|
|
405
|
+
if (
|
|
406
|
+
isinstance(layer, napari.layers.Image | napari.layers.Labels)
|
|
407
|
+
and self._array_layer_combo.enabled
|
|
408
|
+
):
|
|
409
|
+
self._array_layer_combo.native.addItem(layer.name)
|
|
410
|
+
|
|
411
|
+
### 3. Reset combo current choice to previous text if possible
|
|
412
|
+
all_choices = [
|
|
413
|
+
self._array_layer_combo.native.itemText(i) for i in range(self._array_layer_combo.native.count())
|
|
414
|
+
]
|
|
415
|
+
if previous_text in all_choices:
|
|
416
|
+
|
|
417
|
+
# if the previous layer is None, set it to the newest layer
|
|
418
|
+
if previous_text == self._array_layer_combo.native.itemText(0):
|
|
419
|
+
self._array_layer_combo.native.setCurrentIndex(self._array_layer_combo.native.count() - 1)
|
|
420
|
+
else:
|
|
421
|
+
self._array_layer_combo.native.setCurrentText(previous_text)
|
|
422
|
+
else:
|
|
423
|
+
self._array_layer_combo.native.setCurrentIndex(0)
|
|
424
|
+
|
|
425
|
+
def _add_tooltip_button_to_container(self, container, tooltip_text):
|
|
426
|
+
button = HoverTooltipButton(tooltip_text)
|
|
427
|
+
button.native = button
|
|
428
|
+
button._explicitly_hidden = False
|
|
429
|
+
button.name = ""
|
|
430
|
+
|
|
431
|
+
if isinstance(container, Container):
|
|
432
|
+
container.append(button)
|
|
433
|
+
else:
|
|
434
|
+
if isinstance(container, CheckBox | PushButton):
|
|
435
|
+
container = Container(
|
|
436
|
+
widgets=[container, button],
|
|
437
|
+
labels=False,
|
|
438
|
+
layout="horizontal",
|
|
439
|
+
)
|
|
440
|
+
else:
|
|
441
|
+
container_label = container.label
|
|
442
|
+
container.label = ""
|
|
443
|
+
container = Container(
|
|
444
|
+
widgets=[Label(value=container_label), container, button],
|
|
445
|
+
labels=False,
|
|
446
|
+
layout="horizontal",
|
|
447
|
+
)
|
|
448
|
+
return container
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
if __name__ == "__main__":
|
|
453
|
+
import napari
|
|
454
|
+
|
|
455
|
+
viewer = napari.Viewer()
|
|
456
|
+
widget = File2FolderWidget(viewer)
|
|
457
|
+
viewer.window.add_dock_widget(widget)
|
|
458
|
+
|
|
459
|
+
napari.run()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: napari-file2folder
|
|
2
|
+
display_name: Save multidimensional file as folder of tifs
|
|
3
|
+
# use 'hidden' to remove plugin from napari hub search results
|
|
4
|
+
visibility: public
|
|
5
|
+
# see https://napari.org/stable/plugins/manifest.html for valid categories
|
|
6
|
+
categories: ["Annotation", "Segmentation", "Acquisition"]
|
|
7
|
+
contributions:
|
|
8
|
+
commands:
|
|
9
|
+
- id: napari-file2folder.make_qwidget
|
|
10
|
+
python_name: napari_file2folder:File2FolderWidget
|
|
11
|
+
title: Save multidimensional file as folder of tifs
|
|
12
|
+
widgets:
|
|
13
|
+
- command: napari-file2folder.make_qwidget
|
|
14
|
+
display_name: Save multidimensional file as folder of tifs
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: napari-file2folder
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Save multidimensional file as folder of tifs
|
|
5
|
+
Author: Jules Vanaret
|
|
6
|
+
Author-email: jules.vanaret@univ-amu.fr
|
|
7
|
+
License:
|
|
8
|
+
The MIT License (MIT)
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2024 Jules Vanaret
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in
|
|
20
|
+
all copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
|
+
THE SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
31
|
+
Classifier: Framework :: napari
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
43
|
+
Requires-Python: >=3.9
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Requires-Dist: numpy
|
|
47
|
+
Requires-Dist: qtpy
|
|
48
|
+
Requires-Dist: magicgui
|
|
49
|
+
Requires-Dist: tifffile
|
|
50
|
+
Requires-Dist: bioio
|
|
51
|
+
Requires-Dist: bioio-ome-tiff
|
|
52
|
+
Requires-Dist: bioio-ome-zarr
|
|
53
|
+
Requires-Dist: bioio-nd2
|
|
54
|
+
Requires-Dist: bioio-czi
|
|
55
|
+
Provides-Extra: testing
|
|
56
|
+
Requires-Dist: tox; extra == "testing"
|
|
57
|
+
Requires-Dist: pytest; extra == "testing"
|
|
58
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
59
|
+
Requires-Dist: pytest-qt; extra == "testing"
|
|
60
|
+
Requires-Dist: napari; extra == "testing"
|
|
61
|
+
Requires-Dist: pyqt5; extra == "testing"
|
|
62
|
+
|
|
63
|
+
# napari-file2folder
|
|
64
|
+
|
|
65
|
+
[](https://github.com/jules-vanaret/napari-file2folder/raw/main/LICENSE)
|
|
66
|
+
[](https://pypi.org/project/napari-file2folder)
|
|
67
|
+
[](https://python.org)
|
|
68
|
+
[](https://github.com/jules-vanaret/napari-file2folder/actions)
|
|
69
|
+
[](https://codecov.io/gh/jules-vanaret/napari-file2folder)
|
|
70
|
+
[](https://napari-hub.org/plugins/napari-file2folder)
|
|
71
|
+
|
|
72
|
+
<img src="https://github.com/GuignardLab/tapenade/blob/main/imgs/tapenade3.png" width="100">
|
|
73
|
+
|
|
74
|
+
A plugin to inspect bioimages (e.g. .tif, .czi, .nd2, .lsm...) and save them as individual .tif files in a folder.
|
|
75
|
+
|
|
76
|
+
`napari-file2folder` is a [napari] plugin that is part of the [Tapenade](https://github.com/GuignardLab/tapenade) project. Tapenade is a tool for the analysis of dense 3D tissues acquired with deep imaging microscopy. It is designed to be user-friendly and to provide a comprehensive analysis of the data.
|
|
77
|
+
|
|
78
|
+
If you use this plugin for your research, please [cite us](https://github.com/GuignardLab/tapenade/blob/main/README.md#how-to-cite).
|
|
79
|
+
|
|
80
|
+
## Overview
|
|
81
|
+
|
|
82
|
+
<img src="imgs/napari-file2folder-demo.gif"/>
|
|
83
|
+
|
|
84
|
+
This plugin allows you to inspect (possibly large) bioimages by displaying their shape (number of elements in each dimension), and allowing you to save each element along a chosen dimension as a separate .tif file in a folder. This is useful when you have a large movie or stack of images and you want to save each frame or slice as a separate file. Optionally, the plugin allows the user to visualize the middle element of a given dimension to help the user decide which dimension to save as separate files.
|
|
85
|
+
|
|
86
|
+
The plugin currently supports the following file formats:
|
|
87
|
+
- .tif
|
|
88
|
+
- .ome.tiff
|
|
89
|
+
- .zarr
|
|
90
|
+
- .ome.zarr
|
|
91
|
+
- .nd2
|
|
92
|
+
- .lsm
|
|
93
|
+
- .czi
|
|
94
|
+
|
|
95
|
+
This plugin leverages [tifffile], [bioio], and [zarr] to circumvent loading the entire images in memory, which allows inspection of very large images.
|
|
96
|
+
|
|
97
|
+
> [!CAUTION]
|
|
98
|
+
> When inspecting the middle element of a dimension, or when saving one element of a dimension as a separate file, the plugin loads the element in memory, which means that at least this lone element must fit in memory.
|
|
99
|
+
|
|
100
|
+
## Installation
|
|
101
|
+
|
|
102
|
+
The plugin obviously requires [napari] to run. If you don't have it yet, follow the instructions [here](https://napari.org/stable/tutorials/fundamentals/installation.html).
|
|
103
|
+
|
|
104
|
+
The simplest way to install `napari-file2folder` is via the [napari] plugin manager. Open Napari, go to `Plugins > Install/Uninstall Packages...` and search for `napari-file2folder`. Click on the install button and you are ready to go!
|
|
105
|
+
|
|
106
|
+
You can install `napari-file2folder` via [pip]:
|
|
107
|
+
|
|
108
|
+
pip install napari-file2folder
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## Contributing
|
|
114
|
+
|
|
115
|
+
Contributions are very welcome. Tests can be run with [tox], please ensure
|
|
116
|
+
the coverage at least stays the same before you submit a pull request.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
Distributed under the terms of the [MIT] license,
|
|
121
|
+
"napari-file2folder" is free and open source software
|
|
122
|
+
|
|
123
|
+
## Issues
|
|
124
|
+
|
|
125
|
+
If you encounter any problems, please [file an issue] along with a detailed description.
|
|
126
|
+
|
|
127
|
+
----------------------------------
|
|
128
|
+
|
|
129
|
+
This [napari] plugin was generated with [Cookiecutter] using [@napari]'s [cookiecutter-napari-plugin] template.
|
|
130
|
+
|
|
131
|
+
<!--
|
|
132
|
+
Don't miss the full getting started guide to set up your new package:
|
|
133
|
+
https://github.com/napari/cookiecutter-napari-plugin#getting-started
|
|
134
|
+
|
|
135
|
+
and review the napari docs for plugin developers:
|
|
136
|
+
https://napari.org/stable/plugins/index.html
|
|
137
|
+
-->
|
|
138
|
+
|
|
139
|
+
[napari]: https://github.com/napari/napari
|
|
140
|
+
[Cookiecutter]: https://github.com/audreyr/cookiecutter
|
|
141
|
+
[@napari]: https://github.com/napari
|
|
142
|
+
[MIT]: http://opensource.org/licenses/MIT
|
|
143
|
+
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
|
|
144
|
+
[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt
|
|
145
|
+
[GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
146
|
+
[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
|
|
147
|
+
[Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt
|
|
148
|
+
[cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin
|
|
149
|
+
|
|
150
|
+
[napari]: https://github.com/napari/napari
|
|
151
|
+
[tox]: https://tox.readthedocs.io/en/latest/
|
|
152
|
+
[pip]: https://pypi.org/project/pip/
|
|
153
|
+
[PyPI]: https://pypi.org/
|
|
154
|
+
[tifffile]: https://github.com/cgohlke/tifffile
|
|
155
|
+
[bioio]: https://github.com/bioio-devs/bioio
|
|
156
|
+
[zarr]: https://github.com/zarr-developers/zarr-python
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/napari_file2folder/__init__.py
|
|
6
|
+
src/napari_file2folder/_custom_widgets.py
|
|
7
|
+
src/napari_file2folder/_widget.py
|
|
8
|
+
src/napari_file2folder/napari.yaml
|
|
9
|
+
src/napari_file2folder.egg-info/PKG-INFO
|
|
10
|
+
src/napari_file2folder.egg-info/SOURCES.txt
|
|
11
|
+
src/napari_file2folder.egg-info/dependency_links.txt
|
|
12
|
+
src/napari_file2folder.egg-info/entry_points.txt
|
|
13
|
+
src/napari_file2folder.egg-info/requires.txt
|
|
14
|
+
src/napari_file2folder.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
napari_file2folder
|