Source code for MRIsegm.utils

import numpy as np
import pydicom
import glob
from read_roi import read_roi_file
import matplotlib.pyplot as plt
import cv2


__author__ = ['Giuseppe Filitto']
__email__ = ['giuseppe.filitto@studio.unibo.it']


[docs]def rescale(im, max_value, min_value): ''' Rescale image in range (0,255) Parameters ---------- im : array like image to be rescaled max_value : value max image value min_value : value min image value Returns ------- rescaled image: array like rescaled input image as type uint 8 ''' rescaled_image = ((im.astype(float) - min_value) * (1. / (max_value - min_value)) * 255.).astype('uint8') return rescaled_image
[docs]def read_slices(filename): ''' Read dicom file as pixel array Parameters ---------- filename : str name of file.dcm Returns ------- pix_arr : array dcm file as array Raises ------ ValueError filename must be .dcm format ''' _, ext = filename.split('.') if ext != 'dcm': raise ValueError('Input filename must be a DICOM file') pix_arr = pydicom.dcmread(filename).pixel_array return pix_arr
[docs]def get_slices(dir_path, uint8=True): ''' Get full stack of slices from single dcm files ordered by "InstanceNumber" as a rescaled array of shape: depth, height, width Parameters ---------- dir_path : str directory of dcm slices uint8 : bool rescale the image to uint8, by default True Returns ------- slices: array array of shape: depth, height, width , ordered by "InstanceNumber" ''' files = glob.glob(dir_path + '/*.dcm') # ordering as istance number z = [float(pydicom.read_file(f, force=True).get( "InstanceNumber", "0") - 1) for f in files] order = np.argsort(z) files = np.asarray(files)[order] slices = [read_slices(f) for f in files] if uint8: Max = max([x.max() for x in slices]) Min = min([x.min() for x in slices]) slices = [rescale(x, Max, Min) for x in slices] slices = np.asarray(slices) else: slices = np.asarray(slices) return slices
[docs]def get_slices_info(slices): ''' Print depth, height, width of the input slices Parameters ---------- slices : array-like ''' depth, height, width = slices.shape print(f"The image object has the following dimensions: depth:{depth}, height:{height}, width:{width}")
def _dict(dict_list): ''' Function to get true_dict from a dict of dict like {key : true_dict} Parameters ---------- dict_list : list list of dicts Returns ------- true_dict : list list of true_dict ''' true_dict = [] for i in dict_list: _dict = list(i.values()) for j in _dict: keys = j.keys() vals = j.values() _dict = {key: val for key, val in zip(keys, vals)} true_dict.append(_dict) return true_dict
[docs]def get_rois(roi_path): ''' Get ImageJ rois from .roi files stored in roi_path Parameters ---------- roi_path : str path of dir containing .roi files Returns ------- roi: list list of roi dicts orderd by position number and without "type":composite ''' rois_list = glob.glob(roi_path + '/*.roi') rois = [read_roi_file(roi) for roi in rois_list] rois = _dict(rois) # ordering dictionaries by positions and removing rois without x y coords rois = sorted(rois, key=lambda d: list(d.values())[-1]) rois = list(filter(lambda d: d['type'] != 'composite', rois)) return rois
[docs]def make_mask(slices, layer, rois): ''' Generate mask of a given slice Parameters ---------- slices : array array of shape depth, height, width layer : int value between (0, slices.shape[0]) rois : list roi list Returns ------- label : array return mask of a given slice. Pixels outside regions of interest are set to 0 (black), pixel inside regions of interest are set to 255 (white) Raises ------ ValueError if there are no regions of interest: "no labels found!" ''' positions = [rois[i].get('position') - 1 for i in range(len(rois))] if layer not in positions: raise ValueError("no labels found!") else: background = np.zeros_like(slices[layer, ...]) roi = list(filter(lambda d: d['position'] == layer + 1, rois)) x = [roi[i].get('x') for i in range(len(roi))] y = [roi[i].get('y') for i in range(len(roi))] points = [] for i in range(len(x)): pts = np.array([(int(x), int(y)) for(x, y) in zip( x[i], y[i])]) points.append(pts) label = cv2.fillPoly(background, points, 255) return label
[docs]def mask_slices(slices, rois): ''' Make an array of shape depth, height, width, containing for each layer the proper mask. If no mask if found then masked_slices[layer, :, :] = 0 Parameters ---------- slices : array array of shape depth, height, width rois : list roi list Returns ------- masked_slices : array array of shape: depth, height, width containing for each layer the proper mask ''' masked_slices = np.zeros_like(slices) positions = [rois[i].get('position') - 1 for i in range(len(rois))] for layer in range(slices.shape[0]): if layer not in positions: masked_slices[layer, ...] = 0 else: masked_slices[layer, ...] = make_mask( slices=slices, layer=layer, rois=rois) return masked_slices
[docs]def explore_roi(slices, layer, rois): # pragma: no cover ''' Show the regions of interest contours from a given slice Parameters ---------- slices : array array of shape depth, height, width layer : int value between (0, slices.shape[0]) rois : list roi list ''' # -1 to match slice positions = [rois[i].get('position') - 1 for i in range(len(rois))] if layer in positions: plt.figure(figsize=(12, 7), constrained_layout=True) plt.imshow(slices[layer, ...], cmap='gray') plt.title(f'Exploring Slice {layer}', fontsize=20) plt.axis('off') roi = list(filter(lambda d: d['position'] == layer + 1, rois)) x = [roi[i].get('x') for i, _ in enumerate(roi)] y = [roi[i].get('y') for i, _ in enumerate(roi)] for i, _ in enumerate(roi): plt.fill(x[i], y[i], edgecolor='r', fill=False) else: plt.figure(figsize=(12, 7), constrained_layout=True) plt.imshow(slices[layer, ...], cmap='gray') plt.title(f'Exploring Slice {layer}', fontsize=20) plt.axis('off') plt.show()
[docs]def plot_random_layer(slices): # pragma: no cover ''' Show figure of the random slice between (0, slices.shape[0]) Parameters ---------- slices : array array of shape depth, height, width ''' maxval = slices.shape[0] # Select random layer number layer = np.random.randint(0, maxval) # figure explore_slices(slices=slices, layer=layer)
[docs]def explore_slices(slices, layer, **kwargs): # pragma: no cover ''' Show figure of the given slice Parameters ---------- slices : array array of shape depth, height, width layer : int value between (0, slice.shape[0]) ''' if kwargs.get('figsize'): figsize = kwargs.get('figsize') plt.figure(figsize=figsize, constrained_layout=True) plt.imshow(slices[layer, ...], cmap='gray') else: plt.figure(figsize=(6, 6), constrained_layout=True) plt.imshow(slices[layer, ...], cmap='gray') if kwargs.get('fontsize'): fontsize = kwargs.get('fontsize') plt.title(f'Exploring Slice {layer}', fontsize=fontsize) else: plt.title(f'Exploring Slice {layer}', fontsize=15) plt.axis('off') plt.show()
[docs]def display_image(img, figsize=(12, 7), **kwargs): # pragma: no cover ''' Display greyscale image Parameters ---------- img : image, array_like image to be displayed figsize : tuple, optional figsize arg of matplotlib module, by default (12, 7) ''' plt.figure(figsize=(figsize), constrained_layout=True) plt.imshow(img, cmap='gray') if kwargs: title = kwargs.get('title') if kwargs.get('fontsize'): fontsize = kwargs.get('fontsize') plt.title(title, fontsize=fontsize) else: plt.title(title)
[docs]def display_images(display_list, figsize=(12, 8), **kwargs): # pragma: no cover ''' Display a list of greyscale images Parameters ---------- display_list : list list of images to be displayed figsize : tuple, optional figsize arg of matplotlib module, by default (12, 8) ''' plt.figure(figsize=figsize) for i, _ in enumerate(display_list): plt.subplot(1, len(display_list), i + 1) plt.imshow(display_list[i], cmap='gray') if kwargs.get('titles'): titles = kwargs.get('titles') if kwargs.get('fontsize'): fontsize = kwargs.get('fontsize') plt.title(titles[i], fontsize=fontsize) else: plt.title(titles[i]) if kwargs.get('axis'): plt.axis(kwargs.get('axis')) plt.show()