Source code for itkpocus.clarius

import numpy as np
import skvideo
import skvideo.io
from skimage.transform import SimilarityTransform
from scipy.signal import find_peaks
from itkpocus.util import get_framerate
import itk

'''
Preprocessing and device-specific IO for the Clarius HD C7.
'''

def _find_spacing_and_crop(npimg):
    '''
    Find the ruler in the image to calculate the physical dimensions and then crop to the B-mode data.
    
    Parameters
    ----------
    npimg : ndarray
        MxNx3
    
    Returns
    =======
    spacing
    crop : ndarray
        [[ymin,ymax],[xmin,xmax]]
    '''
    colsum = np.sum(npimg[:,:,0], axis=0)
    col_peaks, col_props = find_peaks(colsum, prominence=20)
    ruler_col = col_peaks[-1]
    ruler_peaks, ruler_props = find_peaks(npimg[:,ruler_col,0])
    # TODO: stress test this with max zoomed video, not sure if the ruler would switch to 0.5 cm spacing or < 4 circles would be on the video
    spacing = 10.0 / np.max(ruler_peaks[1:] - ruler_peaks[0:len(ruler_peaks)-1]) # 10 mm per ruler spacing, we get the max peak distance because half-circles on the top and bottom shift the peaks
    
    # find left/right bounds
    # just going assume the ultrasound is in the middle of the image and work outwards to find the letterboxing
    midcol = npimg.shape[1]/2.0 # not worried if non-integer
    zerocol = np.argwhere(colsum == 0)
    leftbound = np.max(zerocol[zerocol < midcol])+1
    rightbound = np.min(zerocol[midcol < zerocol])-1
    
    midrow = npimg.shape[0]/2.0
    rowsum = np.sum(npimg[:,leftbound:rightbound+1,0], axis=1)
    zerorow = np.argwhere(rowsum == 0)
    
    if len(zerorow) == 0: # fills the whole image vertically
        topbound=0
        bottombound=npimg.shape[0]-1
    else:
        topbound = np.max(zerorow[zerorow < midrow])
        bottombound = np.min(zerorow[midrow < zerorow])
    
    return spacing, np.array([[topbound, bottombound], [leftbound, rightbound]])

[docs]def preprocess_image(npimg, version=None): ''' Calculate physical spacing of image from identified ruler within image and crops to B-mode only. Parameters ---------- npimg : ndararay MxNx3 version : None reserved for future use Returns ------- itkImage[itk.F,2] cropped image scaled to 0.0 to 1.0 (MxN) with physical spacing ''' spacing, crop = _find_spacing_and_crop(npimg) img = itk.image_from_array((npimg[crop[0,0]:crop[0,1]+1, crop[1,0]:crop[1,1]+1, 0] / 255.0).astype('float32')) img.SetSpacing([spacing, spacing]) return img, { 'spacing' : [spacing, spacing], 'crop' : crop }
[docs]def preprocess_video(npvid, framerate=1, version=None): ''' npvid : ndarray video TxMxNx3 framerate : float framerate (e.g. extracted from ffprobe) version : None reserved for future use ''' npmean = np.mean(npvid, axis=0) spacing, crop = _find_spacing_and_crop(npmean) vid = itk.image_from_array((npvid[:,crop[0,0]:crop[0,1]+1, crop[1,0]:crop[1,1]+1,0] / 255.0).astype('float32').squeeze()) vid.SetSpacing([spacing, spacing, framerate]) return vid, { 'spacing' : [spacing, spacing, framerate], 'crop' : crop }
[docs]def load_and_preprocess_image(fp, version=None): ''' Loads and preprocesses a Clarius image. Parameters ---------- fp : str filepath to image version : None Reserved for future use. Returns ------- img : itk.Image[itk.F,2] Floating point image with physical spacing set, origin is at upper-left of image, and intensities are between 0.0 and 1.0 meta : dict Meta data (includes spacing and crop) ''' return preprocess_image(itk.imread(fp))
[docs]def load_and_preprocess_video(fp, version=None): ''' Loads and preprocesses a Clarius video. Parameters ---------- fp : str filepath to video (e.g. .mp4) version : optional Reserved for future use. Returns ------- img : itk.Image[itk.F,3] Floating point image with physical spacing set (3rd dimension is framerate), origin is at upper-left of image, and intensities are between 0.0 and 1.0 meta : dict Meta data (includes spacing and crop) ''' return preprocess_video(skvideo.io.vread(fp), framerate=get_framerate(skvideo.io.ffprobe(fp)))