import pydicom import numpy as np from pydicom.pixels import apply_voi_lut from skimage import exposure from PIL import Image,ImageEnhance import cv2 def read_xray(path, voi_lut=True, fix_monochrome=True): """ Read and preprocess a DICOM X-ray image. Parameters: - path: Path to the DICOM file. - voi_lut: Apply VOI LUT if available. - fix_monochrome: Fix inverted monochrome images. Returns: - NumPy array: Preprocessed X-ray image. """ dicom = pydicom.dcmread(path) # Apply VOI LUT if available if voi_lut: data = apply_voi_lut(dicom.pixel_array, dicom) else: data = dicom.pixel_array # Fix inverted monochrome images if fix_monochrome and dicom.PhotometricInterpretation == "MONOCHROME1": data = np.amax(data) - data # Normalize data to start from 0 data = data - np.min(data) return data def resize_pil_image(image: Image.Image, target_shape: tuple) -> Image.Image: """ Resizes a PIL image based on a target shape. Args: image: Input PIL image. target_shape: Desired shape for resizing. It can be a 2D tuple (height, width) or a 3D tuple (height, width, channels), where channels will be ignored. Returns: Resized PIL image. """ # Convert image to a numpy array np_image = np.array(image) # Extract the original height and width from the numpy array height, width = np_image.shape[:2] # If the target shape is 2D (height, width) if len(target_shape) == 2: new_width, new_height = target_shape elif len(target_shape) == 3: # If the target shape is 3D (height, width, channels), only change the first two dimensions new_width, new_height = target_shape[:2] else: raise ValueError("Target shape must be either 2D or 3D.") # Resize the image using cv2 or PIL's in-built resizing (no channels affected) pil_resized_image = Image.fromarray(np_image).resize((new_width, new_height), Image.LANCZOS) return pil_resized_image def enhance_exposure(img): """ Enhance image exposure using histogram equalization. Parameters: - img: Input image as a NumPy array. Returns: - PIL.Image: Exposure-enhanced image. """ img = exposure.equalize_hist(img) img = exposure.equalize_adapthist(img / np.max(img)) img = (img * 255).astype(np.uint8) return Image.fromarray(img) def unsharp_masking(image, kernel_size=5, strength=0.25): """ Apply unsharp masking to enhance image sharpness. Parameters: - image: Input image as a NumPy array or PIL.Image. - kernel_size: Size of the Gaussian blur kernel. - strength: Strength of the high-pass filter. Returns: - PIL.Image: Sharpened image. """ image = np.array(image) # Convert to grayscale if needed if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image # Apply Gaussian blur and calculate high-pass filter blurred = cv2.GaussianBlur(gray, (kernel_size, kernel_size), 0) high_pass = cv2.subtract(gray, blurred) # Combine high-pass with original image sharpened = cv2.addWeighted(gray, 1, high_pass, strength, 0) return Image.fromarray(sharpened) def increase_contrast(image: Image.Image, factor: float) -> Image.Image: """ Increases the contrast of the input PIL image by a given factor. Args: image: Input PIL image. factor: Factor by which to increase the contrast. A factor of 1.0 means no change, values greater than 1.0 increase contrast, values between 0.0 and 1.0 decrease contrast. Returns: Image with increased contrast. """ if image.mode not in ['RGB', 'L']: image = image.convert('RGB') enhancer = ImageEnhance.Contrast(image) image_enhanced = enhancer.enhance(factor) return image_enhanced def increase_brightness(image: Image.Image, factor: float) -> Image.Image: """ Increases the brightness of the input PIL image by a given factor. Args: image: Input PIL image. factor: Factor by which to increase the brightness. A factor of 1.0 means no change, values greater than 1.0 increase brightness, values between 0.0 and 1.0 decrease brightness. Returns: Image with increased brightness. """ if image.mode not in ['RGB', 'L']: image = image.convert('RGB') enhancer = ImageEnhance.Brightness(image) image_enhanced = enhancer.enhance(factor) return image_enhanced def apply_clahe(image, clipLimit=2.0, tileGridSize=(8, 8)): """ Apply CLAHE (Contrast Limited Adaptive Histogram Equalization) to an image. Parameters: - image: Input image as a PIL.Image. - clipLimit: Threshold for contrast limiting. - tileGridSize: Size of the grid for histogram equalization. Returns: - Processed image in the same format as the input (PIL.Image). """ image_np = np.array(image) image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR) # Apply CLAHE based on image type if len(image_np.shape) == 2: # Grayscale image clahe = cv2.createCLAHE(clipLimit=clipLimit, tileGridSize=tileGridSize) processed = clahe.apply(image_np) else: # Color image: Apply CLAHE on the L channel in LAB space lab = cv2.cvtColor(image_np, cv2.COLOR_BGR2LAB) L, A, B = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clipLimit, tileGridSize=tileGridSize) L_clahe = clahe.apply(L) lab_clahe = cv2.merge((L_clahe, A, B)) processed = cv2.cvtColor(lab_clahe, cv2.COLOR_LAB2BGR) processed_rgb = cv2.cvtColor(processed, cv2.COLOR_BGR2RGB) return Image.fromarray(processed_rgb)