Repository: fractal-analytics-platform/fractal-tasks-core (language: python)

Issue:
`zenodo_images_multiplex_shifted` fixture fails with Pillow error
See https://productionresultssa6.blob.core.windows.net/actions-results/7fba96e7-3c3c-4b40-a726-d48a32af5f1d/workflow-job-run-4c309e78-7174-5e1c-e1d6-a7c065a0787a/logs/job/job-logs.txt?rsct=text%2Fplain&se=2024-04-02T10%3A22%3A56Z&sig=hJTfwP2lSuZQCK8ICQaUIAgJeNxbrvZt7XdPNDLsR70%3D&sp=r&spr=https&sr=b&st=2024-04-02T10%3A12%3A51Z&sv=2021-12-02.

This was part of the PR #673, but I think the same would happen in `main` - since we are not pinning Pillow to any specific version.

The first guess here is that this is due to pillow 10.3.0, released yesterday (April 1st).

```
2024-04-02T09:18:52.0051216Z _______________ ERROR at setup of test_multiplexing_registration _______________
2024-04-02T09:18:52.0052042Z 
2024-04-02T09:18:52.0054472Z zenodo_images_multiplex = ['/home/runner/work/fractal-tasks-core/fractal-tasks-core/tests/data/fake_multiplex/cycle1', '/home/runner/work/fractal-tasks-core/fractal-tasks-core/tests/data/fake_multiplex/cycle2']
2024-04-02T09:18:52.0056293Z testdata_path = PosixPath('/home/runner/work/fractal-tasks-core/fractal-tasks-core/tests/data')
2024-04-02T09:18:52.0056868Z 
2024-04-02T09:18:52.0057008Z     @pytest.fixture(scope="function")
2024-04-02T09:18:52.0057413Z     def zenodo_images_multiplex_shifted(
2024-04-02T09:18:52.0057831Z         zenodo_images_multiplex: list[str],
2024-04-02T09:18:52.0058225Z         testdata_path: Path,
2024-04-02T09:18:52.0058566Z     ) -> list[str]:
2024-04-02T09:18:52.0058818Z         """
2024-04-02T09:18:52.0059080Z         Return a list of strings, like
2024-04-02T09:18:52.0059437Z         ```
2024-04-02T09:18:52.0059662Z         [
2024-04-02T09:18:52.0059957Z             "/some/path/fake_multiplex_shifted/cycle1",
2024-04-02T09:18:52.0060415Z             "/some/path/fake_multiplex_shifted/cycle2"
2024-04-02T09:18:52.0060810Z         ]
2024-04-02T09:18:52.0061032Z         ```
2024-04-02T09:18:52.0061253Z         """
2024-04-02T09:18:52.0061510Z         # Define old and new folders
2024-04-02T09:18:52.0061943Z         old_folder = str(testdata_path / "fake_multiplex")
2024-04-02T09:18:52.0062573Z         new_folder = old_folder.replace("fake_multiplex", "fake_multiplex_shifted")
2024-04-02T09:18:52.0063110Z     
2024-04-02T09:18:52.0063432Z         # Define output folders (one per multiplexing cycle)
2024-04-02T09:18:52.0064013Z         cycle_folders = [f"{new_folder}/cycle{ind}" for ind in (1, 2)]
2024-04-02T09:18:52.0064475Z     
2024-04-02T09:18:52.0064725Z         if os.path.isdir(new_folder):
2024-04-02T09:18:52.0065498Z             # If the shifted-images folder already exists, return immediately
2024-04-02T09:18:52.0066008Z             print(f"{new_folder} already exists")
2024-04-02T09:18:52.0066376Z             return cycle_folders
2024-04-02T09:18:52.0066660Z         else:
2024-04-02T09:18:52.0067228Z             # Copy the fake_multiplex folder into a new one
2024-04-02T09:18:52.0067991Z             shutil.copytree(old_folder, new_folder)
2024-04-02T09:18:52.0068712Z             # Loop over images of cycle2 and apply a shift
2024-04-02T09:18:52.0069541Z             for img_path in glob.glob(f"{cycle_folders[1]}/2020*.png"):
2024-04-02T09:18:52.0070317Z                 print(f"Now shifting {img_path}")
2024-04-02T09:18:52.0070950Z >               _shift_image(str(img_path))
2024-04-02T09:18:52.0071378Z 
2024-04-02T09:18:52.0071585Z tests/tasks/test_registration.py:123: 
2024-04-02T09:18:52.0072321Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2024-04-02T09:18:52.0073168Z tests/tasks/test_registration.py:88: in _shift_image
2024-04-02T09:18:52.0073920Z     new_img = Image.fromarray(new_array, mode="I")
2024-04-02T09:18:52.0074903Z .venv/lib/python3.10/site-packages/PIL/Image.py:3154: in fromarray
2024-04-02T09:18:52.0075699Z     return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
2024-04-02T09:18:52.0076768Z .venv/lib/python3.10/site-packages/PIL/Image.py:3069: in frombuffer
2024-04-02T09:18:52.0077688Z     return frombytes(mode, size, data, decoder_name, args)
2024-04-02T09:18:52.0078691Z .venv/lib/python3.10/site-packages/PIL/Image.py:3012: in frombytes
2024-04-02T09:18:52.0079498Z     im.frombytes(data, decoder_name, args)
2024-04-02T09:18:52.0080246Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2024-04-02T09:18:52.0081072Z 
2024-04-02T09:18:52.0081462Z self = <PIL.Image.Image image mode=I size=2560x2160 at 0x7F7221DFDE70>
2024-04-02T09:18:52.0082302Z data = array([[103, 106, 112, ..., 110, 110, 110],
2024-04-02T09:18:52.0082934Z        [105, 100, 104, ..., 110, 110, 110],
2024-04-02T09:18:52.0083535Z        [108, 103,  98, ..., 11... 110, 110, 110],
2024-04-02T09:18:52.0084121Z        [110, 110, 110, ..., 110, 110, 110],
2024-04-02T09:18:52.0084757Z        [110, 110, 110, ..., 110, 110, 110]], dtype=uint16)
2024-04-02T09:18:52.0085498Z decoder_name = 'raw', args = ('I', 0, 1)
2024-04-02T09:18:52.0086245Z d = <ImagingDecoder object at 0x7f7221e7d7d0>, s = (11059200, 0)
2024-04-02T09:18:52.0087044Z msg = 'not enough image data'
2024-04-02T09:18:52.0087382Z 
2024-04-02T09:18:52.0087937Z     def frombytes(self, data: bytes, decoder_name: str = "raw", *args) -> None:
2024-04-02T09:18:52.0088749Z         """
2024-04-02T09:18:52.0089258Z         Loads this image with pixel data from a bytes object.
2024-04-02T09:18:52.0089921Z     
2024-04-02T09:18:52.0090549Z         This method is similar to the :py:func:`~PIL.Image.frombytes` function,
2024-04-02T09:18:52.0091631Z         but loads data into this image instead of creating a new image object.
2024-04-02T09:18:52.0092428Z         """
2024-04-02T09:18:52.0092770Z     
2024-04-02T09:18:52.0093193Z         if self.width == 0 or self.height == 0:
2024-04-02T09:18:52.0093778Z             return
2024-04-02T09:18:52.0094137Z     
2024-04-02T09:18:52.0094551Z         # may pass tuple instead of argument list
2024-04-02T09:18:52.0095324Z         if len(args) == 1 and isinstance(args[0], tuple):
2024-04-02T09:18:52.0095972Z             args = args[0]
2024-04-02T09:18:52.0096394Z     
2024-04-02T09:18:52.0096726Z         # default format
2024-04-02T09:18:52.0097238Z         if decoder_name == "raw" and args == ():
2024-04-02T09:18:52.0097839Z             args = self.mode
2024-04-02T09:18:52.0098277Z     
2024-04-02T09:18:52.0098612Z         # unpack data
2024-04-02T09:18:52.0099129Z         d = _getdecoder(self.mode, decoder_name, args)
2024-04-02T09:18:52.0099764Z         d.setimage(self.im)
2024-04-02T09:18:52.0100229Z         s = d.decode(data)
2024-04-02T09:18:52.0100654Z     
2024-04-02T09:18:52.0100977Z         if s[0] >= 0:
2024-04-02T09:18:52.0101618Z             msg = "not enough image data"
2024-04-02T09:18:52.0102203Z >           raise ValueError(msg)
2024-04-02T09:18:52.0102774Z E           ValueError: not enough image data
2024-04-02T09:18:52.0103214Z 
2024-04-02T09:18:52.0103695Z .venv/lib/python3.10/site-packages/PIL/Image.py:830: ValueError
```
Re-enable manifest validation test
Requires:
* https://github.com/fractal-analytics-platform/fractal-server/pull/1355


Discussion / hints:
I'm switching to using imageio for the file handling, I think that's working fine. Will do further tests to confirm it


