diff --git a/docs/api-script-pyramids.md b/docs/api-script-pyramids.md deleted file mode 100644 index 6b9bec5..0000000 --- a/docs/api-script-pyramids.md +++ /dev/null @@ -1 +0,0 @@ -:::scripts.pyramids.create_pyramids \ No newline at end of file diff --git a/docs/guide-create-pyramids.md b/docs/guide-create-pyramids.md index daa46a8..69df0d8 100644 --- a/docs/guide-create-pyramids.md +++ b/docs/guide-create-pyramids.md @@ -1,33 +1,49 @@ # Create pyramidal OME-TIFF -This page will guide you to use the `create_pyramids` script, in the event the CZI file does not work directly in QuPath. The script will generate [pyramids](tips-formats.md#pyramids) from OME-TIFF files exported from ZEN. +This page will guide you to use the `pyramid-creator` package, in the event the CZI file does not work directly in QuPath. The script will generate [pyramids](tips-formats.md#pyramids) from OME-TIFF files exported from ZEN. !!! tip inline end - The `create_pyramids.py` script can also pyramidalize images using Python only with the `--no-use-qupath` option, but I find it slower and less reliable. + `pyramid-creator` can also pyramidalize images using Python only with the `--no-use-qupath` option. This Python script uses QuPath under the hood, via a companion script called `createPyramids.groovy`. It will find the OME-TIFF files and make QuPath run the groovy script on it, in console mode (without graphical user interface). This script is standalone, eg. it does not rely on the `histoquant` package. But installing the later makes sure all dependencies are installed (namely `typer` and `tqdm` with the QuPath backend and quite a few more for the Python backend). +`pyramid-creator` moved to a standalone package that you can find [here](https://github.com/TeamNCMC/pyramid-creator#pyramid_creator) with [installation](https://github.com/TeamNCMC/pyramid-creator#install) and [usage](https://github.com/TeamNCMC/pyramid-creator#usage) instructions. + ## Installation -You will need a virtual environment with the required dependencies. +You will find instructions on the dedicated project page over at [Github](https://github.com/TeamNCMC/pyramid-creator#pyramid_creator). + +For reference : + +You will need `conda`, follow [those instructions](main-getting-started.md#python-virtual-environment-manager-conda) to install it. -Follow [those instructions](main-getting-started.md#python-virtual-environment-manager-conda) to install miniconda3 if you didn't already. +Then, create a virtual environment if you didn't already (`pyramid-creator` can be installed in the environment for `histoquant`) and install the [`pyramid-creator`](https://github.com/TeamNCMC/pyramid-creator) package. +```bash +conda create -c conda-forge -n hq python=3.12 # not required if you already create an environment +conda activate hq +pip install pyramid-creator +``` +To use the Python backend (with `tifffile`), replace the last line with : +``` +pip install pyramid-creator[python-backend] +``` +To use the QuPath backend, a working QuPath installation is required, and the `pyramid-creator` command needs to be aware of its location. + +To do so, first, install [QuPath](https://qupath.github.io). By default, it will install in `~\AppData\QuPath-0.X.Y`. In any case, note down the installation location. + +Then, you have several options : +- Create a file in your user directory called "QUPATH_PATH" (without extension), containing the full path to the QuPath console executable. In my case, it reads : `C:\Users\glegoc\AppData\Local\QuPath-0.5.1\QuPath-0.5.1 (console).exe`. Then, the `pyramid-creator` script will read this file to find the QuPath executable. +- Specify the QuPath path as an option when calling the command line interface (see the [Usage](#usage) section) : +```bash +pyramid-creator /path/to/your/images --qupath-path "C:\Users\glegoc\AppData\Local\QuPath-0.5.1\QuPath-0.5.1 (console).exe" +``` +- Specify the QuPath path as an option when using the package in a Python script (see the [Usage](#usage) section) : +```python +from pyramid_creator import pyramidalize_directory +pyramidalize_directory("/path/to/your/images/", qupath_path="C:\Users\glegoc\AppData\Local\QuPath-0.5.1\QuPath-0.5.1 (console).exe") +``` +- If you're using Windows, using QuPath v0.6.0, v0.5.1 or v0.5.0 and chose the default installation location, `pyramid-creator` *should* find it automatically and write it down in the "QUPATH_PATH" file by itself. -Then, install the required dependencies. -=== "Recommended" - Install the `histoquant` package by following [those instructions](main-getting-started.md#installation). -=== "Minimal" - Alternatively, if you don't plan to use the `histoquant` package, you can create a minimal conda environment with only the libraries required for `create_pyramids.py`. - ```bash title="With QuPath backend" - conda create -n hq python=3.12 - conda activate hq - pip install typer tqdm - ``` - ```bash title="With Python backend" - conda create -n hq python=3.12 - conda activate hq - pip install typer tqdm numpy tifffile scikit-image - ``` ## Export CZI to OME-TIFF [OME-TIFF](https://ome-model.readthedocs.io/en/stable/ome-tiff/) is a specification of the TIFF image format. It specifies how the metadata should be written to the file to be interoperable between softwares. ZEN can export to OME-TIFF so you don't need to pay attention to [metadata](tips-formats.md#metadata). Therefore, you won't need to specify pixel size and channels names and colors as it will be read directly from the OME-TIFF files. !!! example "" @@ -47,48 +63,4 @@ Then, install the required dependencies. The OME-TIFF files should be ready to be pyramidalized with the `create_pyramids.py` script. ## Usage -The script is located under `scripts/pyramids`. Copy the two files (.py and .groovy) elsewhere on your computer. - -To use the QuPath backend (recommended), you need to set its path in the script. To do so, open the `create_pyramids.py` file with a text editor (Notepad or [vscode](https://code.visualstudio.com/) to get nice syntax coloring). -Locate the `QUPATH_PATH` line : -``` python linenums="51" hl_lines="2" ---8<-- "scripts/pyramids/create_pyramids.py:51:54" -``` - -!!! info inline end - The AppData directory is hidden by default. In the file explorer, you can go to the "View" tab and check "Hidden items" under "Show/hide". -And replace the path to the "QuPath-0.X.Y (console).exe" executable. QuPath should be installed in `C:\Users\USERNAME\AppData\Local\QuPath-0.X.Y\` by default. -Save the file. Then run the script on your images : - -!!! example "" - 1. Open a terminal (PowerShell) so that it can find the `create_pyramids.py` script, by either : - - open PowerShell from the start menu, then browse to the location of your script: - ```bash - cd /path/to/your/scripts - ``` - - From the file explorer, browse to where the script is and in an empty space, ++shift+right-button++ to "Open PowerShell window here" - 2. Activate the virtual environment : - ```bash - conda activate hq - ``` - 3. Copy the path to your OME-TIFF images (for example "D:\Data\Histo\NiceMouseName\NiceMouseName-tiff\") - 4. In the terminal, run the script on your images : - ```bash - python create_pyramids.py "D:\Data\Histo\NiceMouseName\NiceMouseName-tiff\" - ``` - !!! warning - Make sure to use **double quotes** when specifying the path (**"**D:\some\path**"**), because if there are whitespaces in it, each whitespace-separated bits will be parsed as several arguments for the script. - -!!! tip - `create_pyramids.py` can behave like a command line interface. In the event you would need to modify the default values used in the script (tile size and the like), you can either edit the script or, preferably, use options when calling the script like so : - ```bash - python create_pyramids.py --OPTION VALUE /path/to/images - ``` - Learn more by asking for help : - ```bash - python create_pyramids.py --help - ``` - -Upon completion, this will create a subdirectory "pyramidal" next to your OME-TIFF files where you will find the pyramidal images ready to be used in QuPath and ABBA. You can safely delete the original OME-TIFF exported from ZEN. - -You can check the API documentation for this script [here](api-script-pyramids.md). \ No newline at end of file +See the instructions on the dedicated project page over at [Github](https://github.com/TeamNCMC/pyramid-creator#pyramid_creator). diff --git a/docs/guide-install-abba.md b/docs/guide-install-abba.md index 3713b44..1c728a4 100644 --- a/docs/guide-install-abba.md +++ b/docs/guide-install-abba.md @@ -35,8 +35,8 @@ ABBA should be installed and functional ! You can check the [official documentat To be able to leverage those atlases, we need to make ImageJ and Python be able to talk to each other. This is the purpose of [abba_python](https://github.com/BIOP/abba_python), that will install ImageJ and its ABBA plugins *inside* a python environment, with bindings between the two worlds. -### Install conda -If not done already, follow [those instructions](main-getting-started.md#python-virtual-environment-manager-conda) to install miniconda3. +### Install `conda` +If not done already, follow [those instructions](main-getting-started.md#python-virtual-environment-manager-conda) to install `conda`. ### Install abba_python in a virtual environment 1. Open a terminal (PowerShell). diff --git a/docs/images/anaconda-licences.png b/docs/images/anaconda-licences.png new file mode 100644 index 0000000..3ce4a23 Binary files /dev/null and b/docs/images/anaconda-licences.png differ diff --git a/docs/main-getting-started.md b/docs/main-getting-started.md index ee19010..8b2a9ea 100644 --- a/docs/main-getting-started.md +++ b/docs/main-getting-started.md @@ -1,10 +1,10 @@ # Getting started ## Quick start -1. Install QuPath, ABBA and miniconda3. +1. Install QuPath, ABBA and conda. 2. Create an environment : ``` -conda create -c conda-forge -n hq python=3.12 pytables +conda create -c conda-forge -n hq python=3.12 ``` 3. Activate it : ``` @@ -35,15 +35,23 @@ This is where you'll create QuPath projects, in which you'll be able to browse y ### Aligning Big Brain and Atlases (ABBA) This is the tool you'll use to register 2D histological sections to 3D atlases. See the [dedicated page](guide-install-abba.md). -### Python virtual environment manager (conda) +### Python virtual environment manager (`conda`) The `histoquant` package is written in Python. It depends on scientific libraries (such as [NumPy](https://numpy.org/), [pandas](https://pandas.pydata.org/) and many more). Those libraries need to be installed in versions that are compatible with each other and with `histoquant`. To make sure those versions do not conflict with other Python tools you might be using (`deeplabcut`, `abba_python`, ...), we will install `histoquant` and its dependencies in a dedicated *virtual environment*. -[conda](https://docs.conda.io/en/latest/) is a software that takes care of this. It comes with a "base" environment, from which we will create and manage other environments. It is included with the Anaconda distribution, but the latter is bloated : its "base" environment already contains tons of libraries, and tends to self-destruct at some point (eg. becomes unable to resolve the inter-dependencies), which makes you unable to install new libraries nor create new environments. +[`conda`](https://docs.conda.io/en/latest/) is a software that takes care of this. It comes with a "base" environment, from which we will create and manage other, project-specific environments. It is also used to download and install python in each of those environments, as well as third-party libraries. `conda` in itself is free and open-source and can be used freely by anyone. -This is why it is *highly* recommended to install miniconda3 instead, a minimal installer for conda : +It is included with the Anaconda distribution, which is subject to [specific terms of service](https://www.anaconda.com/blog/update-on-anacondas-terms-of-service-for-academia-and-research), which state that unless you're an individual, a member of a company with less than 200 employees or a member of an university (but *not* a national research lab) it's free to use, otherwise, you need to pay a licence. `conda`, while being free, is by default configured to use the "defaults" channel to fetch the packages (including Python itself), a repository operated by Anaconda, which is, itself, subject to the Anaconda terms of service. + +In contrast, conda-forge is a community-run repository that contains more numerous and more update-to-date packages. This is free to use for anyone. The idea is to use `conda` directly (instead of Anaconda graphical interface) and download packages from conda-forge (instead of the Anaconda-run defaults). To try to decipher this mess, Anaconda provides this figure : + +![Anaconda terms of service](images/anaconda-licences.png) + +Furthermore, the "base" conda environment installed with the Anaconda distribution is bloated and already contains tons of libraries, and tends to self-destruct at some point (eg. becomes unable to resolve the inter-dependencies), which makes you unable to install new libraries nor create new environments. + +This is why it is *highly* recommended to install Miniconda instead, a minimal installer for conda, and configure it to use the free, community-run channel conda-forge, or, even better, use Miniforge which is basically the same but pre-configured to use conda-forge. The only downside is that will not get the Anaonda graphical user interface and you'll need to use the terminal instead, but worry not ! We got you covered. !!! example "" - 1. Download and install [miniconda3](https://repo.anaconda.com/miniconda/) (choose the "latest" version for your system). During the installation, choose to install for the current user, add conda to PATH and make python the default interpreter. + 1. Download and install [Miniforge](https://conda-forge.org/download/) (choose the latest release for your system). During the installation, choose to install for the current user, add conda to PATH and make python the default interpreter. 2. Open a terminal (PowerShell in Windows). Run : ```bash conda init @@ -52,22 +60,13 @@ This is why it is *highly* recommended to install miniconda3 instead, a minimal ```bash (base) PS C:\Users\myname> ``` - 3. Run : - ```bash - conda config --add channels conda-forge - ``` - Then : - ```bash - conda config --set channel_priority flexible - ``` - This will make conda download the packages from the "conda-forge" online repository, which is more complete and up-to-date. The flag `-c conda-forge` in the subsequent instructions won't be necessary anymore. !!! tip - If Anaconda is already installed and you don't have the rights to uninstall it, you'll have to use it instead. You can launch the "Anaconda Prompt (PowerShell)", run `conda init` and follow the same instructions below (and hope it won't break in the foreseeable future). + If Anaconda is already installed and you don't have the rights to uninstall it, you'll have to use it instead. You can launch the "Anaconda Prompt (PowerShell)", run `conda init`. Open a regular PowerShell window and run `conda config --add channels conda-forge`, so that subsequent installations and environments creation will fetch required dependencies from conda-forge. ### Installation This section explains how to actually install the `histoquant` package. -The following commands should be run from a terminal (PowerShell). Remember that the `-c conda-forge` bits are not necessary if you did the step 3. above. +The following commands should be run from a terminal (PowerShell). Remember that the `-c conda-forge` bits are not necessary if you installed conda with the miniforge distribution. !!! example "" 1. Create a virtual environment with python 3.12 and some libraries: diff --git a/mkdocs.yml b/mkdocs.yml index 8c540d9..061a816 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,7 +57,6 @@ nav: - histoquant.io: api-io.md - histoquant.seg: api-seg.md - Scripts reference: - - create_pyramids: api-script-pyramids.md - segment_images: api-script-segment.md - qupath_script_runner: api-script-qupath-script-runner.md - main-getting-help.md diff --git a/pyproject.toml b/pyproject.toml index 66ad841..f799b79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "seaborn>=0.13.2", "shapely>=2.0.4", "skan>=0.12.0", + "tables>=3.10.1", "tifffile>=2024.4.24", "tqdm", "typer", @@ -51,9 +52,11 @@ doc = [ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" +[tool.hatch.build] +include = [ + "histoquant" +] + [tool.ruff] line-length = 88 -extend-include = ["*.ipynb"] - -[tool.setuptools.packages.find] -include = ["histoquant"] +extend-include = ["*.ipynb"] \ No newline at end of file diff --git a/scripts/pyramids/createPyramids.groovy b/scripts/pyramids/createPyramids.groovy deleted file mode 100644 index cfaff9a..0000000 --- a/scripts/pyramids/createPyramids.groovy +++ /dev/null @@ -1,44 +0,0 @@ -/** - * createPyramids.groovy - * - * Convert OME-TIFF to pyramidal OME-TIFF, using QuPath. - * See 'create_pyramids.py' python script to call this script in batch from python. - * - * author : Guillaume Le Goc (g.legoc@posteo.org) @ NeuroPSI - * version : 2024.08.30 - */ - -// --- Parameters -def compression = OMEPyramidWriter.CompressionType.LZW // compression (lossless) - -// parse input arguments -def uid = args[0] -def tileSize = args[1] as int // TIFF tile size -def pyramidScaling = args[2] as int // factor between two consecutive pyramid level -def nThreads = args[3] as int // number of threads for parallelization - -// --- Preparation - -// get QuPath image server -def server = getCurrentServer() - -// define input and output file names -def imagePath = server.getBuilder().getURIs()[0].getPath() -File imageFile = new File(imagePath) -def newImageName = uid + '_' + imageFile.getName().toString() -def outputPath = new File(imageFile.getParent().toString(), newImageName).toString() - -// --- Write the new pyramidal tiff -new OMEPyramidWriter.Builder(server) - .parallelize(nThreads) - .tileSize(tileSize) - .scaledDownsampling(1, pyramidScaling) // 1 : full resolution - .compression(compression) - .build() - .writePyramid(outputPath) - -// --- Imports -import qupath.lib.images.writers.ome.OMEPyramidWriter -import qupath.lib.images.servers.* -import javax.imageio.* -import qupath.lib.images.servers.ImageServerMetadata diff --git a/scripts/pyramids/create_pyramids.py b/scripts/pyramids/create_pyramids.py deleted file mode 100644 index 73a78f1..0000000 --- a/scripts/pyramids/create_pyramids.py +++ /dev/null @@ -1,378 +0,0 @@ -""" -create_pyramids command line interface (CLI). -You can set up your settings filling the variables at the top of the file and run the -script : -> python create_pyramids.py /path/to/your/images - -Or alternatively, you can run the script as a CLI : -> python create_pyramids.py [options] /path/to/your/images - -Example : -> python create_pyramids.py --tile-size 1024 --pyramid-factor 4 /path/to/your/images - -To get help (eg. list all options), run : -> python create_pyramids.py --help - -To use the QuPath backend, you'll need the companion 'createPyramids.groovy' script. - -author : Guillaume Le Goc (g.legoc@posteo.org) @ NeuroPSI -version : 2024.11.19 - -""" - -import math -import multiprocessing -import os -import subprocess -import sys -import uuid -import warnings -from typing import Optional - -import typer -from tqdm import tqdm -from typing_extensions import Annotated - -__version__ = 1.2 - -# --- Parameters (default values) -TILE_SIZE: int = 512 -"""Tile size (usually 512 or 1024).""" -PYRAMID_FACTOR: int = 2 -"""Factor between two consecutive pyramid levels.""" -NTHREADS: int = int(multiprocessing.cpu_count() / 2) -"""Number of threads for parallelization.""" - -# --- QuPath backend parameters (default values) -USE_QUPATH: bool = True -"""Use QuPath and the external groovy script instead of pure python (more reliable).""" -SCRIPT_PATH: str = os.path.join(os.path.dirname(__file__), "createPyramids.groovy") -"""Full path to the groovy script that does the job.""" -QUPATH_PATH: str = ( - "C:/Users/glegoc/AppData/Local/QuPath-0.5.1/QuPath-0.5.1 (console).exe" -) -"""Full path to the QuPath (console) executable.""" -PYRAMID_MAX: int = 32 -"""Maximum rescaling (smaller pyramid).""" - -INEXT: str = "ome.tiff" -"""Input files extension.""" -COMPRESSION_PYTHON: str = "LZW" -"""Compression method.""" - - -# --- Typer functions -def version_callback(value: bool): - if value: - print(f"create-pyramids CLI version : {__version__}") - raise typer.Exit() - - -# --- Processing functions -def pyramidalize_qupath( - image_path: str, - output_image: str, - qupath_path: str, - script_path: str, - tile_size: int, - pyramid_factor: int, - nthreads: int, -): - """ - Pyramidalization with QuPath backend. - - """ - # generate an uid to make sure to not overwrite original file - uid = uuid.uuid1().hex - - # prepare image names - imagename = os.path.basename(image_path) - inputdir = os.path.dirname(image_path) - new_imagename = uid + "_" + imagename - new_imagepath = os.path.join(inputdir, new_imagename) - - # prepare arguments - args = "[" f"{uid}," f"{tile_size}," f"{pyramid_factor}," f"{nthreads}" "]" - - # call the qupath groovy script within a shell - subprocess.run( - [qupath_path, "script", script_path, "-i", image_path, "--args", args], - shell=True, - stdout=subprocess.DEVNULL, - ) - - if not os.path.isfile(new_imagepath): - raise FileNotFoundError( - "QuPath did not manage to create the pyramidalized image." - ) - - # move the pyramidalized image in the output directory - os.rename(new_imagepath, output_image) - - -def pyramidalize_python( - image_path: str, output_image: str, levels: list | tuple, tiffoptions: dict -): - """ - Pyramidalization with tifffile and scikit-image. - - Parameters - ---------- - image_path : str - Full path to the image. - output_image : str - Full path to the pyramidalized image. - levels : list-like of int - Pyramids levels. - tiffoptions : dict - Options for TiffWriter. - """ - # specific imports - import xml.etree.ElementTree as ET - - import numpy as np - import tifffile - from skimage import transform - - # Nested functions - def get_pixelsize_ome( - desc: str, - namespace: dict = {"ome": "http://www.openmicroscopy.org/Schemas/OME/2016-06"}, - ) -> float: - """ - Extract physical pixel size from OME-XML description. - - Raise a warning if pixels are anisotropic (eg. X and Y sizes are not the same). - Raise an error if size units are not microns ("µm"). - - Parameters - ---------- - desc : str - OME-XML string from Tiff page. - namespace : dict, optional - XML namespace, defaults to latest OME-XML schema (2016-06). - - Returns - ------- - pixelsize : float - Physical pixel size. - - """ - root = ET.fromstring(desc) - - for pixels in root.findall(".//ome:Pixels", namespace): - pixelsize_x = float(pixels.get("PhysicalSizeX")) - pixelsize_y = float(pixels.get("PhysicalSizeY")) - break # stop at first Pixels field in the XML - - # sanity checks - if pixelsize_x != pixelsize_y: - warnings.warn( - f"Anisotropic pixels size found, are you sure ? ({pixelsize_x}, {pixelsize_y})" - ) - - return np.mean([pixelsize_x, pixelsize_y]) - - def im_downscale(img, downfactor, **kwargs): - """ - Downscale an image by the given factor. - - Wrapper for `skimage.transform.rescale`. - - Parameters - ---------- - img : np.ndarray - downfactor : int or float - Downscaling factor. - **kwargs : passed to skimage.transform.rescale - - Returns - ------- - img_rs : np.ndarray - Rescaled image. - - """ - return transform.rescale( - img, 1 / downfactor, anti_aliasing=False, preserve_range=True, **kwargs - ) - - # get metadata from original file (without loading the whole image) - with tifffile.TiffFile(image_path) as tifin: - metadata = tifin.ome_metadata - pixelsize = get_pixelsize_ome(metadata) - - with tifffile.TiffWriter(output_image, ome=False) as tifout: - # read full image - img = tifffile.imread(image_path) - - # write full resolution multichannel image - tifout.write( - img, - subifds=len(levels), - resolution=(1e4 / pixelsize, 1e4 / pixelsize), - description=metadata, - metadata=None, - **tiffoptions, - ) - - # write downsampled images (pyramidal levels) - for level in levels: - img_down = im_downscale( - img, level, order=0, channel_axis=0 - ) # downsample image - tifout.write( - img_down, - subfiletype=1, - resolution=(1e4 / level / pixelsize, 1e4 / level / pixelsize), - **tiffoptions, - ) - - -def get_tiff_options(compression: str, nthreads: int, tilesize: int) -> dict: - """ - Get the relevant tags and options to write a TIFF file. - - The returned dict is meant to be used to write a new tiff page with those tags. - - Parameters - ---------- - compression : str - Tiff compression (None, LZW, ...). - nthreads : int - Number of threads to write tiles. - tilesize : int - Tile size in pixels. Should be a power of 2. - - Returns - ------- - options : dict - Dictionary with Tiff tags. - - """ - return { - "compression": compression, - "photometric": "minisblack", - "resolutionunit": "CENTIMETER", - "maxworkers": nthreads, - "tile": (tilesize, tilesize), - } - - -def pyramidalize_directory( - inputdir: Annotated[ - str, - typer.Argument(help="Full path to the directory with images to pyramidalize."), - ], - version: Annotated[ - Optional[bool], - typer.Option("--version", callback=version_callback, is_eager=True), - ] = None, - use_qupath: Annotated[ - Optional[bool], - typer.Option(help="Use QuPath backend instead of Python."), - ] = USE_QUPATH, - tile_size: Annotated[ - Optional[int], - typer.Option(help="Image tile size, typically 512 or 1024."), - ] = TILE_SIZE, - pyramid_factor: Annotated[ - Optional[int], - typer.Option(help="Factor between two consecutive pyramid levels."), - ] = PYRAMID_FACTOR, - nthreads: Annotated[ - Optional[int], - typer.Option(help="Number of threads to parallelize image writing."), - ] = NTHREADS, - qupath_path: Annotated[ - Optional[str], - typer.Option( - help="Full path to the QuPath (console) executable.", - rich_help_panel="QuPath backend", - ), - ] = QUPATH_PATH, - script_path: Annotated[ - Optional[str], - typer.Option( - help="Full path to the groovy script that does the job.", - rich_help_panel="QuPath backend", - ), - ] = SCRIPT_PATH, - pyramid_max: Annotated[ - Optional[int], - typer.Option( - help="Maximum rescaling (smaller pyramid, will be rounded to closer power of 2).", - rich_help_panel="Python backend", - ), - ] = PYRAMID_MAX, -): - """ - Create pyramidal versions of .ome.tiff images found in the input directory. - You need to edit the script to set the "QUPATH_PATH" to your installation of QuPath. - Usually on Windows it should be here : - C:/Users/$USERNAME$/AppData/Local/QuPath-0.X.Y/QuPath-0.X.Y (console).exe - Alternatively you can run the script with the --qupath-path option. - - """ - # check QuPath was correctly set - if not os.path.isfile(qupath_path): - raise FileNotFoundError( - """QuPath executable was not found. Edit the script to set 'QUPATH_PATH', - or run the script with the --qupath-path option. Usually it is installed - at C:/Users/$USERNAME$/AppData/Local/QuPath-0.X.Y/QuPath-0.X.Y (console).exe""" - ) - # prepare output directory - outputdir = os.path.join(inputdir, "pyramidal") - if not os.path.isdir(outputdir): - os.mkdir(outputdir) - - # get a list of images - files = [filename for filename in os.listdir(inputdir) if filename.endswith(INEXT)] - - # check we have files to process - if len(files) == 0: - print("Specified input directory is empty.") - sys.exit() - - # loop over all files - print(f"Found {len(files)} to pyramidalize...") - - pbar = tqdm(files) - for imagename in pbar: - # prepare image names - image_path = os.path.join(inputdir, imagename) - output_image = os.path.join(outputdir, imagename) - - # check if output file already exists - if os.path.isfile(output_image): - continue - - # verbose - pbar.set_description(f"Pyramidalyzing {imagename}") - - if use_qupath: - pyramidalize_qupath( - image_path, - output_image, - qupath_path, - script_path, - tile_size, - pyramid_factor, - nthreads, - ) - else: - # prepare tiffwriter options - tiffoptions = get_tiff_options(COMPRESSION_PYTHON, nthreads, tile_size) - - # number of pyramid levels - levels = [ - pyramid_factor**i - for i in range(1, int(math.log(pyramid_max, pyramid_factor)) + 1) - ] - pyramidalize_python(image_path, output_image, levels, tiffoptions) - - print("All done!") - - -# --- Call -if __name__ == "__main__": - typer.run(pyramidalize_directory)