From 647a6d56f833d61aa1db736d3b695a1cb2f79cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ho=CC=88rst?= Date: Wed, 23 Oct 2024 16:26:36 +0200 Subject: [PATCH] Changing the hardware setup (the image loader) for a more fine grained image loader selection --- .gitignore | 4 +- README.md | 11 +- docs/README_pypi.md | 10 +- examples/patch_extraction.yaml | 2 +- pathopatch/cli.py | 8 +- pathopatch/patch_extraction/dataset.py | 54 +++-- .../patch_extraction/patch_extraction.py | 203 +++++++++++++----- pathopatch/patch_extraction/process_batch.py | 3 +- pyproject.toml | 61 ++++++ 9 files changed, 270 insertions(+), 86 deletions(-) create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index 5bf711b..fd0870f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,7 @@ test_database/3DHistech test_database/XiaLab test_database/dicom_files test_database/dicom_leica -dpyproject.toml +test_database/dicom_leica_anonymized +test_database/dicom_wsidicomizer mask_ihc.ipynb -pyproject.toml test_output diff --git a/README.md b/README.md index d1682fa..275cd26 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ In our Pre-Processing pipeline, we are able to extract quadratic patches from de [--apply_prefilter APPLY_PREFILTER] [--log_path LOG_PATH] [--log_level {critical,error,warning,info,debug}] - [--hardware_selection {cucim,openslide}] + [--hardware_selection {cucim,openslide,wsidicom}] [--wsi_magnification WSI_MAGNIFICATION] [--wsi_mpp WSI_MPP] @@ -256,9 +256,9 @@ In our Pre-Processing pipeline, we are able to extract quadratic patches from de --log_level {critical,error,warning,info,debug} Set the logging level. Options are ['critical', 'error', 'warning', 'info', 'debug'] (default: None) - --hardware_selection {cucim,openslide} + --hardware_selection {cucim,openslide,wsidicom} Select hardware device (just if available, otherwise always - cucim). Defaults to cucim. (default: None) + cucim). Defaults to None. (default: None) --wsi_magnification WSI_MAGNIFICATION Manual WSI magnification, but just applies if metadata cannot be derived from OpenSlide (e.g., for .tiff files). @@ -370,6 +370,11 @@ In our Pre-Processing pipeline, we are able to extract quadratic patches from de ``` +## A Note on DICOM +If you use DICOM files directly converted by vendors, first try to use OpenSlide which is the default setting. If this is slow, you could try to enforce the `wsidicom` image loader by setting the hardware to "wsidicom" (`hardware_selection: wsi_dicom`or `--hardware_selection wsidicom`) and check if this works. + +If you converted files to DICOM with the `wsidicomizer` tool as explained below, please always set the image loader to "wsidicom" (`hardware_selection: wsi_dicom`or `--hardware_selection wsidicom`). + ## Examples An example notebook is given [here](PathoPatch.ipynb): Open In Colab diff --git a/docs/README_pypi.md b/docs/README_pypi.md index 5f29293..0e5c245 100644 --- a/docs/README_pypi.md +++ b/docs/README_pypi.md @@ -92,7 +92,7 @@ wsi_extraction [-h] [--apply_prefilter APPLY_PREFILTER] [--log_path LOG_PATH] [--log_level {critical,error,warning,info,debug}] - [--hardware_selection {cucim,openslide}] + [--hardware_selection {cucim,openslide,wsidicom}] [--wsi_magnification WSI_MAGNIFICATION] [--wsi_mpp WSI_MPP] @@ -230,9 +230,9 @@ options: --log_level {critical,error,warning,info,debug} Set the logging level. Options are ['critical', 'error', 'warning', 'info', 'debug'] (default: None) - --hardware_selection {cucim,openslide} - Select hardware device (just if available, otherwise always - cucim). Defaults to cucim. (default: None) + --hardware_selection {cucim,openslide,wsidicom} + Select hardware device (just if available, otherwise always + cucim). Defaults to None. (default: None) --wsi_magnification WSI_MAGNIFICATION Manual WSI magnification, but just applies if metadata cannot be derived from OpenSlide (e.g., for .tiff files). @@ -306,6 +306,8 @@ WSI_Name ├── patch_metadata.json # Patch metadata of WSI merged in one file └── thumbnail.png # WSI thumbnail ``` + +## Further information For more information, check out the git. ## License diff --git a/examples/patch_extraction.yaml b/examples/patch_extraction.yaml index c60f3f1..275f530 100644 --- a/examples/patch_extraction.yaml +++ b/examples/patch_extraction.yaml @@ -80,4 +80,4 @@ filter_patches: # Post-extraction patch filtering to sort out arte # logging log_path: # Path where log files should be stored. Otherwise, log files are stored in the output folder. [str][Optional, defaults to None] log_level: # Set the logging level. [str][Optional, defaults to info] -hardware_selection: # Select hardware device (just if available, otherwise always cucim). [str] [Optional, defaults to cucim] +hardware_selection: # Select hardware device (just if available, otherwise always cucim). Options are openslide,cucim,wsidicom [str] [Optional, defaults to cucim] diff --git a/pathopatch/cli.py b/pathopatch/cli.py index 79f6ee9..13cebc1 100644 --- a/pathopatch/cli.py +++ b/pathopatch/cli.py @@ -148,7 +148,7 @@ class PreProcessingConfig(BaseModel): apply_prefilter (bool, optional): Pre-extraction mask filtering to remove marker from mask before applying otsu. Defaults to False. log_path (str, optional): Path where log files should be stored. Otherwise, log files are stored in the output folder. Defaults to None. log_level (str, optional): Set the logging level. Defaults to "info". - hardware_selection (str, optional): Select hardware device (just if available, otherwise always cucim). Defaults to "cucim". + hardware_selection (str, optional): Select hardware device (just if available, otherwise always cucim). Defaults to None. wsi_properties (dict, optional): Dictionary with manual WSI metadata, but just applies if metadata cannot be derived from OpenSlide (e.g., for .tiff files). Supported keys are slide_mpp and magnification Raises: @@ -209,7 +209,7 @@ class PreProcessingConfig(BaseModel): # other log_path: Optional[str] log_level: Optional[str] = "info" - hardware_selection: Optional[str] = "cucim" + hardware_selection: Optional[str] = None wsi_properties: Optional[dict] def __init__(__pydantic_self__, **data: Any) -> None: @@ -577,8 +577,8 @@ def __init__(self) -> None: parser.add_argument( "--hardware_selection", type=str, - choices=["cucim", "openslide"], - help="Select hardware device (just if available, otherwise always cucim). Defaults to cucim.", + choices=["cucim", "openslide", "wsidicom"], + help="Select hardware device (just if available, otherwise always cucim). Defaults to None.", ) parser.add_argument( "--wsi_magnification", diff --git a/pathopatch/patch_extraction/dataset.py b/pathopatch/patch_extraction/dataset.py index 241c68a..2fa1f47 100644 --- a/pathopatch/patch_extraction/dataset.py +++ b/pathopatch/patch_extraction/dataset.py @@ -28,6 +28,10 @@ from PIL import Image from pathopatch.utils.exceptions import WrongParameterException from pathopatch.wsi_interfaces.openslide_deepzoom import DeepZoomGeneratorOS +from pathopatch.wsi_interfaces.wsidicomizer_openslide import ( + DicomSlide, + DeepZoomGeneratorDicom, +) from pathopatch.utils.patch_util import ( calculate_background_ratio, compute_interesting_patches, @@ -280,20 +284,44 @@ def __init__( def _set_hardware(self) -> None: """Either load CuCIM (GPU-accelerated) or OpenSlide""" - if module_exists("cucim", error="ignore"): - self.logger.debug("Using CuCIM") - from cucim import CuImage + wsi_file = Path(self.config.wsi_path) + if wsi_file.is_dir(): + if len(list(wsi_file.glob("*.dcm"))) != 0: + self.logger.debug("Detected dicom files") + try: + dcm_files = list(wsi_file.glob("*.dcm")) + dcm_files = [(f, os.path.getsize(f.resolve())) for f in dcm_files] + dcm_files = sorted(dcm_files, key=lambda x: x[1], reverse=True) + OpenSlide(str(dcm_files[0][0])) + wsi_file = dcm_files[0][0] + self.image_loader = OpenSlide + self.deepzoomgenerator = DeepZoomGeneratorOS + self.slide_metadata_loader = OpenSlide + except: + try: + DicomSlide(wsi_file) + except Exception as e: + raise e + self.deepzoomgenerator = DeepZoomGeneratorDicom + self.image_loader = DicomSlide + self.slide_metadata_loader = DicomSlide + else: + if module_exists("cucim", error="ignore"): + self.logger.debug("Using CuCIM") + from cucim import CuImage - from pathopatch.wsi_interfaces.cucim_deepzoom import ( - DeepZoomGeneratorCucim, - ) + from pathopatch.wsi_interfaces.cucim_deepzoom import ( + DeepZoomGeneratorCucim, + ) - self.deepzoomgenerator = DeepZoomGeneratorCucim - self.image_loader = CuImage - else: - self.logger.debug("Using OpenSlide") - self.deepzoomgenerator = DeepZoomGeneratorOS - self.image_loader = OpenSlide + self.deepzoomgenerator = DeepZoomGeneratorCucim + self.image_loader = CuImage + self.slide_metadata_loader = OpenSlide + else: + self.logger.debug("Using OpenSlide") + self.deepzoomgenerator = DeepZoomGeneratorOS + self.image_loader = OpenSlide + self.slide_metadata_loader = OpenSlide def _set_tissue_detector(self) -> None: """Set up the tissue detection model and transformations. @@ -364,7 +392,7 @@ def _prepare_slide( * List[Polygon]: List of polygons, downsampled to the target level * List[str]: List of region labels """ - self.slide_openslide = OpenSlide(str(self.config.wsi_path)) + self.slide_openslide = self.slide_metadata_loader(str(self.config.wsi_path)) self.slide = self.image_loader(str(self.config.wsi_path)) if ( diff --git a/pathopatch/patch_extraction/patch_extraction.py b/pathopatch/patch_extraction/patch_extraction.py index 4312d4f..f60eb79 100644 --- a/pathopatch/patch_extraction/patch_extraction.py +++ b/pathopatch/patch_extraction/patch_extraction.py @@ -57,6 +57,7 @@ DicomSlide, DeepZoomGeneratorDicom, ) +from openslide import OpenSlideUnsupportedFormatError warnings.filterwarnings("ignore", category=DeprecationWarning) warnings.filterwarnings("ignore", category=UserWarning) @@ -194,13 +195,13 @@ class PreProcessor(object): num_files (int): Number of WSI files rescaling_factor (int): Rescaling factor for the WSI deepzoomgenerator (Union[DeepZoomGeneratorOS, DeepZoomGeneratorCucim]): DeepZoomGenerator object - image_loader (Union[OpenSlide, CuImage]): Image loader object + image_loader (Union[OpenSlide, CuImage, DicomSlide]): Image loader object + image_metadata_loader (Union[OpenSlide, DicomSlide]): Image metadata loader object detector_device (str): Device for tissue detection detector_model (nn.Module): Tissue detection model detector_transforms (Compose): Tissue detection transforms curr_wsi_level (int): Current WSI level save_context (bool): Save context flag - # TODO: improve and check with new dcm coce and new filelist loading Methods: setup_output_path(output_path: Union[str, Path]) -> None: @@ -292,9 +293,6 @@ def __init__(self, slide_processor_config: PreProcessingConfig) -> None: self.config.incomplete_annotations, ) - # hardware - self._set_hardware(self.config.hardware_selection) - # convert overlap from percentage to pixels self.config.patch_overlap = int( np.floor(self.config.patch_size / 2 * self.config.patch_overlap / 100) @@ -414,7 +412,9 @@ def _set_annotations_paths( "files to have the same name as the WSI files. Otherwise use incomplete_annotations=True" ) - def _set_hardware(self, hardware_selection: str = "cucim") -> None: + def _set_hardware( + self, wsi_file: Union[Path, str], hardware_selection: str = None + ) -> None: """Either load CuCIM (GPU-accelerated) or OpenSlide @@ -422,28 +422,106 @@ def _set_hardware(self, hardware_selection: str = "cucim") -> None: hardware_selection (str, optional): Specify hardware. Just for experiments. Must be either "openslide", or "cucim". Defaults to cucim. """ - if self.config.wsi_extension == "dcm": - logger.info("Using WsiDicom as WSIReader") - self.deepzoomgenerator = DeepZoomGeneratorDicom - self.image_loader = DicomSlide + wsi_file = Path(wsi_file) + wsi_file_global = Path(wsi_file) + + # Force the use of the hardware selection + if hardware_selection is not None: + if self.config.wsi_extension == "dcm": + if hardware_selection.lower() not in ["openslide", "wsidicom"]: + raise NotImplementedError("Option not available for DCM files.") + else: + if hardware_selection.lower() == "openslide": + logger.info("Using OpenSlide as WSIReader") + self.deepzoomgenerator = DeepZoomGeneratorOS + self.image_loader = OpenSlide + dcm_files = list(wsi_file.glob("*.dcm")) + dcm_files = [ + (f, os.path.getsize(f.resolve())) for f in dcm_files + ] + dcm_files = sorted(dcm_files, key=lambda x: x[1], reverse=True) + wsi_file = dcm_files[0][0] + try: + OpenSlide(str(wsi_file)) + self.image_loader = OpenSlide + self.deepzoomgenerator = DeepZoomGeneratorOS + self.slide_metadata_loader = OpenSlide + except OpenSlideUnsupportedFormatError as e: + raise e + except Exception as e: + raise e + + elif hardware_selection.lower() == "wsidicom": + logger.info("Using WsiDicom as WSIReader") + try: + DicomSlide(wsi_file) + except Exception as e: + raise e + self.deepzoomgenerator = DeepZoomGeneratorDicom + self.image_loader = DicomSlide + self.slide_metadata_loader = DicomSlide + else: + if hardware_selection.lower() not in ["openslide", "cucim"]: + raise NotImplementedError("Option not available") + if ( + module_exists("cucim", error="ignore") + and hardware_selection.lower() == "cucim" + ): + logger.info("Using CuCIM as WSIReader") + from cucim import CuImage + + from pathopatch.wsi_interfaces.cucim_deepzoom import ( + DeepZoomGeneratorCucim, + ) + + self.deepzoomgenerator = DeepZoomGeneratorCucim + self.image_loader = CuImage + self.slide_metadata_loader = OpenSlide + elif hardware_selection.lower() == "openslide": + logger.info("Using OpenSlide as WSIReader") + self.deepzoomgenerator = DeepZoomGeneratorOS + self.image_loader = OpenSlide + self.slide_metadata_loader = OpenSlide else: - if ( - module_exists("cucim", error="ignore") - and hardware_selection.lower() == "cucim" - ): - logger.info("Using CuCIM as WSIReader") - from cucim import CuImage + if self.config.wsi_extension == "dcm": + try: + dcm_files = list(wsi_file.glob("*.dcm")) + dcm_files = [(f, os.path.getsize(f.resolve())) for f in dcm_files] + dcm_files = sorted(dcm_files, key=lambda x: x[1], reverse=True) + OpenSlide(str(dcm_files[0][0])) + wsi_file = dcm_files[0][0] + self.image_loader = OpenSlide + self.deepzoomgenerator = DeepZoomGeneratorOS + self.slide_metadata_loader = OpenSlide + logger.info("Using OpenSlide as WSIReader") + except: + try: + DicomSlide(wsi_file) + except Exception as e: + raise e + self.deepzoomgenerator = DeepZoomGeneratorDicom + self.image_loader = DicomSlide + self.slide_metadata_loader = DicomSlide + logger.info("Using WsiDicom as WSIReader") + else: + if module_exists("cucim", error="ignore"): + logger.info("Using CuCIM as WSIReader") + from cucim import CuImage - from pathopatch.wsi_interfaces.cucim_deepzoom import ( - DeepZoomGeneratorCucim, - ) + from pathopatch.wsi_interfaces.cucim_deepzoom import ( + DeepZoomGeneratorCucim, + ) - self.deepzoomgenerator = DeepZoomGeneratorCucim - self.image_loader = CuImage - else: - logger.info("Using OpenSlide as WSIReader") - self.deepzoomgenerator = DeepZoomGeneratorOS - self.image_loader = OpenSlide + self.deepzoomgenerator = DeepZoomGeneratorCucim + self.image_loader = CuImage + self.slide_metadata_loader = OpenSlide + else: + logger.info("Using OpenSlide as WSIReader") + self.deepzoomgenerator = DeepZoomGeneratorOS + self.image_loader = OpenSlide + self.slide_metadata_loader = OpenSlide + + return wsi_file_global, wsi_file def _set_tissue_detector(self) -> None: """Set up the tissue detection model and transformations. @@ -532,7 +610,8 @@ def sample_patches_dataset(self) -> None: try: # prepare wsi, espeically find patches ( - (n_cols, n_rows), + (wsi_file_global, wsi_file), + (_, _), (wsi_metadata, mask_images, mask_images_annotations, thumbnails), ( interesting_coords_wsi, @@ -544,7 +623,7 @@ def sample_patches_dataset(self) -> None: # setup storage store = Storage( - wsi_name=wsi_file.stem, + wsi_name=wsi_file_global.stem, output_path=self.config.output_path, metadata=wsi_metadata, mask_images=mask_images, @@ -563,6 +642,7 @@ def sample_patches_dataset(self) -> None: ) = self.process_queue( batch=interesting_coords_wsi, wsi_file=wsi_file, + wsi_file_global=wsi_file_global, wsi_metadata=wsi_metadata, level=level_wsi, polygons=polygons_downsampled_wsi, @@ -571,7 +651,7 @@ def sample_patches_dataset(self) -> None: ) if patch_count == 0: - logger.warning(f"No patches sampled from {wsi_file.name}") + logger.warning(f"No patches sampled from {wsi_file_global.name}") logger.info(f"Total patches sampled: {patch_count}") store.clean_up(patch_distribution, patch_result_metadata) @@ -625,12 +705,12 @@ def sample_patches_dataset(self) -> None: # for segmentation algorithms with neighbourhood (e.g., valuing vicinity) if self.config.save_context_without_mask: self._get_surrounding_patches( - wsi_file=wsi_file, + wsi_file=wsi_file_global, roi_patches=patch_result_metadata, store=store, - ) + ) # TODO: add wsi_file for dcm except NotImplementedError as e: - logger.error(f"Cannot preprocess file {wsi_file.name}: {e}") + logger.error(f"Cannot preprocess file {wsi_file_global.name}: {e}") continue logger.info(f"Patches saved to: {self.config.output_path.resolve()}") @@ -763,7 +843,11 @@ def _check_wsi_resolution(self, slide_properties: dict[str, str]) -> None: def _prepare_wsi( self, wsi_file: str ) -> Tuple[ - Tuple[int, int], Tuple[dict, dict, dict, dict], Callable, List[List[Tuple]] + Tuple[str, str], + Tuple[int, int], + Tuple[dict, dict, dict, dict], + Callable, + List[List[Tuple]], ]: """Prepare a WSI for preprocessing @@ -781,8 +865,8 @@ def _prepare_wsi( WrongParameterException: The level resulting from target magnification or downsampling factor must exists to extract patches. Returns: - Tuple[Tuple[int, int], Tuple[dict, dict, dict, dict], Callable, List[List[Tuple]]]: - + Tuple[Tuple[str, str], Tuple[int, int], Tuple[dict, dict, dict, dict], Callable, List[List[Tuple]]]: + - Tuple[str, str]: Gloabl WSI Path and loader WSI Path (in most cases identical except for DICOM with OpenSlide) - Tuple[int, int]: Number of rows, cols of the WSI at the given level - dict: Dictionary with Metadata of the WSI - dict[str, Image]: Masks generated during tissue detection stored in dict with keys equals the mask name and values equals the PIL image @@ -792,18 +876,14 @@ def _prepare_wsi( Keys are the thumbnail names and values are the PIL images. - callable: Batch-Processing function performing the actual patch-extraction task - List[List[Tuple]]: Divided List with batches of batch-size. Each batch-element contains the row, col position of a patch together with bg-ratio. - - Todo: - * TODO: Check if this works out for non-GPU devices - * TODO: Class documentation link """ logger.info(f"Computing patches for {wsi_file.name}") - # load slide (OS and CuImage/OS) - if self.config.wsi_extension == "dcm": - slide = DicomSlide(wsi_file) - else: - slide = OpenSlide(str(wsi_file)) + # set hardware + wsi_file_global, wsi_file = self._set_hardware( + wsi_file, self.config.hardware_selection + ) + slide = self.slide_metadata_loader(str(wsi_file)) slide_cu = self.image_loader(str(wsi_file)) slide_mpp = None @@ -877,7 +957,7 @@ def _prepare_wsi( slide_properties["mpp"] * self.rescaling_factor * self.config.downsample - ) # TODO: should it be divided by 2 or not? + ) # should it be divided by 2 or not? else: resulting_mpp = slide_properties["mpp"] * self.config.downsample # target mag has precedence before downsample! @@ -999,8 +1079,11 @@ def _prepare_wsi( "level": level, } - logger.info(f"{wsi_file.name}: Processing {len(interesting_coords)} patches.") + logger.info( + f"{wsi_file_global.name}: Processing {len(interesting_coords)} patches." + ) return ( + (wsi_file_global, wsi_file), (n_cols, n_rows), (wsi_metadata, mask_images, mask_images_annotations, thumbnails), (list(interesting_coords), level, polygons_downsampled, region_labels), @@ -1010,6 +1093,7 @@ def process_queue( self, batch: List[Tuple[int, int, float]], wsi_file: Union[Path, str], + wsi_file_global: Union[Path, str], wsi_metadata: dict, level: int, polygons: List[Polygon], @@ -1024,7 +1108,8 @@ def process_queue( Args: batch (List[Tuple[int, int, float]]): A batch of patch coordinates (row, col, backgropund ratio) - wsi_file (Union[Path, str]): Path to the WSI file from which the patches should be extracted from + wsi_file (Union[Path, str]): Path to file that is handover to the slide loader + wsi_file_global (Union[Path, str]): Path to the WSI file from which the patches should be extracted from wsi_metadata (dict): Dictionary with important WSI metadata level (int): The tile level for sampling. polygons (List[Polygon]): Annotations of this WSI as a list of polygons -> on reference downsample level @@ -1037,15 +1122,13 @@ def process_queue( int: Number of processed patches """ logger.debug(f"Started process {multiprocessing.current_process().name}") - + wsi_file_global = Path(wsi_file_global) + wsi_file = Path(wsi_file) # store context_tiles context_tiles = {} # reload image - if self.config.wsi_extension == "dcm": - slide = DicomSlide(wsi_file) - else: - slide = OpenSlide(str(wsi_file)) + slide = self.slide_metadata_loader(str(wsi_file)) slide_cu = self.image_loader(str(wsi_file)) tile_size, overlap = patch_to_tile_size( @@ -1101,8 +1184,8 @@ def process_queue( for row, col, _ in batch: pbar.update() # set name - patch_fname = f"{wsi_file.stem}_{row}_{col}.png" - patch_yaml_name = f"{wsi_file.stem}_{row}_{col}.yaml" + patch_fname = f"{wsi_file_global.stem}_{row}_{col}.png" + patch_yaml_name = f"{wsi_file_global.stem}_{row}_{col}.yaml" if self.config.context_scales is not None: context_patches = {scale: [] for scale in self.config.context_scales} @@ -1259,7 +1342,7 @@ def _get_surrounding_patches( logger.info("Computing surrounding patches outside of ROI") # load slide (OS and CuImage/OS) - slide = OpenSlide(str(wsi_file)) + slide = self.slide_metadata_loader(str(wsi_file)) slide_cu = self.image_loader(str(wsi_file)) tile_size, overlap = patch_to_tile_size( @@ -1361,6 +1444,7 @@ def save_normalization_vector( wsi_file (Path): Path to WSI file, must be within the dataset save_json_path (Union[Path, str]): Path to JSON-File where to Macenko-Vectors should be stored. """ + # TODO: this does not work with dicom currently # check input assert ( wsi_file in self.files @@ -1378,14 +1462,17 @@ def save_normalization_vector( min_background_ratio=self.config.min_intersection_ratio, ) - ((_, _), (_, _, _, _), (interesting_coords_wsi, _, _, _)) = self._prepare_wsi( - wsi_file - ) + ( + (_, _), + (_, _), + (_, _, _, _), + (interesting_coords_wsi, _, _, _), + ) = self._prepare_wsi(wsi_file) # convert divided back to batch # batch = [item for sublist in divided for item in sublist] # open slide - slide = OpenSlide(str(wsi_file)) + slide = self.slide_metadata_loader(str(wsi_file)) slide_cu = self.image_loader(str(wsi_file)) tile_size, overlap = patch_to_tile_size( self.config.patch_size, self.config.patch_overlap diff --git a/pathopatch/patch_extraction/process_batch.py b/pathopatch/patch_extraction/process_batch.py index aa7eedf..3438eb3 100644 --- a/pathopatch/patch_extraction/process_batch.py +++ b/pathopatch/patch_extraction/process_batch.py @@ -4,7 +4,7 @@ # @ Fabian Hörst, fabian.hoerst@uk-essen.de # Institute for Artifical Intelligence in Medicine, # University Medicine Essen - +from warnings import deprecated import multiprocessing from pathlib import Path @@ -28,6 +28,7 @@ from pathopatch.wsi_interfaces.openslide_deepzoom import DeepZoomGeneratorOS +@deprecated("Do not use this anymore") def process_batch( batch: List[Tuple[int, int, float]], *, diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..731b995 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "pathopatch" +version = "1.0.3" +authors = [ + {name = "Fabian Hörst", email = "fabian.hoerst@uk-essen.de"}, +] +description = "PathoPatch - Accelerating Artificial Intelligence Based Whole Slide Image Analysis with an Optimized Preprocessing Pipeline" +readme = "docs/README_pypi.md" +requires-python = ">=3.9" +keywords = ["python", "pathopatch"] +license = {text = "CC BY-NC-SA 4.0"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Education", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Programming Language :: Python :: 3", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Other", +] +dependencies = [ + "Pillow>=9.5.0", + "PyYAML", + "Shapely==1.8.5.post1", + "colorama", + "future", + "geojson>=3.0.0", + "matplotlib", + "natsort", + "numpy>1.22,<1.24", + "opencv_python_headless", + "openslide_python", + "pandas", + "pydantic==1.10.4", + "rasterio==1.3.5.post1", + "requests", + "scikit-image", + "setuptools<=65.6.3", + "tqdm", + "torchvision", + "torch", + "wsidicom==0.20.4", + "wsidicomizer==0.14.1", + "pydicom==2.4.4", +] + +[project.urls] +Homepage = "https://github.com/TIO-IKIM/PathoPatcher" + +[project.scripts] +wsi_extraction = "pathopatch.wsi_extraction:main" +annotation_conversion = "pathopatch.annotation_conversion:main" +macenko_vector_generation = "pathopatch.macenko_vector_generation:main" + +[tool.setuptools] +packages = {find = {exclude = ["tests", "tests.*"]}} +package-data = {"pathopatch" = ["data/*"]}