Fix patch:
diff --git a/fractal_tasks_core/__FRACTAL_MANIFEST__.json b/fractal_tasks_core/__FRACTAL_MANIFEST__.json
index 0bbf34dcc..32d359420 100644
--- a/fractal_tasks_core/__FRACTAL_MANIFEST__.json
+++ b/fractal_tasks_core/__FRACTAL_MANIFEST__.json
@@ -1,79 +1,163 @@
 {
+  "args_schema_version": "pydantic_v1",
+  "has_args_schemas": true,
   "manifest_version": "2",
   "task_list": [
     {
-      "name": "Convert Cellvoyager to OME-Zarr",
-      "executable_non_parallel": "tasks/cellvoyager_to_ome_zarr_init.py",
-      "executable_parallel": "tasks/cellvoyager_to_ome_zarr_compute.py",
       "args_schema_non_parallel": {
-        "title": "CellvoyagerToOmeZarrInit",
-        "type": "object",
+        "additionalProperties": false,
+        "definitions": {
+          "OmeroChannel": {
+            "description": "Custom class for Omero channels, based on OME-NGFF v0.4.",
+            "properties": {
+              "active": {
+                "default": true,
+                "description": "Should this channel be shown in the viewer?",
+                "title": "Active",
+                "type": "boolean"
+              },
+              "coefficient": {
+                "default": 1,
+                "description": "Do not change. Omero-channel attribute.",
+                "title": "Coefficient",
+                "type": "integer"
+              },
+              "color": {
+                "description": "Optional hex colormap to display the channel in napari (it must be of length 6, e.g. `00FFFF`).",
+                "title": "Color",
+                "type": "string"
+              },
+              "index": {
+                "description": "Do not change. For internal use only.",
+                "title": "Index",
+                "type": "integer"
+              },
+              "inverted": {
+                "default": false,
+                "description": "Do not change. Omero-channel attribute.",
+                "title": "Inverted",
+                "type": "boolean"
+              },
+              "label": {
+                "description": "Name of the channel.",
+                "title": "Label",
+                "type": "string"
+              },
+              "wavelength_id": {
+                "description": "Unique ID for the channel wavelength, e.g. `A01_C01`.",
+                "title": "Wavelength Id",
+                "type": "string"
+              },
+              "window": {
+                "$ref": "#/definitions/Window",
+                "description": "Optional `Window` object to set default display settings for napari.",
+                "title": "Window"
+              }
+            },
+            "required": [
+              "wavelength_id"
+            ],
+            "title": "OmeroChannel",
+            "type": "object"
+          },
+          "Window": {
+            "description": "Custom class for Omero-channel window, based on OME-NGFF v0.4.",
+            "properties": {
+              "end": {
+                "description": "Upper-bound rescaling value for visualization.",
+                "title": "End",
+                "type": "integer"
+              },
+              "max": {
+                "description": "Do not change. It will be set according to bit-depth of the images by default (e.g. 65535 for 16 bit images).",
+                "title": "Max",
+                "type": "integer"
+              },
+              "min": {
+                "description": "Do not change. It will be set to `0` by default.",
+                "title": "Min",
+                "type": "integer"
+              },
+              "start": {
+                "description": "Lower-bound rescaling value for visualization.",
+                "title": "Start",
+                "type": "integer"
+              }
+            },
+            "required": [
+              "start",
+              "end"
+            ],
+            "title": "Window",
+            "type": "object"
+          }
+        },
         "properties": {
-          "zarr_urls": {
-            "title": "Zarr Urls",
-            "type": "array",
+          "allowed_channels": {
+            "description": "A list of `OmeroChannel` s, where each channel must include the `wavelength_id` attribute and where the `wavelength_id` values must be unique across the list.",
             "items": {
-              "type": "string"
+              "$ref": "#/definitions/OmeroChannel"
             },
-            "description": "List of paths or urls to the individual OME-Zarr image to be processed. Not used by the converter task. (standard argument for Fractal tasks, managed by Fractal server)."
+            "title": "Allowed Channels",
+            "type": "array"
           },
-          "zarr_dir": {
-            "title": "Zarr Dir",
-            "type": "string",
-            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server)."
+          "coarsening_xy": {
+            "default": 2,
+            "description": "Linear coarsening factor between subsequent levels. If set to `2`, level 1 is 2x downsampled, level 2 is 4x downsampled etc.",
+            "title": "Coarsening Xy",
+            "type": "integer"
           },
           "image_dirs": {
-            "title": "Image Dirs",
-            "type": "array",
+            "description": "list of paths to the folders that contains the Cellvoyager image files. Each entry is a path to a folder that contains the image files themselves for a multiwell plate and the MeasurementData & MeasurementDetail metadata files.",
             "items": {
               "type": "string"
             },
-            "description": "list of paths to the folders that contains the Cellvoyager image files. Each entry is a path to a folder that contains the image files themselves for a multiwell plate and the MeasurementData & MeasurementDetail metadata files."
+            "title": "Image Dirs",
+            "type": "array"
           },
-          "allowed_channels": {
-            "title": "Allowed Channels",
-            "type": "array",
-            "items": {
-              "$ref": "#/definitions/OmeroChannel"
-            },
-            "description": "A list of `OmeroChannel` s, where each channel must include the `wavelength_id` attribute and where the `wavelength_id` values must be unique across the list."
+          "image_extension": {
+            "default": "tif",
+            "description": "Filename extension of images (e.g. `\"tif\"` or `\"png\"`)",
+            "title": "Image Extension",
+            "type": "string"
           },
           "image_glob_patterns": {
-            "title": "Image Glob Patterns",
-            "type": "array",
+            "description": "If specified, only parse images with filenames that match with all these patterns. Patterns must be defined as in https://docs.python.org/3/library/fnmatch.html, Example: `image_glob_pattern=[\"*_B03_*\"]` => only process well B03 `image_glob_pattern=[\"*_C09_*\", \"*F016*\", \"*Z[0-5][0-9]C*\"]` => only process well C09, field of view 16 and Z planes 0-59.",
             "items": {
               "type": "string"
             },
-            "description": "If specified, only parse images with filenames that match with all these patterns. Patterns must be defined as in https://docs.python.org/3/library/fnmatch.html, Example: `image_glob_pattern=[\"*_B03_*\"]` => only process well B03 `image_glob_pattern=[\"*_C09_*\", \"*F016*\", \"*Z[0-5][0-9]C*\"]` => only process well C09, field of view 16 and Z planes 0-59."
-          },
-          "num_levels": {
-            "title": "Num Levels",
-            "default": 5,
-            "type": "integer",
-            "description": "Number of resolution-pyramid levels. If set to `5`, there will be the full-resolution level and 4 levels of downsampled images."
-          },
-          "coarsening_xy": {
-            "title": "Coarsening Xy",
-            "default": 2,
-            "type": "integer",
-            "description": "Linear coarsening factor between subsequent levels. If set to `2`, level 1 is 2x downsampled, level 2 is 4x downsampled etc."
-          },
-          "image_extension": {
-            "title": "Image Extension",
-            "default": "tif",
-            "type": "string",
-            "description": "Filename extension of images (e.g. `\"tif\"` or `\"png\"`)"
+            "title": "Image Glob Patterns",
+            "type": "array"
           },
           "metadata_table_file": {
+            "description": "If `None`, parse Yokogawa metadata from mrf/mlf files in the input_path folder; else, the full path to a csv file containing the parsed metadata table.",
             "title": "Metadata Table File",
-            "type": "string",
-            "description": "If `None`, parse Yokogawa metadata from mrf/mlf files in the input_path folder; else, the full path to a csv file containing the parsed metadata table."
+            "type": "string"
+          },
+          "num_levels": {
+            "default": 5,
+            "description": "Number of resolution-pyramid levels. If set to `5`, there will be the full-resolution level and 4 levels of downsampled images.",
+            "title": "Num Levels",
+            "type": "integer"
           },
           "overwrite": {
-            "title": "Overwrite",
             "default": false,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
+          },
+          "zarr_dir": {
+            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Dir",
+            "type": "string"
+          },
+          "zarr_urls": {
+            "description": "List of paths or urls to the individual OME-Zarr image to be processed. Not used by the converter task. (standard argument for Fractal tasks, managed by Fractal server).",
+            "items": {
+              "type": "string"
+            },
+            "title": "Zarr Urls",
+            "type": "array"
           }
         },
         "required": [
@@ -82,152 +166,41 @@
           "image_dirs",
           "allowed_channels"
         ],
-        "additionalProperties": false,
-        "definitions": {
-          "Window": {
-            "title": "Window",
-            "description": "Custom class for Omero-channel window, based on OME-NGFF v0.4.",
-            "type": "object",
-            "properties": {
-              "min": {
-                "title": "Min",
-                "type": "integer",
-                "description": "Do not change. It will be set to `0` by default."
-              },
-              "max": {
-                "title": "Max",
-                "type": "integer",
-                "description": "Do not change. It will be set according to bit-depth of the images by default (e.g. 65535 for 16 bit images)."
-              },
-              "start": {
-                "title": "Start",
-                "type": "integer",
-                "description": "Lower-bound rescaling value for visualization."
-              },
-              "end": {
-                "title": "End",
-                "type": "integer",
-                "description": "Upper-bound rescaling value for visualization."
-              }
-            },
-            "required": [
-              "start",
-              "end"
-            ]
-          },
-          "OmeroChannel": {
-            "title": "OmeroChannel",
-            "description": "Custom class for Omero channels, based on OME-NGFF v0.4.",
-            "type": "object",
-            "properties": {
-              "wavelength_id": {
-                "title": "Wavelength Id",
-                "type": "string",
-                "description": "Unique ID for the channel wavelength, e.g. `A01_C01`."
-              },
-              "index": {
-                "title": "Index",
-                "type": "integer",
-                "description": "Do not change. For internal use only."
-              },
-              "label": {
-                "title": "Label",
-                "type": "string",
-                "description": "Name of the channel."
-              },
-              "window": {
-                "$ref": "#/definitions/Window",
-                "title": "Window",
-                "description": "Optional `Window` object to set default display settings for napari."
-              },
-              "color": {
-                "title": "Color",
-                "type": "string",
-                "description": "Optional hex colormap to display the channel in napari (it must be of length 6, e.g. `00FFFF`)."
-              },
-              "active": {
-                "title": "Active",
-                "default": true,
-                "type": "boolean",
-                "description": "Should this channel be shown in the viewer?"
-              },
-              "coefficient": {
-                "title": "Coefficient",
-                "default": 1,
-                "type": "integer",
-                "description": "Do not change. Omero-channel attribute."
-              },
-              "inverted": {
-                "title": "Inverted",
-                "default": false,
-                "type": "boolean",
-                "description": "Do not change. Omero-channel attribute."
-              }
-            },
-            "required": [
-              "wavelength_id"
-            ]
-          }
-        }
+        "title": "CellvoyagerToOmeZarrInit",
+        "type": "object"
       },
       "args_schema_parallel": {
-        "title": "CellvoyagerToOmeZarrCompute",
-        "type": "object",
-        "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "init_args": {
-            "$ref": "#/definitions/InitArgsCellVoyager",
-            "title": "Init_Args",
-            "description": "Intialization arguments provided by `create_cellvoyager_ome_zarr_init`."
-          },
-          "overwrite": {
-            "title": "Overwrite",
-            "default": false,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
-          }
-        },
-        "required": [
-          "zarr_url",
-          "init_args"
-        ],
         "additionalProperties": false,
         "definitions": {
           "InitArgsCellVoyager": {
-            "title": "InitArgsCellVoyager",
             "description": "Arguments to be passed from cellvoyager converter init to compute",
-            "type": "object",
             "properties": {
+              "acquisition": {
+                "title": "Acquisition",
+                "type": "integer"
+              },
               "image_dir": {
                 "title": "Image Dir",
                 "type": "string"
               },
-              "plate_prefix": {
-                "title": "Plate Prefix",
-                "type": "string"
-              },
-              "well_ID": {
-                "title": "Well Id",
-                "type": "string"
-              },
               "image_extension": {
                 "title": "Image Extension",
                 "type": "string"
               },
               "image_glob_patterns": {
-                "title": "Image Glob Patterns",
-                "type": "array",
                 "items": {
                   "type": "string"
-                }
+                },
+                "title": "Image Glob Patterns",
+                "type": "array"
               },
-              "acquisition": {
-                "title": "Acquisition",
-                "type": "integer"
+              "plate_prefix": {
+                "title": "Plate Prefix",
+                "type": "string"
+              },
+              "well_ID": {
+                "title": "Well Id",
+                "type": "string"
               }
             },
             "required": [
@@ -235,10 +208,40 @@
               "plate_prefix",
               "well_ID",
               "image_extension"
-            ]
+            ],
+            "title": "InitArgsCellVoyager",
+            "type": "object"
           }
-        }
+        },
+        "properties": {
+          "init_args": {
+            "$ref": "#/definitions/InitArgsCellVoyager",
+            "description": "Intialization arguments provided by `create_cellvoyager_ome_zarr_init`.",
+            "title": "Init_Args"
+          },
+          "overwrite": {
+            "default": false,
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
+          }
+        },
+        "required": [
+          "zarr_url",
+          "init_args"
+        ],
+        "title": "CellvoyagerToOmeZarrCompute",
+        "type": "object"
       },
+      "docs_info": "## cellvoyager_to_ome_zarr_init\nCreate a OME-NGFF zarr folder, without reading/writing image data.\n\nFind plates (for each folder in input_paths):\n\n- glob image files,\n- parse metadata from image filename to identify plates,\n- identify populated channels.\n\nCreate a zarr folder (for each plate):\n\n- parse mlf metadata,\n- identify wells and field of view (FOV),\n- create FOV ZARR,\n- verify that channels are uniform (i.e., same channels).\n## cellvoyager_to_ome_zarr_compute\nConvert Yokogawa output (png, tif) to zarr file.\n\nThis task is typically run after Create OME-Zarr or\nCreate OME-Zarr Multiplexing and populates the empty OME-Zarr files that\nwere prepared.\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_non_parallel": "tasks/cellvoyager_to_ome_zarr_init.py",
+      "executable_parallel": "tasks/cellvoyager_to_ome_zarr_compute.py",
       "meta_non_parallel": {
         "cpus_per_task": 1,
         "mem": 4000
@@ -247,252 +250,222 @@
         "cpus_per_task": 1,
         "mem": 4000
       },
-      "docs_info": "## cellvoyager_to_ome_zarr_init\nCreate a OME-NGFF zarr folder, without reading/writing image data.\n\nFind plates (for each folder in input_paths):\n\n- glob image files,\n- parse metadata from image filename to identify plates,\n- identify populated channels.\n\nCreate a zarr folder (for each plate):\n\n- parse mlf metadata,\n- identify wells and field of view (FOV),\n- create FOV ZARR,\n- verify that channels are uniform (i.e., same channels).\n## cellvoyager_to_ome_zarr_compute\nConvert Yokogawa output (png, tif) to zarr file.\n\nThis task is typically run after Create OME-Zarr or\nCreate OME-Zarr Multiplexing and populates the empty OME-Zarr files that\nwere prepared.\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Convert Cellvoyager to OME-Zarr"
     },
     {
-      "name": "Convert Cellvoyager Multiplexing to OME-Zarr",
-      "executable_non_parallel": "tasks/cellvoyager_to_ome_zarr_init_multiplex.py",
-      "executable_parallel": "tasks/cellvoyager_to_ome_zarr_compute.py",
       "args_schema_non_parallel": {
-        "title": "CellvoyagerToOmeZarrInitMultiplex",
-        "type": "object",
-        "properties": {
-          "zarr_urls": {
-            "title": "Zarr Urls",
-            "type": "array",
-            "items": {
-              "type": "string"
-            },
-            "description": "List of paths or urls to the individual OME-Zarr image to be processed. Not used by the converter task. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "zarr_dir": {
-            "title": "Zarr Dir",
-            "type": "string",
-            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "acquisitions": {
-            "title": "Acquisitions",
-            "type": "object",
-            "additionalProperties": {
-              "$ref": "#/definitions/MultiplexingAcquisition"
-            },
-            "description": "dictionary of acquisitions. Each key is the acquisition identifier (normally 0, 1, 2, 3 etc.). Each item defines the acquisition by providing the image_dir and the allowed_channels."
-          },
-          "image_glob_patterns": {
-            "title": "Image Glob Patterns",
-            "type": "array",
-            "items": {
-              "type": "string"
-            },
-            "description": "If specified, only parse images with filenames that match with all these patterns. Patterns must be defined as in https://docs.python.org/3/library/fnmatch.html, Example: `image_glob_pattern=[\"*_B03_*\"]` => only process well B03 `image_glob_pattern=[\"*_C09_*\", \"*F016*\", \"*Z[0-5][0-9]C*\"]` => only process well C09, field of view 16 and Z planes 0-59."
-          },
-          "num_levels": {
-            "title": "Num Levels",
-            "default": 5,
-            "type": "integer",
-            "description": "Number of resolution-pyramid levels. If set to `5`, there will be the full-resolution level and 4 levels of downsampled images."
-          },
-          "coarsening_xy": {
-            "title": "Coarsening Xy",
-            "default": 2,
-            "type": "integer",
-            "description": "Linear coarsening factor between subsequent levels. If set to `2`, level 1 is 2x downsampled, level 2 is 4x downsampled etc."
-          },
-          "image_extension": {
-            "title": "Image Extension",
-            "default": "tif",
-            "type": "string",
-            "description": "Filename extension of images (e.g. `\"tif\"` or `\"png\"`)."
-          },
-          "metadata_table_files": {
-            "title": "Metadata Table Files",
-            "type": "object",
-            "additionalProperties": {
-              "type": "string"
-            },
-            "description": "If `None`, parse Yokogawa metadata from mrf/mlf files in the input_path folder; else, a dictionary of key-value pairs like `(acquisition, path)` with `acquisition` a string like the key of the `acquisitions` dict and `path` pointing to a csv file containing the parsed metadata table."
-          },
-          "overwrite": {
-            "title": "Overwrite",
-            "default": false,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
-          }
-        },
-        "required": [
-          "zarr_urls",
-          "zarr_dir",
-          "acquisitions"
-        ],
         "additionalProperties": false,
         "definitions": {
-          "Window": {
-            "title": "Window",
-            "description": "Custom class for Omero-channel window, based on OME-NGFF v0.4.",
-            "type": "object",
+          "MultiplexingAcquisition": {
+            "description": "Input class for Multiplexing Cellvoyager converter",
             "properties": {
-              "min": {
-                "title": "Min",
-                "type": "integer",
-                "description": "Do not change. It will be set to `0` by default."
-              },
-              "max": {
-                "title": "Max",
-                "type": "integer",
-                "description": "Do not change. It will be set according to bit-depth of the images by default (e.g. 65535 for 16 bit images)."
-              },
-              "start": {
-                "title": "Start",
-                "type": "integer",
-                "description": "Lower-bound rescaling value for visualization."
+              "allowed_channels": {
+                "items": {
+                  "$ref": "#/definitions/OmeroChannel"
+                },
+                "title": "Allowed Channels",
+                "type": "array"
               },
-              "end": {
-                "title": "End",
-                "type": "integer",
-                "description": "Upper-bound rescaling value for visualization."
+              "image_dir": {
+                "title": "Image Dir",
+                "type": "string"
               }
             },
             "required": [
-              "start",
-              "end"
-            ]
+              "image_dir",
+              "allowed_channels"
+            ],
+            "title": "MultiplexingAcquisition",
+            "type": "object"
           },
           "OmeroChannel": {
-            "title": "OmeroChannel",
             "description": "Custom class for Omero channels, based on OME-NGFF v0.4.",
-            "type": "object",
             "properties": {
-              "wavelength_id": {
-                "title": "Wavelength Id",
-                "type": "string",
-                "description": "Unique ID for the channel wavelength, e.g. `A01_C01`."
-              },
-              "index": {
-                "title": "Index",
-                "type": "integer",
-                "description": "Do not change. For internal use only."
-              },
-              "label": {
-                "title": "Label",
-                "type": "string",
-                "description": "Name of the channel."
-              },
-              "window": {
-                "$ref": "#/definitions/Window",
-                "title": "Window",
-                "description": "Optional `Window` object to set default display settings for napari."
-              },
-              "color": {
-                "title": "Color",
-                "type": "string",
-                "description": "Optional hex colormap to display the channel in napari (it must be of length 6, e.g. `00FFFF`)."
-              },
               "active": {
-                "title": "Active",
                 "default": true,
-                "type": "boolean",
-                "description": "Should this channel be shown in the viewer?"
+                "description": "Should this channel be shown in the viewer?",
+                "title": "Active",
+                "type": "boolean"
               },
               "coefficient": {
-                "title": "Coefficient",
                 "default": 1,
-                "type": "integer",
-                "description": "Do not change. Omero-channel attribute."
+                "description": "Do not change. Omero-channel attribute.",
+                "title": "Coefficient",
+                "type": "integer"
+              },
+              "color": {
+                "description": "Optional hex colormap to display the channel in napari (it must be of length 6, e.g. `00FFFF`).",
+                "title": "Color",
+                "type": "string"
+              },
+              "index": {
+                "description": "Do not change. For internal use only.",
+                "title": "Index",
+                "type": "integer"
               },
               "inverted": {
-                "title": "Inverted",
                 "default": false,
-                "type": "boolean",
-                "description": "Do not change. Omero-channel attribute."
+                "description": "Do not change. Omero-channel attribute.",
+                "title": "Inverted",
+                "type": "boolean"
+              },
+              "label": {
+                "description": "Name of the channel.",
+                "title": "Label",
+                "type": "string"
+              },
+              "wavelength_id": {
+                "description": "Unique ID for the channel wavelength, e.g. `A01_C01`.",
+                "title": "Wavelength Id",
+                "type": "string"
+              },
+              "window": {
+                "$ref": "#/definitions/Window",
+                "description": "Optional `Window` object to set default display settings for napari.",
+                "title": "Window"
               }
             },
             "required": [
               "wavelength_id"
-            ]
+            ],
+            "title": "OmeroChannel",
+            "type": "object"
           },
-          "MultiplexingAcquisition": {
-            "title": "MultiplexingAcquisition",
-            "description": "Input class for Multiplexing Cellvoyager converter",
-            "type": "object",
+          "Window": {
+            "description": "Custom class for Omero-channel window, based on OME-NGFF v0.4.",
             "properties": {
-              "image_dir": {
-                "title": "Image Dir",
-                "type": "string"
+              "end": {
+                "description": "Upper-bound rescaling value for visualization.",
+                "title": "End",
+                "type": "integer"
               },
-              "allowed_channels": {
-                "title": "Allowed Channels",
-                "type": "array",
-                "items": {
-                  "$ref": "#/definitions/OmeroChannel"
-                }
+              "max": {
+                "description": "Do not change. It will be set according to bit-depth of the images by default (e.g. 65535 for 16 bit images).",
+                "title": "Max",
+                "type": "integer"
+              },
+              "min": {
+                "description": "Do not change. It will be set to `0` by default.",
+                "title": "Min",
+                "type": "integer"
+              },
+              "start": {
+                "description": "Lower-bound rescaling value for visualization.",
+                "title": "Start",
+                "type": "integer"
               }
             },
             "required": [
-              "image_dir",
-              "allowed_channels"
-            ]
+              "start",
+              "end"
+            ],
+            "title": "Window",
+            "type": "object"
           }
-        }
-      },
-      "args_schema_parallel": {
-        "title": "CellvoyagerToOmeZarrCompute",
-        "type": "object",
+        },
         "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
+          "acquisitions": {
+            "additionalProperties": {
+              "$ref": "#/definitions/MultiplexingAcquisition"
+            },
+            "description": "dictionary of acquisitions. Each key is the acquisition identifier (normally 0, 1, 2, 3 etc.). Each item defines the acquisition by providing the image_dir and the allowed_channels.",
+            "title": "Acquisitions",
+            "type": "object"
           },
-          "init_args": {
-            "$ref": "#/definitions/InitArgsCellVoyager",
-            "title": "Init_Args",
-            "description": "Intialization arguments provided by `create_cellvoyager_ome_zarr_init`."
+          "coarsening_xy": {
+            "default": 2,
+            "description": "Linear coarsening factor between subsequent levels. If set to `2`, level 1 is 2x downsampled, level 2 is 4x downsampled etc.",
+            "title": "Coarsening Xy",
+            "type": "integer"
+          },
+          "image_extension": {
+            "default": "tif",
+            "description": "Filename extension of images (e.g. `\"tif\"` or `\"png\"`).",
+            "title": "Image Extension",
+            "type": "string"
+          },
+          "image_glob_patterns": {
+            "description": "If specified, only parse images with filenames that match with all these patterns. Patterns must be defined as in https://docs.python.org/3/library/fnmatch.html, Example: `image_glob_pattern=[\"*_B03_*\"]` => only process well B03 `image_glob_pattern=[\"*_C09_*\", \"*F016*\", \"*Z[0-5][0-9]C*\"]` => only process well C09, field of view 16 and Z planes 0-59.",
+            "items": {
+              "type": "string"
+            },
+            "title": "Image Glob Patterns",
+            "type": "array"
+          },
+          "metadata_table_files": {
+            "additionalProperties": {
+              "type": "string"
+            },
+            "description": "If `None`, parse Yokogawa metadata from mrf/mlf files in the input_path folder; else, a dictionary of key-value pairs like `(acquisition, path)` with `acquisition` a string like the key of the `acquisitions` dict and `path` pointing to a csv file containing the parsed metadata table.",
+            "title": "Metadata Table Files",
+            "type": "object"
+          },
+          "num_levels": {
+            "default": 5,
+            "description": "Number of resolution-pyramid levels. If set to `5`, there will be the full-resolution level and 4 levels of downsampled images.",
+            "title": "Num Levels",
+            "type": "integer"
           },
           "overwrite": {
-            "title": "Overwrite",
             "default": false,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
+          },
+          "zarr_dir": {
+            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Dir",
+            "type": "string"
+          },
+          "zarr_urls": {
+            "description": "List of paths or urls to the individual OME-Zarr image to be processed. Not used by the converter task. (standard argument for Fractal tasks, managed by Fractal server).",
+            "items": {
+              "type": "string"
+            },
+            "title": "Zarr Urls",
+            "type": "array"
           }
         },
         "required": [
-          "zarr_url",
-          "init_args"
+          "zarr_urls",
+          "zarr_dir",
+          "acquisitions"
         ],
+        "title": "CellvoyagerToOmeZarrInitMultiplex",
+        "type": "object"
+      },
+      "args_schema_parallel": {
         "additionalProperties": false,
         "definitions": {
           "InitArgsCellVoyager": {
-            "title": "InitArgsCellVoyager",
             "description": "Arguments to be passed from cellvoyager converter init to compute",
-            "type": "object",
             "properties": {
+              "acquisition": {
+                "title": "Acquisition",
+                "type": "integer"
+              },
               "image_dir": {
                 "title": "Image Dir",
                 "type": "string"
               },
-              "plate_prefix": {
-                "title": "Plate Prefix",
-                "type": "string"
-              },
-              "well_ID": {
-                "title": "Well Id",
-                "type": "string"
-              },
               "image_extension": {
                 "title": "Image Extension",
                 "type": "string"
               },
               "image_glob_patterns": {
-                "title": "Image Glob Patterns",
-                "type": "array",
                 "items": {
                   "type": "string"
-                }
+                },
+                "title": "Image Glob Patterns",
+                "type": "array"
               },
-              "acquisition": {
-                "title": "Acquisition",
-                "type": "integer"
+              "plate_prefix": {
+                "title": "Plate Prefix",
+                "type": "string"
+              },
+              "well_ID": {
+                "title": "Well Id",
+                "type": "string"
               }
             },
             "required": [
@@ -500,10 +473,40 @@
               "plate_prefix",
               "well_ID",
               "image_extension"
-            ]
+            ],
+            "title": "InitArgsCellVoyager",
+            "type": "object"
+          }
+        },
+        "properties": {
+          "init_args": {
+            "$ref": "#/definitions/InitArgsCellVoyager",
+            "description": "Intialization arguments provided by `create_cellvoyager_ome_zarr_init`.",
+            "title": "Init_Args"
+          },
+          "overwrite": {
+            "default": false,
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
           }
-        }
+        },
+        "required": [
+          "zarr_url",
+          "init_args"
+        ],
+        "title": "CellvoyagerToOmeZarrCompute",
+        "type": "object"
       },
+      "docs_info": "## cellvoyager_to_ome_zarr_init_multiplex\nCreate OME-NGFF structure and metadata to host a multiplexing dataset.\n\nThis task takes a set of image folders (i.e. different acquisition cycles)\nand build the internal structure and metadata of a OME-NGFF zarr group,\nwithout actually loading/writing the image data.\n\nEach element in input_paths should be treated as a different acquisition.\n## cellvoyager_to_ome_zarr_compute\nConvert Yokogawa output (png, tif) to zarr file.\n\nThis task is typically run after Create OME-Zarr or\nCreate OME-Zarr Multiplexing and populates the empty OME-Zarr files that\nwere prepared.\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_non_parallel": "tasks/cellvoyager_to_ome_zarr_init_multiplex.py",
+      "executable_parallel": "tasks/cellvoyager_to_ome_zarr_compute.py",
       "meta_non_parallel": {
         "cpus_per_task": 1,
         "mem": 4000
@@ -512,86 +515,50 @@
         "cpus_per_task": 1,
         "mem": 4000
       },
-      "docs_info": "## cellvoyager_to_ome_zarr_init_multiplex\nCreate OME-NGFF structure and metadata to host a multiplexing dataset.\n\nThis task takes a set of image folders (i.e. different acquisition cycles)\nand build the internal structure and metadata of a OME-NGFF zarr group,\nwithout actually loading/writing the image data.\n\nEach element in input_paths should be treated as a different acquisition.\n## cellvoyager_to_ome_zarr_compute\nConvert Yokogawa output (png, tif) to zarr file.\n\nThis task is typically run after Create OME-Zarr or\nCreate OME-Zarr Multiplexing and populates the empty OME-Zarr files that\nwere prepared.\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Convert Cellvoyager Multiplexing to OME-Zarr"
     },
     {
-      "name": "Maximum Intensity Projection HCS Plate",
-      "input_types": {
-        "is_3D": true
-      },
-      "output_types": {
-        "is_3D": false
-      },
-      "executable_non_parallel": "tasks/copy_ome_zarr_hcs_plate.py",
-      "executable_parallel": "tasks/maximum_intensity_projection.py",
       "args_schema_non_parallel": {
-        "title": "CopyOmeZarrHcsPlate",
-        "type": "object",
+        "additionalProperties": false,
         "properties": {
-          "zarr_urls": {
-            "title": "Zarr Urls",
-            "type": "array",
-            "items": {
-              "type": "string"
-            },
-            "description": "List of paths or urls to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "zarr_dir": {
-            "title": "Zarr Dir",
-            "type": "string",
-            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server)."
+          "overwrite": {
+            "default": false,
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
           },
           "suffix": {
-            "title": "Suffix",
             "default": "mip",
-            "type": "string",
-            "description": "The suffix that is used to transform `plate.zarr` into `plate_suffix.zarr`. Note that `None` is not currently supported."
+            "description": "The suffix that is used to transform `plate.zarr` into `plate_suffix.zarr`. Note that `None` is not currently supported.",
+            "title": "Suffix",
+            "type": "string"
           },
-          "overwrite": {
-            "title": "Overwrite",
-            "default": false,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
+          "zarr_dir": {
+            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Dir",
+            "type": "string"
+          },
+          "zarr_urls": {
+            "description": "List of paths or urls to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "items": {
+              "type": "string"
+            },
+            "title": "Zarr Urls",
+            "type": "array"
           }
         },
         "required": [
           "zarr_urls",
           "zarr_dir"
         ],
-        "additionalProperties": false
+        "title": "CopyOmeZarrHcsPlate",
+        "type": "object"
       },
       "args_schema_parallel": {
-        "title": "MaximumIntensityProjection",
-        "type": "object",
-        "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "init_args": {
-            "$ref": "#/definitions/InitArgsMIP",
-            "title": "Init_Args",
-            "description": "Intialization arguments provided by `create_cellvoyager_ome_zarr_init`."
-          },
-          "overwrite": {
-            "title": "Overwrite",
-            "default": false,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
-          }
-        },
-        "required": [
-          "zarr_url",
-          "init_args"
-        ],
         "additionalProperties": false,
         "definitions": {
           "InitArgsMIP": {
-            "title": "InitArgsMIP",
             "description": "Init Args for MIP task.",
-            "type": "object",
             "properties": {
               "origin_url": {
                 "title": "Origin Url",
@@ -600,9 +567,42 @@
             },
             "required": [
               "origin_url"
-            ]
+            ],
+            "title": "InitArgsMIP",
+            "type": "object"
           }
-        }
+        },
+        "properties": {
+          "init_args": {
+            "$ref": "#/definitions/InitArgsMIP",
+            "description": "Intialization arguments provided by `create_cellvoyager_ome_zarr_init`.",
+            "title": "Init_Args"
+          },
+          "overwrite": {
+            "default": false,
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
+          }
+        },
+        "required": [
+          "zarr_url",
+          "init_args"
+        ],
+        "title": "MaximumIntensityProjection",
+        "type": "object"
+      },
+      "docs_info": "## copy_ome_zarr_hcs_plate\nDuplicate the OME-Zarr HCS structure for a set of zarr_urls.\n\nThis task only processes the zarr images in the zarr_urls, not all the\nimages in the plate. It copies all the  plate & well structure, but none\nof the image metadata or the actual image data:\n\n- For each plate, create a new OME-Zarr HCS plate with the attributes for\n    all the images in zarr_urls\n- For each well (in each plate), create a new zarr subgroup with the\n   same attributes as the original one.\n\nNote: this task makes use of methods from the `Attributes` class, see\nhttps://zarr.readthedocs.io/en/stable/api/attrs.html.\n## maximum_intensity_projection\nPerform maximum-intensity projection along Z axis.\n\nNote: this task stores the output in a new zarr file.\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_non_parallel": "tasks/copy_ome_zarr_hcs_plate.py",
+      "executable_parallel": "tasks/maximum_intensity_projection.py",
+      "input_types": {
+        "is_3D": true
       },
       "meta_non_parallel": {
         "cpus_per_task": 1,
@@ -612,63 +612,56 @@
         "cpus_per_task": 1,
         "mem": 4000
       },
