1 minute read

Définir la zone d’intérêt sur QuPath

Ouvrez QuPath et sélectionnez votre zone d’intérêt.

Sauvegarder le GeoJSON

Sauvegardez votre objet GeoJSON dans le même dossier que votre lame, avec le même nom. Dans QuPath 0.7, utilisez File → Export objects as GeoJSON… Si besoin, vous pouvez aussi sélectionner l’annotation dans la liste des objets puis l’exporter en GeoJSON.

Lancer le script

import cv2
import openslide
import math
import json
import numpy as np
from shapely.geometry import Polygon
from PIL import Image
from skimage import transform,util
import os

def extract_and_plot_lowest_mag_roi_svs(fpath: str, rotation=False):
    print(fpath)
    """
    Extrait une région définie par un GeoJSON d'un fichier SVS au niveau de grandissement le plus faible,
    l'exporte en OME-TIFF et affiche les étapes intermédiaires.

    Args:
        svs_path (str): Chemin du fichier SVS.
        geojson_path (str): Chemin du fichier GeoJSON contenant la région à extraire.
        output_path (str): Chemin de sortie pour l'image OME-TIFF.
    """
    svs_path=fpath+".svs"
    geojson_path=fpath+".geojson"
    i=0
    # Charger l'image SVS
    slide = openslide.OpenSlide(svs_path)

    # Charger le GeoJSON
    with open(geojson_path, 'r') as f:
        geojson_data = json.load(f)

    # Extraire les coordonnées du polygone du GeoJSON
    for feature in geojson_data["features"]:
        i=i+1
        output_path=fpath+"."+str(i)+".jpg"
        polygon_coords = feature["geometry"]["coordinates"][0]
        # Mettre le polygone à l'échelle
        polygon_coords= [(int(xi ), int(yi )) for xi, yi in polygon_coords]
        # Convertir en tableau NumPy pour utiliser min et max
        polygon_coords_np = np.array(polygon_coords)
        min_x, min_y, max_x, max_y = map(int, Polygon(polygon_coords).bounds)
        # Déterminer les min et max des coordonnées
        Min_x, Min_y = polygon_coords_np.min(axis=0)
        # Normalisation et mise à l’échelle
        scaled_coords = [(int((x - Min_x)),int((y - Min_y)))for x, y in polygon_coords]

        # Lire la région d'intérêt (ROI) depuis le niveau de faible grandissement
        roi = slide.read_region((int(min_x), int(min_y)), 0, (max_x - min_x, max_y - min_y))
        roi = roi.convert("RGB")
        roi_np = np.array(roi)
        # Créer un masque pour ne garder que la zone définie par le polygone
        mask = np.zeros((roi_np.shape[0], roi_np.shape[1]), dtype=np.uint8)

        cv2.fillPoly(mask, [np.array(scaled_coords, np.int32)], 255)

        # Convertir en BGR pour OpenCV
        #roi_np_bgr = cv2.cvtColor(roi_np, cv2.COLOR_RGB2BGR)
        roi_np_bgr = roi_np
        roi_np_masked = cv2.bitwise_and(roi_np_bgr, roi_np_bgr, mask=mask)

        # Appliquer le masque sur l'image
        if rotation:
            X=int(np.min((np.where(roi_np_masked[0,:,0]>0))[0]))
            Y=int(np.min((np.where(roi_np_masked[:,0,0]>0))[0]))
            roi_np_masked = cv2.resize(roi_np_masked, (0, 0), fx=0.8, fy=0.8)
            if X!=0:
                roi_np_masked=transform.rotate(roi_np_masked, 180-math.atan(Y/X)*180/np.pi, resize=True)
            x,y=np.where(roi_np_masked[:,:,0]>0)
            roi_np_masked=roi_np_masked[min(x):max(x),min(y):max(y),:]
            if roi_np_masked.shape[1]<roi_np_masked.shape[0]:
                print("haut")
                roi_np_masked=transform.rotate(roi_np_masked, 90, resize=True)
        
        x,y=np.where(roi_np_masked[:,:,0]>0)
        roi_np_masked=roi_np_masked[min(x):max(x),min(y):max(y),:]

        Image.fromarray(util.img_as_ubyte(roi_np_masked)).save(output_path, quality=95)