-      "docs_info": "## copy_ome_zarr_hcs_plate\nDuplicate the OME-Zarr HCS structure for a set of zarr_urls.\n\nThis task only processes the zarr images in the zarr_urls, not all the\nimages in the plate. It copies all the  plate & well structure, but none\nof the image metadata or the actual image data:\n\n- For each plate, create a new OME-Zarr HCS plate with the attributes for\n    all the images in zarr_urls\n- For each well (in each plate), create a new zarr subgroup with the\n   same attributes as the original one.\n\nNote: this task makes use of methods from the `Attributes` class, see\nhttps://zarr.readthedocs.io/en/stable/api/attrs.html.\n## maximum_intensity_projection\nPerform maximum-intensity projection along Z axis.\n\nNote: this task stores the output in a new zarr file.\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Maximum Intensity Projection HCS Plate",
+      "output_types": {
+        "is_3D": false
+      }
     },
     {
-      "name": "Illumination Correction",
-      "input_types": {
-        "illumination_corrected": false
-      },
-      "output_types": {
-        "illumination_corrected": true
-      },
-      "executable_parallel": "tasks/illumination_correction.py",
       "args_schema_parallel": {
-        "title": "IlluminationCorrection",
-        "type": "object",
+        "additionalProperties": false,
         "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "illumination_profiles_folder": {
-            "title": "Illumination Profiles Folder",
-            "type": "string",
-            "description": "Path of folder of illumination profiles."
+          "background": {
+            "default": 110,
+            "description": "Background value that is subtracted from the image before the illumination correction is applied. Set it to `0` if you don't want any background subtraction.",
+            "title": "Background",
+            "type": "integer"
           },
           "dict_corr": {
-            "title": "Dict Corr",
-            "type": "object",
             "additionalProperties": {
               "type": "string"
             },
-            "description": "Dictionary where keys match the `wavelength_id` attributes of existing channels (e.g. `A01_C01` ) and values are the filenames of the corresponding illumination profiles."
+            "description": "Dictionary where keys match the `wavelength_id` attributes of existing channels (e.g. `A01_C01` ) and values are the filenames of the corresponding illumination profiles.",
+            "title": "Dict Corr",
+            "type": "object"
           },
-          "background": {
-            "title": "Background",
-            "default": 110,
-            "type": "integer",
-            "description": "Background value that is subtracted from the image before the illumination correction is applied. Set it to `0` if you don't want any background subtraction."
+          "illumination_profiles_folder": {
+            "description": "Path of folder of illumination profiles.",
+            "title": "Illumination Profiles Folder",
+            "type": "string"
           },
           "input_ROI_table": {
-            "title": "Input Roi Table",
             "default": "FOV_ROI_table",
-            "type": "string",
-            "description": "Name of the ROI table that contains the information about the location of the individual field of views (FOVs) to which the illumination correction shall be applied. Defaults to \"FOV_ROI_table\", the default name Fractal converters give the ROI tables that list all FOVs separately. If you generated your OME-Zarr with a different converter and used Import OME-Zarr to generate the ROI tables, `image_ROI_table` is the right choice if you only have 1 FOV per Zarr image and `grid_ROI_table` if you have multiple FOVs per Zarr image and set the right grid options during import."
+            "description": "Name of the ROI table that contains the information about the location of the individual field of views (FOVs) to which the illumination correction shall be applied. Defaults to \"FOV_ROI_table\", the default name Fractal converters give the ROI tables that list all FOVs separately. If you generated your OME-Zarr with a different converter and used Import OME-Zarr to generate the ROI tables, `image_ROI_table` is the right choice if you only have 1 FOV per Zarr image and `grid_ROI_table` if you have multiple FOVs per Zarr image and set the right grid options during import.",
+            "title": "Input Roi Table",
+            "type": "string"
           },
           "overwrite_input": {
-            "title": "Overwrite Input",
             "default": true,
-            "type": "boolean",
-            "description": "If `True`, the results of this task will overwrite the input image data. If false, a new image is generated and the illumination corrected data is saved there."
+            "description": "If `True`, the results of this task will overwrite the input image data. If false, a new image is generated and the illumination corrected data is saved there.",
+            "title": "Overwrite Input",
+            "type": "boolean"
           },
           "suffix": {
-            "title": "Suffix",
             "default": "_illum_corr",
-            "type": "string",
-            "description": "What suffix to append to the illumination corrected images. Only relevant if overwrite_input=False."
+            "description": "What suffix to append to the illumination corrected images. Only relevant if overwrite_input=False.",
+            "title": "Suffix",
+            "type": "string"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
           }
         },
         "required": [
@@ -676,79 +669,166 @@
           "illumination_profiles_folder",
           "dict_corr"
         ],
-        "additionalProperties": false
+        "title": "IlluminationCorrection",
+        "type": "object"
+      },
+      "docs_info": "## illumination_correction\nApplies illumination correction to the images in the OME-Zarr.\n\nAssumes that the illumination correction profiles were generated before\nseparately and that the same background subtraction was used during\ncalculation of the illumination correction (otherwise, it will not work\nwell & the correction may only be partial).\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_parallel": "tasks/illumination_correction.py",
+      "input_types": {
+        "illumination_corrected": false
       },
       "meta_parallel": {
         "cpus_per_task": 1,
         "mem": 4000
       },
-      "docs_info": "## illumination_correction\nApplies illumination correction to the images in the OME-Zarr.\n\nAssumes that the illumination correction profiles were generated before\nseparately and that the same background subtraction was used during\ncalculation of the illumination correction (otherwise, it will not work\nwell & the correction may only be partial).\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Illumination Correction",
+      "output_types": {
+        "illumination_corrected": true
+      }
     },
     {
-      "name": "Cellpose Segmentation",
-      "executable_parallel": "tasks/cellpose_segmentation.py",
       "args_schema_parallel": {
-        "title": "CellposeSegmentation",
-        "type": "object",
+        "additionalProperties": false,
+        "definitions": {
+          "CellposeCustomNormalizer": {
+            "description": "Validator to handle different normalization scenarios for Cellpose models",
+            "properties": {
+              "lower_bound": {
+                "description": "Explicit lower bound value to rescale the image at. Needs to be an integer, e.g. 100. You can only specify percentiles or bounds, not both.",
+                "title": "Lower Bound",
+                "type": "integer"
+              },
+              "lower_percentile": {
+                "description": "Specify a custom lower-bound percentile for rescaling as a float value between 0 and 100. Set to 1 to run the same as default). You can only specify percentiles or bounds, not both.",
+                "maximum": 100,
+                "minimum": 0,
+                "title": "Lower Percentile",
+                "type": "number"
+              },
+              "type": {
+                "default": "default",
+                "description": "One of `default` (Cellpose default normalization), `custom` (using the other custom parameters) or `no_normalization`.",
+                "enum": [
+                  "default",
+                  "custom",
+                  "no_normalization"
+                ],
+                "title": "Type",
+                "type": "string"
+              },
+              "upper_bound": {
+                "description": "Explicit upper bound value to rescale the image at. Needs to be an integer, e.g. 2000. You can only specify percentiles or bounds, not both.",
+                "title": "Upper Bound",
+                "type": "integer"
+              },
+              "upper_percentile": {
+                "description": "Specify a custom upper-bound percentile for rescaling as a float value between 0 and 100. Set to 99 to run the same as default, set to e.g. 99.99 if the default rescaling was too harsh. You can only specify percentiles or bounds, not both.",
+                "maximum": 100,
+                "minimum": 0,
+                "title": "Upper Percentile",
+                "type": "number"
+              }
+            },
+            "title": "CellposeCustomNormalizer",
+            "type": "object"
+          },
+          "ChannelInputModel": {
+            "description": "A channel which is specified by either `wavelength_id` or `label`.",
+            "properties": {
+              "label": {
+                "description": "Name of the channel.",
+                "title": "Label",
+                "type": "string"
+              },
+              "wavelength_id": {
+                "description": "Unique ID for the channel wavelength, e.g. `A01_C01`.",
+                "title": "Wavelength Id",
+                "type": "string"
+              }
+            },
+            "title": "ChannelInputModel",
+            "type": "object"
+          }
+        },
         "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
+          "anisotropy": {
+            "description": "Ratio of the pixel sizes along Z and XY axis (ignored if the image is not three-dimensional). If `None`, it is inferred from the OME-NGFF metadata.",
+            "title": "Anisotropy",
+            "type": "number"
           },
-          "level": {
-            "title": "Level",
-            "type": "integer",
-            "description": "Pyramid level of the image to be segmented. Choose `0` to process at full resolution."
+          "augment": {
+            "default": false,
+            "description": "Parameter of `CellposeModel` class. Whether to use cellpose augmentation to tile images with overlap.",
+            "title": "Augment",
+            "type": "boolean"
+          },
+          "batch_size": {
+            "default": 8,
+            "description": "number of 224x224 patches to run simultaneously on the GPU (can make smaller or bigger depending on GPU memory usage)",
+            "title": "Batch Size",
+            "type": "integer"
+          },
+          "cellprob_threshold": {
+            "default": 0.0,
+            "description": "Parameter of `CellposeModel.eval` method. Valid values between -6 to 6. From Cellpose documentation: \"Decrease this threshold if cellpose is not returning as many ROIs as you\u2019d expect. Similarly, increase this threshold if cellpose is returning too ROIs particularly from dim areas.\"",
+            "title": "Cellprob Threshold",
+            "type": "number"
           },
           "channel": {
             "$ref": "#/definitions/ChannelInputModel",
-            "title": "Channel",
-            "description": "Primary channel for segmentation; requires either `wavelength_id` (e.g. `A01_C01`) or `label` (e.g. `DAPI`)."
+            "description": "Primary channel for segmentation; requires either `wavelength_id` (e.g. `A01_C01`) or `label` (e.g. `DAPI`).",
+            "title": "Channel"
           },
           "channel2": {
             "$ref": "#/definitions/ChannelInputModel",
-            "title": "Channel2",
-            "description": "Second channel for segmentation (in the same format as `channel`). If specified, cellpose runs in dual channel mode. For dual channel segmentation of cells, the first channel should contain the membrane marker, the second channel should contain the nuclear marker."
+            "description": "Second channel for segmentation (in the same format as `channel`). If specified, cellpose runs in dual channel mode. For dual channel segmentation of cells, the first channel should contain the membrane marker, the second channel should contain the nuclear marker.",
+            "title": "Channel2"
           },
-          "input_ROI_table": {
-            "title": "Input Roi Table",
-            "default": "FOV_ROI_table",
-            "type": "string",
-            "description": "Name of the ROI table over which the task loops to apply Cellpose segmentation. Examples: `FOV_ROI_table` => loop over the field of views, `organoid_ROI_table` => loop over the organoid ROI table (generated by another task), `well_ROI_table` => process the whole well as one image."
+          "diameter_level0": {
+            "default": 30.0,
+            "description": "Expected diameter of the objects that should be segmented in pixels at level 0. Initial diameter is rescaled using the `level` that was selected. The rescaled value is passed as the diameter to the `CellposeModel.eval` method.",
+            "title": "Diameter Level0",
+            "type": "number"
           },
-          "output_ROI_table": {
-            "title": "Output Roi Table",
-            "type": "string",
-            "description": "If provided, a ROI table with that name is created, which will contain the bounding boxes of the newly segmented labels. ROI tables should have `ROI` in their name."
+          "flow_threshold": {
+            "default": 0.4,
+            "description": "Parameter of `CellposeModel.eval` method. Valid values between 0.0 and 1.0. From Cellpose documentation: \"Increase this threshold if cellpose is not returning as many ROIs as you\u2019d expect. Similarly, decrease this threshold if cellpose is returning too many ill-shaped ROIs.\"",
+            "title": "Flow Threshold",
+            "type": "number"
           },
-          "output_label_name": {
-            "title": "Output Label Name",
-            "type": "string",
-            "description": "Name of the output label image (e.g. `\"organoids\"`)."
+          "input_ROI_table": {
+            "default": "FOV_ROI_table",
+            "description": "Name of the ROI table over which the task loops to apply Cellpose segmentation. Examples: `FOV_ROI_table` => loop over the field of views, `organoid_ROI_table` => loop over the organoid ROI table (generated by another task), `well_ROI_table` => process the whole well as one image.",
+            "title": "Input Roi Table",
+            "type": "string"
           },
-          "use_masks": {
-            "title": "Use Masks",
+          "interp": {
             "default": true,
-            "type": "boolean",
-            "description": "If `True`, try to use masked loading and fall back to `use_masks=False` if the ROI table is not suitable. Masked loading is relevant when only a subset of the bounding box should actually be processed (e.g. running within `organoid_ROI_table`)."
+            "description": "interpolate during 2D dynamics (not available in 3D) (in previous versions it was False, now it defaults to True)",
+            "title": "Interp",
+            "type": "boolean"
           },
-          "relabeling": {
-            "title": "Relabeling",
-            "default": true,
-            "type": "boolean",
-            "description": "If `True`, apply relabeling so that label values are unique for all objects in the well."
+          "invert": {
+            "default": false,
+            "description": "invert image pixel intensity before running network (if True, image is also normalized)",
+            "title": "Invert",
+            "type": "boolean"
           },
-          "diameter_level0": {
-            "title": "Diameter Level0",
-            "default": 30.0,
-            "type": "number",
-            "description": "Expected diameter of the objects that should be segmented in pixels at level 0. Initial diameter is rescaled using the `level` that was selected. The rescaled value is passed as the diameter to the `CellposeModel.eval` method."
+          "level": {
+            "description": "Pyramid level of the image to be segmented. Choose `0` to process at full resolution.",
+            "title": "Level",
+            "type": "integer"
+          },
+          "min_size": {
+            "default": 15,
+            "description": "Parameter of `CellposeModel` class. Minimum size of the segmented objects (in pixels). Use `-1` to turn off the size filter.",
+            "title": "Min Size",
+            "type": "integer"
           },
           "model_type": {
-            "title": "Model Type",
             "default": "cyto2",
+            "description": "Parameter of `CellposeModel` class. Defines which model should be used. Typical choices are `nuclei`, `cyto`, `cyto2`, etc.",
             "enum": [
               "cyto",
               "nuclei",
@@ -766,260 +846,194 @@
               "LC3",
               "LC4"
             ],
-            "type": "string",
-            "description": "Parameter of `CellposeModel` class. Defines which model should be used. Typical choices are `nuclei`, `cyto`, `cyto2`, etc."
-          },
-          "pretrained_model": {
-            "title": "Pretrained Model",
-            "type": "string",
-            "description": "Parameter of `CellposeModel` class (takes precedence over `model_type`). Allows you to specify the path of a custom trained cellpose model."
-          },
-          "cellprob_threshold": {
-            "title": "Cellprob Threshold",
-            "default": 0.0,
-            "type": "number",
-            "description": "Parameter of `CellposeModel.eval` method. Valid values between -6 to 6. From Cellpose documentation: \"Decrease this threshold if cellpose is not returning as many ROIs as you\u2019d expect. Similarly, increase this threshold if cellpose is returning too ROIs particularly from dim areas.\""
+            "title": "Model Type",
+            "type": "string"
           },
-          "flow_threshold": {
-            "title": "Flow Threshold",
-            "default": 0.4,
-            "type": "number",
-            "description": "Parameter of `CellposeModel.eval` method. Valid values between 0.0 and 1.0. From Cellpose documentation: \"Increase this threshold if cellpose is not returning as many ROIs as you\u2019d expect. Similarly, decrease this threshold if cellpose is returning too many ill-shaped ROIs.\""
+          "net_avg": {
+            "default": false,
+            "description": "Parameter of `CellposeModel` class. Whether to use cellpose net averaging to run the 4 built-in networks (useful for `nuclei`, `cyto` and `cyto2`, not sure it works for the others).",
+            "title": "Net Avg",
+            "type": "boolean"
           },
           "normalize": {
-            "title": "Normalize",
-            "default": {
-              "type": "default",
-              "lower_percentile": null,
-              "upper_percentile": null,
-              "lower_bound": null,
-              "upper_bound": null
-            },
             "allOf": [
               {
                 "$ref": "#/definitions/CellposeCustomNormalizer"
               }
             ],
-            "description": "By default, data is normalized so 0.0=1st percentile and 1.0=99th percentile of image intensities in each channel. This automatic normalization can lead to issues when the image to be segmented is very sparse. You can turn off the default rescaling. With the \"custom\" option, you can either provide your own rescaling percentiles or fixed rescaling upper and lower bound integers."
-          },
-          "anisotropy": {
-            "title": "Anisotropy",
-            "type": "number",
-            "description": "Ratio of the pixel sizes along Z and XY axis (ignored if the image is not three-dimensional). If `None`, it is inferred from the OME-NGFF metadata."
-          },
-          "min_size": {
-            "title": "Min Size",
-            "default": 15,
-            "type": "integer",
-            "description": "Parameter of `CellposeModel` class. Minimum size of the segmented objects (in pixels). Use `-1` to turn off the size filter."
+            "default": {
+              "lower_bound": null,
+              "lower_percentile": null,
+              "type": "default",
+              "upper_bound": null,
+              "upper_percentile": null
+            },
+            "description": "By default, data is normalized so 0.0=1st percentile and 1.0=99th percentile of image intensities in each channel. This automatic normalization can lead to issues when the image to be segmented is very sparse. You can turn off the default rescaling. With the \"custom\" option, you can either provide your own rescaling percentiles or fixed rescaling upper and lower bound integers.",
+            "title": "Normalize"
           },
-          "augment": {
-            "title": "Augment",
-            "default": false,
-            "type": "boolean",
-            "description": "Parameter of `CellposeModel` class. Whether to use cellpose augmentation to tile images with overlap."
+          "output_ROI_table": {
+            "description": "If provided, a ROI table with that name is created, which will contain the bounding boxes of the newly segmented labels. ROI tables should have `ROI` in their name.",
+            "title": "Output Roi Table",
+            "type": "string"
           },
-          "net_avg": {
-            "title": "Net Avg",
-            "default": false,
-            "type": "boolean",
-            "description": "Parameter of `CellposeModel` class. Whether to use cellpose net averaging to run the 4 built-in networks (useful for `nuclei`, `cyto` and `cyto2`, not sure it works for the others)."
+          "output_label_name": {
+            "description": "Name of the output label image (e.g. `\"organoids\"`).",
+            "title": "Output Label Name",
+            "type": "string"
           },
-          "use_gpu": {
-            "title": "Use Gpu",
+          "overwrite": {
             "default": true,
-            "type": "boolean",
-            "description": "If `False`, always use the CPU; if `True`, use the GPU if possible (as defined in `cellpose.core.use_gpu()`) and fall-back to the CPU otherwise."
-          },
-          "batch_size": {
-            "title": "Batch Size",
-            "default": 8,
-            "type": "integer",
-            "description": "number of 224x224 patches to run simultaneously on the GPU (can make smaller or bigger depending on GPU memory usage)"
+            "description": "If `True`, overwrite the task output.",
+            "title": "Overwrite",
+            "type": "boolean"
           },
-          "invert": {
-            "title": "Invert",
-            "default": false,
-            "type": "boolean",
-            "description": "invert image pixel intensity before running network (if True, image is also normalized)"
+          "pretrained_model": {
+            "description": "Parameter of `CellposeModel` class (takes precedence over `model_type`). Allows you to specify the path of a custom trained cellpose model.",
+            "title": "Pretrained Model",
+            "type": "string"
           },
-          "tile": {
-            "title": "Tile",
+          "relabeling": {
             "default": true,
-            "type": "boolean",
-            "description": "tiles image to ensure GPU/CPU memory usage limited (recommended)"
-          },
-          "tile_overlap": {
-            "title": "Tile Overlap",
-            "default": 0.1,
-            "type": "number",
-            "description": "fraction of overlap of tiles when computing flows"
+            "description": "If `True`, apply relabeling so that label values are unique for all objects in the well.",
+            "title": "Relabeling",
+            "type": "boolean"
           },
           "resample": {
-            "title": "Resample",
-            "default": true,
-            "type": "boolean",
-            "description": "run dynamics at original image size (will be slower but create more accurate boundaries)"
-          },
-          "interp": {
-            "title": "Interp",
             "default": true,
-            "type": "boolean",
-            "description": "interpolate during 2D dynamics (not available in 3D) (in previous versions it was False, now it defaults to True)"
+            "description": "run dynamics at original image size (will be slower but create more accurate boundaries)",
+            "title": "Resample",
+            "type": "boolean"
           },
           "stitch_threshold": {
-            "title": "Stitch Threshold",
             "default": 0.0,
-            "type": "number",
-            "description": "if stitch_threshold>0.0 and not do_3D and equal image sizes, masks are stitched in 3D to return volume segmentation"
+            "description": "if stitch_threshold>0.0 and not do_3D and equal image sizes, masks are stitched in 3D to return volume segmentation",
+            "title": "Stitch Threshold",
+            "type": "number"
           },
-          "overwrite": {
-            "title": "Overwrite",
+          "tile": {
             "default": true,
-            "type": "boolean",
-            "description": "If `True`, overwrite the task output."
-          }
-        },
-        "required": [
-          "zarr_url",
-          "level",
-          "channel"
-        ],
-        "additionalProperties": false,
-        "definitions": {
-          "ChannelInputModel": {
-            "title": "ChannelInputModel",
-            "description": "A channel which is specified by either `wavelength_id` or `label`.",
-            "type": "object",
-            "properties": {
-              "wavelength_id": {
-                "title": "Wavelength Id",
-                "type": "string",
-                "description": "Unique ID for the channel wavelength, e.g. `A01_C01`."
-              },
-              "label": {
-                "title": "Label",
-                "type": "string",
-                "description": "Name of the channel."
-              }
-            }
+            "description": "tiles image to ensure GPU/CPU memory usage limited (recommended)",
+            "title": "Tile",
+            "type": "boolean"
           },
-          "CellposeCustomNormalizer": {
-            "title": "CellposeCustomNormalizer",
-            "description": "Validator to handle different normalization scenarios for Cellpose models",
-            "type": "object",
-            "properties": {
-              "type": {
-                "title": "Type",
-                "default": "default",
-                "enum": [
-                  "default",
-                  "custom",
-                  "no_normalization"
-                ],
-                "type": "string",
-                "description": "One of `default` (Cellpose default normalization), `custom` (using the other custom parameters) or `no_normalization`."
-              },
-              "lower_percentile": {
-                "title": "Lower Percentile",
-                "minimum": 0,
-                "maximum": 100,
-                "type": "number",
-                "description": "Specify a custom lower-bound percentile for rescaling as a float value between 0 and 100. Set to 1 to run the same as default). You can only specify percentiles or bounds, not both."
-              },
-              "upper_percentile": {
-                "title": "Upper Percentile",
-                "minimum": 0,
-                "maximum": 100,
-                "type": "number",
-                "description": "Specify a custom upper-bound percentile for rescaling as a float value between 0 and 100. Set to 99 to run the same as default, set to e.g. 99.99 if the default rescaling was too harsh. You can only specify percentiles or bounds, not both."
-              },
-              "lower_bound": {
-                "title": "Lower Bound",
-                "type": "integer",
-                "description": "Explicit lower bound value to rescale the image at. Needs to be an integer, e.g. 100. You can only specify percentiles or bounds, not both."
-              },
-              "upper_bound": {
-                "title": "Upper Bound",
-                "type": "integer",
-                "description": "Explicit upper bound value to rescale the image at. Needs to be an integer, e.g. 2000. You can only specify percentiles or bounds, not both."
-              }
-            }
+          "tile_overlap": {
+            "default": 0.1,
+            "description": "fraction of overlap of tiles when computing flows",
+            "title": "Tile Overlap",
+            "type": "number"
+          },
+          "use_gpu": {
+            "default": true,
+            "description": "If `False`, always use the CPU; if `True`, use the GPU if possible (as defined in `cellpose.core.use_gpu()`) and fall-back to the CPU otherwise.",
+            "title": "Use Gpu",
+            "type": "boolean"
+          },
+          "use_masks": {
+            "default": true,
+            "description": "If `True`, try to use masked loading and fall back to `use_masks=False` if the ROI table is not suitable. Masked loading is relevant when only a subset of the bounding box should actually be processed (e.g. running within `organoid_ROI_table`).",
+            "title": "Use Masks",
+            "type": "boolean"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
           }
-        }
+        },
+        "required": [
+          "zarr_url",
+          "level",
+          "channel"
+        ],
+        "title": "CellposeSegmentation",
+        "type": "object"
       },
+      "docs_info": "## cellpose_segmentation\nRun cellpose segmentation on the ROIs of a single OME-Zarr image.\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_parallel": "tasks/cellpose_segmentation.py",
       "meta_parallel": {
         "cpus_per_task": 4,
         "mem": 16000,
         "needs_gpu": true
       },
-      "docs_info": "## cellpose_segmentation\nRun cellpose segmentation on the ROIs of a single OME-Zarr image.\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Cellpose Segmentation"
     },
     {
-      "name": "Calculate Registration (image-based)",
-      "executable_non_parallel": "tasks/image_based_registration_hcs_init.py",
-      "executable_parallel": "tasks/calculate_registration_image_based.py",
       "args_schema_non_parallel": {
-        "title": "ImageBasedRegistrationHcsInit",
-        "type": "object",
+        "additionalProperties": false,
         "properties": {
-          "zarr_urls": {
-            "title": "Zarr Urls",
-            "type": "array",
-            "items": {
-              "type": "string"
-            },
-            "description": "List of paths or urls to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
+          "reference_cycle": {
+            "default": 0,
+            "description": "Which cycle to register against. Needs to match the acquisition metadata in the OME-Zarr image.",
+            "title": "Reference Cycle",
+            "type": "integer"
           },
           "zarr_dir": {
+            "description": "path of the directory where the new OME-Zarrs will be created. Not used by this task. (standard argument for Fractal tasks, managed by Fractal server).",
             "title": "Zarr Dir",
-            "type": "string",
-            "description": "path of the directory where the new OME-Zarrs will be created. Not used by this task. (standard argument for Fractal tasks, managed by Fractal server)."
+            "type": "string"
           },
-          "reference_cycle": {
-            "title": "Reference Cycle",
-            "default": 0,
-            "type": "integer",
-            "description": "Which cycle to register against. Needs to match the acquisition metadata in the OME-Zarr image."
+          "zarr_urls": {
+            "description": "List of paths or urls to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "items": {
+              "type": "string"
+            },
+            "title": "Zarr Urls",
+            "type": "array"
           }
         },
         "required": [
           "zarr_urls",
           "zarr_dir"
         ],
-        "additionalProperties": false
+        "title": "ImageBasedRegistrationHcsInit",
+        "type": "object"
       },
       "args_schema_parallel": {
-        "title": "CalculateRegistrationImageBased",
-        "type": "object",
+        "additionalProperties": false,
+        "definitions": {
+          "InitArgsRegistration": {
+            "description": "Registration init args.",
+            "properties": {
+              "reference_zarr_url": {
+                "title": "Reference Zarr Url",
+                "type": "string"
+              }
+            },
+            "required": [
+              "reference_zarr_url"
+            ],
+            "title": "InitArgsRegistration",
+            "type": "object"
+          }
+        },
         "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
           "init_args": {
             "$ref": "#/definitions/InitArgsRegistration",
-            "title": "Init_Args",
-            "description": "Intialization arguments provided by `image_based_registration_hcs_init`. They contain the reference_zarr_url that is used for registration."
+            "description": "Intialization arguments provided by `image_based_registration_hcs_init`. They contain the reference_zarr_url that is used for registration. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Init_Args"
           },
-          "wavelength_id": {
-            "title": "Wavelength Id",
-            "type": "string",
-            "description": "Wavelength that will be used for image-based registration; e.g. `A01_C01` for Yokogawa, `C01` for MD."
+          "level": {
+            "default": 2,
+            "description": "Pyramid level of the image to be used for registration. Choose `0` to process at full resolution.",
+            "title": "Level",
+            "type": "integer"
           },
           "roi_table": {
-            "title": "Roi Table",
             "default": "FOV_ROI_table",
-            "type": "string",
-            "description": "Name of the ROI table over which the task loops to calculate the registration. Examples: `FOV_ROI_table` => loop over the field of views, `well_ROI_table` => process the whole well as one image."
+            "description": "Name of the ROI table over which the task loops to calculate the registration. Examples: `FOV_ROI_table` => loop over the field of views, `well_ROI_table` => process the whole well as one image.",
+            "title": "Roi Table",
+            "type": "string"
           },
-          "level": {
-            "title": "Level",
-            "default": 2,
-            "type": "integer",
-            "description": "Pyramid level of the image to be used for registration. Choose `0` to process at full resolution."
+          "wavelength_id": {
+            "description": "Wavelength that will be used for image-based registration; e.g. `A01_C01` for Yokogawa, `C01` for MD.",
+            "title": "Wavelength Id",
+            "type": "string"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
           }
         },
         "required": [
@@ -1027,24 +1041,13 @@
           "init_args",
           "wavelength_id"
         ],
-        "additionalProperties": false,
-        "definitions": {
-          "InitArgsRegistration": {
-            "title": "InitArgsRegistration",
-            "description": "Registration init args.",
-            "type": "object",
-            "properties": {
-              "reference_zarr_url": {
-                "title": "Reference Zarr Url",
-                "type": "string"
-              }
-            },
-            "required": [
-              "reference_zarr_url"
-            ]
-          }
-        }
+        "title": "CalculateRegistrationImageBased",
+        "type": "object"
       },
+      "docs_info": "## image_based_registration_hcs_init\nInitialized calculate registration task\n\nThis task prepares a parallelization list of all zarr_urls that need to be\nused to calculate the registration between cycles (all zarr_urls except\nthe reference cycle vs. the reference cycle).\nThis task only works for HCS OME-Zarrs for 2 reasons: Only HCS OME-Zarrs\ncurrently have defined acquisition metadata to determine reference cycles.\nAnd we have only implemented the grouping of images for HCS OME-Zarrs by\nwell (with the assumption that every well just has 1 image per acqusition).\n## calculate_registration_image_based\nCalculate registration based on images\n\nThis task consists of 3 parts:\n\n1. Loading the images of a given ROI (=> loop over ROIs)\n2. Calculating the transformation for that ROI\n3. Storing the calculated transformation in the ROI table\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_non_parallel": "tasks/image_based_registration_hcs_init.py",
+      "executable_parallel": "tasks/calculate_registration_image_based.py",
       "meta_non_parallel": {
         "cpus_per_task": 1,
         "mem": 1000
@@ -1053,94 +1056,94 @@
         "cpus_per_task": 1,
         "mem": 8000
       },
-      "docs_info": "## image_based_registration_hcs_init\nInitialized calculate registration task\n\nThis task prepares a parallelization list of all zarr_urls that need to be\nused to calculate the registration between cycles (all zarr_urls except\nthe reference cycle vs. the reference cycle).\nThis task only works for HCS OME-Zarrs for 2 reasons: Only HCS OME-Zarrs\ncurrently have defined acquisition metadata to determine reference cycles.\nAnd we have only implemented the grouping of images for HCS OME-Zarrs by\nwell (with the assumption that every well just has 1 image per acqusition).\n## calculate_registration_image_based\nCalculate registration based on images\n\nThis task consists of 3 parts:\n\n1. Loading the images of a given ROI (=> loop over ROIs)\n2. Calculating the transformation for that ROI\n3. Storing the calculated transformation in the ROI table\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Calculate Registration (image-based)"
     },
     {
-      "name": "Find Registration Consensus",
-      "executable_non_parallel": "tasks/init_group_by_well_for_multiplexing.py",
-      "executable_parallel": "tasks/find_registration_consensus.py",
       "args_schema_non_parallel": {
-        "title": "InitGroupByWellForMultiplexing",
-        "type": "object",
+        "additionalProperties": false,
         "properties": {
-          "zarr_urls": {
-            "title": "Zarr Urls",
-            "type": "array",
-            "items": {
-              "type": "string"
-            },
-            "description": "List of paths or urls to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
+          "reference_cycle": {
+            "default": 0,
+            "description": "Which cycle to register against. Defaults to 0, which is the first OME-Zarr image in the well, usually the first cycle that was provided",
+            "title": "Reference Cycle",
+            "type": "integer"
           },
           "zarr_dir": {
+            "description": "path of the directory where the new OME-Zarrs will be created. Not used by this task. (standard argument for Fractal tasks, managed by Fractal server).",
             "title": "Zarr Dir",
-            "type": "string",
-            "description": "path of the directory where the new OME-Zarrs will be created. Not used by this task. (standard argument for Fractal tasks, managed by Fractal server)."
+            "type": "string"
           },
-          "reference_cycle": {
-            "title": "Reference Cycle",
-            "default": 0,
-            "type": "integer",
-            "description": "Which cycle to register against. Defaults to 0, which is the first OME-Zarr image in the well, usually the first cycle that was provided"
+          "zarr_urls": {
+            "description": "List of paths or urls to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "items": {
+              "type": "string"
+            },
+            "title": "Zarr Urls",
+            "type": "array"
           }
         },
         "required": [
           "zarr_urls",
           "zarr_dir"
         ],
-        "additionalProperties": false
+        "title": "InitGroupByWellForMultiplexing",
+        "type": "object"
       },
       "args_schema_parallel": {
-        "title": "FindRegistrationConsensus",
-        "type": "object",
-        "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Missing description"
-          },
-          "init_args": {
-            "$ref": "#/definitions/InitArgsRegistrationConsensus",
-            "title": "Init_Args",
-            "description": "Missing description"
-          },
-          "roi_table": {
-            "title": "Roi Table",
-            "default": "FOV_ROI_table",
-            "type": "string",
-            "description": "Name of the ROI table over which the task loops to calculate the registration. Examples: `FOV_ROI_table` => loop over the field of views, `well_ROI_table` => process the whole well as one image."
-          },
-          "new_roi_table": {
-            "title": "New Roi Table",
-            "type": "string",
-            "description": "Optional name for the new, registered ROI table. If no name is given, it will default to \"registered_\" + `roi_table`"
-          }
-        },
-        "required": [
-          "zarr_url",
-          "init_args"
-        ],
         "additionalProperties": false,
         "definitions": {
           "InitArgsRegistrationConsensus": {
-            "title": "InitArgsRegistrationConsensus",
             "description": "Registration consensus init args.",
-            "type": "object",
             "properties": {
               "zarr_url_list": {
-                "title": "Zarr Url List",
-                "type": "array",
                 "items": {
                   "type": "string"
-                }
+                },
+                "title": "Zarr Url List",
+                "type": "array"
               }
             },
             "required": [
               "zarr_url_list"
-            ]
+            ],
+            "title": "InitArgsRegistrationConsensus",
+            "type": "object"
+          }
+        },
+        "properties": {
+          "init_args": {
+            "$ref": "#/definitions/InitArgsRegistrationConsensus",
+            "description": "Intialization arguments provided by `init_group_by_well_for_multiplexing`. It contains the zarr_url_list listing all the zarr_urls in the same well as the zarr_url of the reference cycle that are being processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Init_Args"
+          },
+          "new_roi_table": {
+            "description": "Optional name for the new, registered ROI table. If no name is given, it will default to \"registered_\" + `roi_table`",
+            "title": "New Roi Table",
+            "type": "string"
+          },
+          "roi_table": {
+            "default": "FOV_ROI_table",
+            "description": "Name of the ROI table over which the task loops to calculate the registration. Examples: `FOV_ROI_table` => loop over the field of views, `well_ROI_table` => process the whole well as one image.",
+            "title": "Roi Table",
+            "type": "string"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. Refers to the zarr_url of the reference cycle. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
           }
-        }
+        },
+        "required": [
+          "zarr_url",
+          "init_args"
+        ],
+        "title": "FindRegistrationConsensus",
+        "type": "object"
       },
+      "docs_info": "## init_group_by_well_for_multiplexing\nFinds images for all acquisitions per well.\n\nReturns the parallelization_list to run `find_registration_consensus`.\n## find_registration_consensus\nApplies pre-calculated registration to ROI tables.\n\nApply pre-calculated registration such that resulting ROIs contain\nthe consensus align region between all cycles.\n\nParallelization level: well\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_non_parallel": "tasks/init_group_by_well_for_multiplexing.py",
+      "executable_parallel": "tasks/find_registration_consensus.py",
       "meta_non_parallel": {
         "cpus_per_task": 1,
         "mem": 1000
@@ -1149,118 +1152,114 @@
         "cpus_per_task": 1,
         "mem": 1000
       },
-      "docs_info": "## init_group_by_well_for_multiplexing\nFinds images for all acquisitions per well.\n\nReturns the parallelization_list to run `find_registration_consensus`.\n## find_registration_consensus\nApplies pre-calculated registration to ROI tables.\n\nApply pre-calculated registration such that resulting ROIs contain\nthe consensus align region between all cycles.\n\nParallelization level: well\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Find Registration Consensus"
     },
     {
-      "name": "Apply Registration to Image",
-      "input_types": {
-        "registered": false
-      },
-      "output_types": {
-        "registered": true
-      },
-      "executable_parallel": "tasks/apply_registration_to_image.py",
       "args_schema_parallel": {
-        "title": "ApplyRegistrationToImage",
-        "type": "object",
+        "additionalProperties": false,
         "properties": {
-          "zarr_url": {
-            "title": "Zarr Url",
-            "type": "string",
-            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "registered_roi_table": {
-            "title": "Registered Roi Table",
-            "type": "string",
-            "description": "Name of the ROI table which has been registered and will be applied to mask and shift the images. Examples: `registered_FOV_ROI_table` => loop over the field of views, `registered_well_ROI_table` => process the whole well as one image."
+          "overwrite_input": {
+            "default": true,
+            "description": "Whether the old image data should be replaced with the newly registered image data. Currently only implemented for `overwrite_input=True`.",
+            "title": "Overwrite Input",
+            "type": "boolean"
           },
           "reference_cycle": {
-            "title": "Reference Cycle",
             "default": "0",
-            "type": "string",
-            "description": "Which cycle to register against. Defaults to 0, which is the first OME-Zarr image in the well, usually the first cycle that was provided"
+            "description": "Which cycle to register against. Defaults to 0, which is the first OME-Zarr image in the well, usually the first cycle that was provided",
+            "title": "Reference Cycle",
+            "type": "string"
           },
-          "overwrite_input": {
-            "title": "Overwrite Input",
-            "default": true,
-            "type": "boolean",
-            "description": "Whether the old image data should be replaced with the newly registered image data. Currently only implemented for `overwrite_input=True`."
+          "registered_roi_table": {
+            "description": "Name of the ROI table which has been registered and will be applied to mask and shift the images. Examples: `registered_FOV_ROI_table` => loop over the field of views, `registered_well_ROI_table` => process the whole well as one image.",
+            "title": "Registered Roi Table",
+            "type": "string"
+          },
+          "zarr_url": {
+            "description": "Path or url to the individual OME-Zarr image to be processed. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Url",
+            "type": "string"
           }
         },
         "required": [
           "zarr_url",
           "registered_roi_table"
         ],
-        "additionalProperties": false
+        "title": "ApplyRegistrationToImage",
+        "type": "object"
+      },
+      "docs_info": "## apply_registration_to_image\nApply registration to images by using a registered ROI table\n\nThis task consists of 4 parts:\n\n1. Mask all regions in images that are not available in the\nregistered ROI table and store each cycle aligned to the\nreference_cycle (by looping over ROIs).\n2. Do the same for all label images.\n3. Copy all tables from the non-aligned image to the aligned image\n(currently only works well if the only tables are well & FOV ROI tables\n(registered and original). Not implemented for measurement tables and\nother ROI tables).\n4. Clean up: Delete the old, non-aligned image and rename the new,\naligned image to take over its place.\n\nParallelization level: image\n",
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_parallel": "tasks/apply_registration_to_image.py",
+      "input_types": {
+        "registered": false
       },
       "meta_parallel": {
         "cpus_per_task": 1,
         "mem": 4000
       },
-      "docs_info": "## apply_registration_to_image\nApply registration to images by using a registered ROI table\n\nThis task consists of 4 parts:\n\n1. Mask all regions in images that are not available in the\nregistered ROI table and store each cycle aligned to the\nreference_cycle (by looping over ROIs).\n2. Do the same for all label images.\n3. Copy all tables from the non-aligned image to the aligned image\n(currently only works well if the only tables are well & FOV ROI tables\n(registered and original). Not implemented for measurement tables and\nother ROI tables).\n4. Clean up: Delete the old, non-aligned image and rename the new,\naligned image to take over its place.\n\nParallelization level: image\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "name": "Apply Registration to Image",
+      "output_types": {
+        "registered": true
+      }
     },
     {
-      "name": "Import OME-Zarr",
-      "executable_non_parallel": "tasks/import_ome_zarr.py",
       "args_schema_non_parallel": {
-        "title": "ImportOmeZarr",
-        "type": "object",
+        "additionalProperties": false,
         "properties": {
-          "zarr_urls": {
-            "title": "Zarr Urls",
-            "type": "array",
-            "items": {
-              "type": "string"
-            },
-            "description": "List of paths or urls to the individual OME-Zarr image to be processed. Not used. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "zarr_dir": {
-            "title": "Zarr Dir",
-            "type": "string",
-            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server)."
-          },
-          "zarr_name": {
-            "title": "Zarr Name",
-            "type": "string",
-            "description": "The OME-Zarr name, without its parent folder. The parent folder is provided by zarr_dir; e.g. `zarr_name=\"array.zarr\"`, if the OME-Zarr path is in `/zarr_dir/array.zarr`."
+          "add_grid_ROI_table": {
+            "default": true,
+            "description": "Whether to add a `grid_ROI_table` table to each image, with the image split into a rectangular grid of ROIs.",
+            "title": "Add Grid Roi Table",
+            "type": "boolean"
           },
           "add_image_ROI_table": {
-            "title": "Add Image Roi Table",
             "default": true,
-            "type": "boolean",
-            "description": "Whether to add a `image_ROI_table` table to each image, with a single ROI covering the whole image."
+            "description": "Whether to add a `image_ROI_table` table to each image, with a single ROI covering the whole image.",
+            "title": "Add Image Roi Table",
+            "type": "boolean"
           },
-          "add_grid_ROI_table": {
-            "title": "Add Grid Roi Table",
-            "default": true,
-            "type": "boolean",
-            "description": "Whether to add a `grid_ROI_table` table to each image, with the image split into a rectangular grid of ROIs."
+          "grid_x_shape": {
+            "default": 2,
+            "description": "X shape of the ROI grid in `grid_ROI_table`.",
+            "title": "Grid X Shape",
+            "type": "integer"
           },
           "grid_y_shape": {
-            "title": "Grid Y Shape",
             "default": 2,
-            "type": "integer",
-            "description": "Y shape of the ROI grid in `grid_ROI_table`."
+            "description": "Y shape of the ROI grid in `grid_ROI_table`.",
+            "title": "Grid Y Shape",
+            "type": "integer"
           },
-          "grid_x_shape": {
-            "title": "Grid X Shape",
-            "default": 2,
-            "type": "integer",
-            "description": "X shape of the ROI grid in `grid_ROI_table`."
+          "overwrite": {
+            "default": false,
+            "description": "Whether new ROI tables (added when `add_image_ROI_table` and/or `add_grid_ROI_table` are `True`) can overwite existing ones.",
+            "title": "Overwrite",
+            "type": "boolean"
           },
           "update_omero_metadata": {
-            "title": "Update Omero Metadata",
             "default": true,
-            "type": "boolean",
-            "description": "Whether to update Omero-channels metadata, to make them Fractal-compatible."
+            "description": "Whether to update Omero-channels metadata, to make them Fractal-compatible.",
+            "title": "Update Omero Metadata",
+            "type": "boolean"
           },
-          "overwrite": {
-            "title": "Overwrite",
-            "default": false,
-            "type": "boolean",
-            "description": "Whether new ROI tables (added when `add_image_ROI_table` and/or `add_grid_ROI_table` are `True`) can overwite existing ones."
+          "zarr_dir": {
+            "description": "path of the directory where the new OME-Zarrs will be created. (standard argument for Fractal tasks, managed by Fractal server).",
+            "title": "Zarr Dir",
+            "type": "string"
+          },
+          "zarr_name": {
+            "description": "The OME-Zarr name, without its parent folder. The parent folder is provided by zarr_dir; e.g. `zarr_name=\"array.zarr\"`, if the OME-Zarr path is in `/zarr_dir/array.zarr`.",
+            "title": "Zarr Name",
+            "type": "string"
+          },
+          "zarr_urls": {
+            "description": "List of paths or urls to the individual OME-Zarr image to be processed. Not used. (standard argument for Fractal tasks, managed by Fractal server).",
+            "items": {
+              "type": "string"
+            },
+            "title": "Zarr Urls",
+            "type": "array"
           }
         },
         "required": [
@@ -1268,12 +1267,13 @@
           "zarr_dir",
           "zarr_name"
         ],
-        "additionalProperties": false
+        "title": "ImportOmeZarr",
+        "type": "object"
       },
       "docs_info": "## import_ome_zarr\nImport a single OME-Zarr into Fractal.\n\nThe single OME-Zarr can be a full OME-Zarr HCS plate or an individual\nOME-Zarr image. The image needs to be in the zarr_dir as specified by the\ndataset. The current version of this task:\n\n1. Creates the appropriate components-related metadata, needed for\n   processing an existing OME-Zarr through Fractal.\n2. Optionally adds new ROI tables to the existing OME-Zarr.\n",
-      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+      "docs_link": "https://fractal-analytics-platform.github.io/fractal-tasks-core",
+      "executable_non_parallel": "tasks/import_ome_zarr.py",
+      "name": "Import OME-Zarr"
     }
-  ],
-  "has_args_schemas": true,
-  "args_schema_version": "pydantic_v1"
+  ]
 }
diff --git a/fractal_tasks_core/dev/create_manifest.py b/fractal_tasks_core/dev/create_manifest.py
index 4accd56fa..477cc477d 100644
--- a/fractal_tasks_core/dev/create_manifest.py
+++ b/fractal_tasks_core/dev/create_manifest.py
@@ -16,80 +16,133 @@
 import logging
 from importlib import import_module
 from pathlib import Path
+from typing import Optional
 
 from fractal_tasks_core.dev.lib_args_schemas import (
     create_schema_for_single_task,
 )
 from fractal_tasks_core.dev.lib_task_docs import create_docs_info
-from fractal_tasks_core.task_list import TASK_LIST
 
-# from fractal_tasks_core.dev.lib_task_docs import create_docs_link
 
+def create_manifest(
+    package: str = "fractal_tasks_core",
+    manifest_version: str = "2",
+    has_args_schemas: bool = True,
+    args_schema_version: str = "pydantic_v1",
+    docs_link: Optional[str] = None,
+    custom_pydantic_models: Optional[list[tuple[str, str, str]]] = None,
+):
+    """
+    This function creates the package manifest based on a `task_list.py`
+    Python module located in the `dev` subfolder of the package, see an
+    example of such list at ...
 
-PACKAGE = "fractal_tasks_core"
-MANIFEST_VERSION = "2"
-HAS_ARGS_SCHEMAS = True
-ARGS_SCHEMA_VERSION = "pydantic_v1"
+    The manifest is then written to `__FRACTAL_MANIFEST__.json`, in the
+    main `package` directory.
 
+    Note: a valid example of `custom_pydantic_models` would be
+    ```
+    [
+        ("my_task_package", "some_module.py", "SomeModel"),
+    ]
+    ```
 
-if __name__ == "__main__":
+    Arguments:
+        package: The name of the package (must be importable).
+        manifest_version: Only `"2"` is supported.
+        has_args_schemas:
+            Whether to autogenerate JSON Schemas for task arguments.
+        args_schema_version:
+            Only `"pydantic_v1"` is currently supported in `fractal-server`
+            and `fractal-web`.
+        custom_pydantic_models:
+            Custom models to be included when building JSON Schemas for task
+            arguments.
+    """
+
+    # Preliminary check
+    if manifest_version != "2":
+        raise NotImplementedError(f"{manifest_version=} is not supported")
+
+    logging.info("Start generating a new manifest")
+
+    # Prepare an empty manifest
     manifest = dict(
-        manifest_version=MANIFEST_VERSION,
+        manifest_version=manifest_version,
         task_list=[],
-        has_args_schemas=HAS_ARGS_SCHEMAS,
-        args_schema_version=ARGS_SCHEMA_VERSION,
+        has_args_schemas=has_args_schemas,
     )
+    if has_args_schemas:
+        manifest["args_schema_version"] = args_schema_version
+
+    # Prepare a default value of docs_link
+    if package == "fractal_tasks_core" and docs_link is None:
+        docs_link = (
+            "https://fractal-analytics-platform.github.io/fractal-tasks-core"
+        )
 
+    # Import the task list from `dev/task_list.py`
+    task_list_module = import_module(f"{package}.dev.task_list")
+    TASK_LIST = getattr(task_list_module, "TASK_LIST")
+
+    # Loop over TASK_LIST, and append the proper task dictionary
+    # to manifest["task_list"]
     for task_obj in TASK_LIST:
+        # Convert Pydantic object to dictionary
         task_dict = task_obj.dict(
             exclude={"meta_init", "executable_init", "meta", "executable"},
             exclude_unset=True,
         )
+
+        # Copy some properties from `task_obj` to `task_dict`
         if task_obj.executable_non_parallel is not None:
             task_dict[
                 "executable_non_parallel"
             ] = task_obj.executable_non_parallel
         if task_obj.executable_parallel is not None:
             task_dict["executable_parallel"] = task_obj.executable_parallel
-
-        for step in ["non_parallel", "parallel"]:
-            executable = task_dict.get(f"executable_{step}")
-            if executable is None:
-                continue
-            # Create new JSON Schema for task arguments
-            logging.info(f"[{executable}] START")
-            schema = create_schema_for_single_task(executable, package=PACKAGE)
-            logging.info(f"[{executable}] END (new schema)")
-            task_dict[f"args_schema_{step}"] = schema
-
         if task_obj.meta_non_parallel is not None:
             task_dict["meta_non_parallel"] = task_obj.meta_non_parallel
         if task_obj.meta_parallel is not None:
             task_dict["meta_parallel"] = task_obj.meta_parallel
 
+        # Autogenerate JSON Schemas for non-parallel/parallel task arguments
+        if has_args_schemas:
+            for kind in ["non_parallel", "parallel"]:
+                executable = task_dict.get(f"executable_{kind}")
+                if executable is not None:
+                    logging.info(f"[{executable}] START")
+                    schema = create_schema_for_single_task(
+                        executable,
+                        package=package,
+                        custom_pydantic_models=custom_pydantic_models,
+                    )
+                    logging.info(f"[{executable}] END (new schema)")
+                    task_dict[f"args_schema_{kind}"] = schema
+
         # Update docs_info, based on task-function description
         docs_info = create_docs_info(
             executable_non_parallel=task_obj.executable_non_parallel,
             executable_parallel=task_obj.executable_parallel,
         )
-        # docs_link = create_docs_link(executable)
-        docs_link = (
-            "https://fractal-analytics-platform.github.io/fractal-tasks-core"
-        )
-        if docs_info:
+        if docs_info is not None:
             task_dict["docs_info"] = docs_info
-        if docs_link:
+        if docs_link is not None:
             task_dict["docs_link"] = docs_link
 
         manifest["task_list"].append(task_dict)
         print()
 
     # Write manifest
-    imported_package = import_module(PACKAGE)
+    imported_package = import_module(package)
     manifest_path = (
         Path(imported_package.__file__).parent / "__FRACTAL_MANIFEST__.json"
     )
     with manifest_path.open("w") as f:
-        json.dump(manifest, f, indent=2)
+        json.dump(manifest, f, indent=2, sort_keys=True)
         f.write("\n")
-    logging.info(f"Up-to-date manifest stored in {manifest_path.as_posix()}")
+    logging.info(f"Manifest stored in {manifest_path.as_posix()}")
+
+
+if __name__ == "__main__":
+    create_manifest()
diff --git a/fractal_tasks_core/task_list.py b/fractal_tasks_core/dev/task_list.py
similarity index 100%
rename from fractal_tasks_core/task_list.py
rename to fractal_tasks_core/dev/task_list.py
diff --git a/poetry.lock b/poetry.lock
index 83c9c1257..6353417c2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -3831,79 +3831,80 @@ ptyprocess = ">=0.5"
 
 [[package]]
 name = "pillow"
-version = "10.2.0"
+version = "10.3.0"
 description = "Python Imaging Library (Fork)"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
-    {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"},
-    {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"},
-    {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"},
-    {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"},
-    {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"},
-    {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"},
-    {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"},
-    {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"},
-    {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"},
-    {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"},
-    {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"},
-    {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"},
-    {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"},
-    {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"},
-    {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"},
-    {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"},
-    {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"},
-    {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"},
-    {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"},
-    {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"},
-    {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"},
-    {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"},
-    {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"},
-    {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"},
-    {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"},
-    {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"},
-    {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"},
-    {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"},
-    {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"},
-    {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"},
-    {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"},
-    {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"},
-    {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"},
-    {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"},
-    {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"},
-    {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"},
-    {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"},
-    {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"},
-    {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"},
-    {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"},
-    {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"},
-    {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"},
-    {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"},
-    {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"},
-    {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"},
-    {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"},
-    {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"},
-    {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"},
-    {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"},
-    {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"},
-    {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"},
-    {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"},
-    {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"},
-    {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"},
-    {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"},
-    {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"},
-    {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"},
-    {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"},
-    {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"},
-    {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"},
-    {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"},
-    {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"},
-    {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"},
-    {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"},
-    {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"},
-    {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"},
-    {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"},
+    {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
+    {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
+    {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
+    {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
+    {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
+    {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
+    {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
+    {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
+    {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
+    {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
+    {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
+    {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
+    {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
+    {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
+    {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
+    {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
+    {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
+    {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
+    {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
+    {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
+    {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
+    {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
+    {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
+    {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
+    {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
+    {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
+    {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
+    {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
+    {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
+    {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
+    {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
+    {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
+    {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
+    {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
+    {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
+    {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
+    {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
+    {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
+    {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
+    {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
+    {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
+    {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
+    {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
+    {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
+    {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
+    {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
+    {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
+    {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
+    {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
+    {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
+    {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
+    {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
+    {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
+    {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
+    {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
+    {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
+    {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
 ]
 
 [package.extras]
@@ -4457,7 +4458,6 @@ files = [
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
     {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
     {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
-    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
     {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
     {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
     {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},


Test patch:
diff --git a/tests/tasks/skip_test_valid_args_schemas.py b/tests/tasks/test_valid_args_schemas.py
similarity index 68%
rename from tests/tasks/skip_test_valid_args_schemas.py
rename to tests/tasks/test_valid_args_schemas.py
index 533ffad2d..0bbd7a298 100644
--- a/tests/tasks/skip_test_valid_args_schemas.py
+++ b/tests/tasks/test_valid_args_schemas.py
@@ -102,10 +102,13 @@ def test_task_functions_have_valid_signatures():
     """
     Test that task functions have valid signatures.
     """
-    for ind_task, task in enumerate(TASK_LIST):
-        function_name = Path(task["executable"]).with_suffix("").name
-        task_function = _extract_function(task["executable"], function_name)
-        _validate_function_signature(task_function)
+    for task in TASK_LIST:
+        for key in ["executable_non_parallel", "executable_parallel"]:
+            value = task.get(key, None)
+            if value is not None:
+                function_name = Path(task[key]).with_suffix("").name
+                task_function = _extract_function(task[key], function_name)
+                _validate_function_signature(task_function)
 
 
 def test_args_schemas_are_up_to_date():
@@ -113,14 +116,22 @@ def test_args_schemas_are_up_to_date():
     Test that args_schema attributes in the manifest are up-to-date
     """
     for ind_task, task in enumerate(TASK_LIST):
-        print(f"Now handling {task['executable']}")
-        old_schema = TASK_LIST[ind_task]["args_schema"]
-        new_schema = create_schema_for_single_task(task["executable"])
-        # The following step is required because some arguments may have a
-        # default which has a non-JSON type (e.g. a tuple), which we need to
-        # convert to JSON type (i.e. an array) before comparison.
-        new_schema = json.loads(json.dumps(new_schema))
-        assert new_schema == old_schema
+        for kind in ["non_parallel", "parallel"]:
+            key = f"executable_{kind}"
+            value = task.get(key, None)
+            if value is not None:
+                print(f"Now handling {task[key]}")
+                old_schema = TASK_LIST[ind_task].get(
+                    f"args_schema_{kind}", None
+                )
+                assert old_schema is not None
+                new_schema = create_schema_for_single_task(task[key])
+                # The following step is required because some arguments may
+                # have a default which has a non-JSON type (e.g. a tuple),
+                # which we need to convert to JSON type (i.e. an array) before
+                # comparison.
+                new_schema = json.loads(json.dumps(new_schema))
+                assert new_schema == old_schema
 
 
 @pytest.mark.parametrize(
@@ -132,14 +143,18 @@ def test_args_schema_comply_with_jsonschema_specs(jsonschema_validator):
     This test is actually useful, see
     https://github.com/fractal-analytics-platform/fractal-tasks-core/issues/564.
     """
-    for ind_task, task in enumerate(TASK_LIST):
-        schema = TASK_LIST[ind_task]["args_schema"]
-        my_validator = jsonschema_validator(schema=schema)
-        my_validator.check_schema(my_validator.schema)
-        print(
-            f"Schema for task {task['executable']} is valid for "
-            f"{jsonschema_validator}."
-        )
+    for task in TASK_LIST:
+        for kind in ["non_parallel", "parallel"]:
+            key = f"executable_{kind}"
+            value = task.get(key, None)
+            if value is not None:
+                schema = task[f"args_schema_{kind}"]
+                my_validator = jsonschema_validator(schema=schema)
+                my_validator.check_schema(my_validator.schema)
+                print(
+                    f"Schema for task {task[key]} is valid for "
+                    f"{jsonschema_validator}."
+                )
 
 
 def test_args_title():
@@ -155,7 +170,9 @@ def test_args_title():
     cellpose_task = next(
         task for task in TASK_LIST if task["name"] == "Cellpose Segmentation"
     )
-    new_schema = create_schema_for_single_task(cellpose_task["executable"])
+    new_schema = create_schema_for_single_task(
+        cellpose_task["executable_parallel"]
+    )
     properties = new_schema["properties"]
     # Standard task argument
     level_prop = properties["level"]
@@ -166,13 +183,13 @@ def test_args_title():
     debug(channel2_prop)
     assert channel2_prop["title"] == "Channel2"
 
-    create_ome_zarr_task = next(
+    cellvoyager_task = next(
         task
         for task in TASK_LIST
-        if task["name"] == "Create OME-Zarr structure"
+        if task["name"] == "Convert Cellvoyager to OME-Zarr"
     )
     new_schema = create_schema_for_single_task(
-        create_ome_zarr_task["executable"]
+        cellvoyager_task["executable_non_parallel"]
     )
     definitions = new_schema["definitions"]
     omero_channel_def = definitions["OmeroChannel"]
diff --git a/tests/tasks/test_valid_task_interface.py b/tests/tasks/test_valid_task_interface.py
index f55fd8dfd..260b6b726 100644
--- a/tests/tasks/test_valid_task_interface.py
+++ b/tests/tasks/test_valid_task_interface.py
@@ -4,12 +4,11 @@
 from shlex import split as shlex_split
 from subprocess import PIPE
 
+import pytest
 from devtools import debug
 
 import fractal_tasks_core
 
-# import pytest
-
 
 def validate_command(cmd: str):
     """
@@ -40,19 +39,23 @@ def validate_command(cmd: str):
     manifest_dict = json.load(fin)
 
 
-# @pytest.mark.parametrize("task", manifest_dict["task_list"])
-# def test_task_interface(task, tmp_path):
-
-#     tmp_file_args = str(tmp_path / "args.json")
-#     tmp_file_metadiff = str(tmp_path / "metadiff.json")
-#     with open(tmp_file_args, "w") as fout:
-#         args = dict(wrong_arg_1=123, wrong_arg_2=[1, 2, 3])
-#         json.dump(args, fout, indent=4)
-
-#     task_path = (module_dir / task["executable"]).as_posix()
-#     cmd = (
-#         f"python {task_path} "
-#         f"-j {tmp_file_args} "
-#         f"--metadata-out {tmp_file_metadiff}"
-#     )
-#     validate_command(cmd)
+@pytest.mark.parametrize("task", manifest_dict["task_list"])
+def test_task_interface(task, tmp_path):
+
+    tmp_file_args = str(tmp_path / "args.json")
+    tmp_file_metadiff = str(tmp_path / "metadiff.json")
+    with open(tmp_file_args, "w") as fout:
+        args = dict(wrong_arg_1=123, wrong_arg_2=[1, 2, 3])
+        json.dump(args, fout, indent=4)
+
+    for key in ["executable_non_parallel", "executable_parallel"]:
+        value = task.get(key, None)
+        if value is None:
+            continue
+        task_path = (module_dir / value).as_posix()
+        cmd = (
+            f"python {task_path} "
+            f"--args-json {tmp_file_args} "
+            f"--out-json {tmp_file_metadiff}"
+        )
+        validate_command(cmd)
diff --git a/tests/test_valid_manifest.py b/tests/test_valid_manifest.py
index b337e7e79..238874581 100644
--- a/tests/test_valid_manifest.py
+++ b/tests/test_valid_manifest.py
@@ -1,40 +1,33 @@
 import json
 from pathlib import Path
 
-import pytest
 import requests
-from devtools import debug
 from jsonschema import validate
 
 import fractal_tasks_core
 
 
-@pytest.mark.xfail(
-    reason=(
-        "See https://github.com/fractal-analytics-platform/"
-        "fractal-server/issues/1347"
-    )
-)
 def test_valid_manifest(tmp_path):
     """
     NOTE: to avoid adding a fractal-server dependency, we simply download the
     relevant file.
     """
-    # Download JSON Schema for ManifestV1
+    # Download JSON Schema for ManifestV2
     url = (
         "https://raw.githubusercontent.com/fractal-analytics-platform/"
         "fractal-server/main/"
-        "fractal_server/app/schemas/json_schemas/manifest.json"
+        "fractal_server/json_schemas/manifest_v2.json"
     )
     r = requests.get(url)
-    with (tmp_path / "manifest_schema.json").open("wb") as f:
+    with (tmp_path / "manifest_schema_v2.json").open("wb") as f:
         f.write(r.content)
-    with (tmp_path / "manifest_schema.json").open("r") as f:
+    with (tmp_path / "manifest_schema_v2.json").open("r") as f:
         manifest_schema = json.load(f)
 
+    # Load tasks-package manifest
     module_dir = Path(fractal_tasks_core.__file__).parent
     with (module_dir / "__FRACTAL_MANIFEST__.json").open("r") as fin:
         manifest_dict = json.load(fin)
 
-    debug(manifest_dict)
+    # Validate manifest against JSON Schema
     validate(instance=manifest_dict, schema=manifest_schema)