def getArguments(parser): "Provides additional validation of the arguments collected by argparse." args = parser.parse_args() if args.paintc and None == args.contour: raise ArgumentError('If the "-p" switch is set, a contour file must be provide.') elif args.type in ['es'] and None == args.contour: raise ArgumentError('If the type is set to "es", a contour file must be provide.') return args
def main(): # parse cmd arguments parser = getParser() parser.parse_args() args = getArguments(parser) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load first input image as example example_data, example_header = load(args.inputs[0]) # test if the supplied position is valid if args.position > example_data.ndim or args.position < 0: raise ArgumentError( 'The supplied position for the new dimension is invalid. It has to be between 0 and {}.' .format(example_data.ndim)) # prepare empty output volume output_data = scipy.zeros([len(args.inputs)] + list(example_data.shape), dtype=example_data.dtype) # add first image to output volume output_data[0] = example_data # load input images and add to output volume for idx, image in enumerate(args.inputs[1:]): image_data, _ = load(image) if not args.ignore and image_data.dtype != example_data.dtype: raise ArgumentError( 'The dtype {} of image {} differs from the one of the first image {}, which is {}.' .format(image_data.dtype, image, args.inputs[0], example_data.dtype)) if image_data.shape != example_data.shape: raise ArgumentError( 'The shape {} of image {} differs from the one of the first image {}, which is {}.' .format(image_data.shape, image, args.inputs[0], example_data.shape)) output_data[idx + 1] = image_data # move new dimension to the end or to target position for dim in range(output_data.ndim - 1): if dim >= args.position: break output_data = scipy.swapaxes(output_data, dim, dim + 1) # set pixel spacing spacing = list(header.get_pixel_spacing(example_header)) spacing = tuple(spacing[:args.position] + [args.spacing] + spacing[args.position:]) example_header.set_voxel_spacing(spacing) # save created volume save(output_data, args.output, example_header, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load dicom slices [series] = pydicom_series.read_files( args.input, False, True) # second to not show progress bar, third to retrieve data #print series.sampling # Note: The first value is the mean of all differences between ImagePositionPatient-values of the DICOM slices - of course total bullshit data_3d = series.get_pixel_array() # check parameters if args.dimension >= data_3d.ndim or args.dimension < 0: raise ArgumentError( 'The image has only {} dimensions. The supplied target dimension {} exceeds this number.' .format(data_3d.ndim, args.dimension)) if not 0 == data_3d.shape[args.dimension] % args.offset: raise ArgumentError( 'The number of slices {} in the target dimension {} of the image shape {} is not dividable by the supplied number of consecutive slices {}.' .format(data_3d.shape[args.dimension], args.dimension, data_3d.shape, args.offset)) # prepare empty target volume volumes_3d = data_3d.shape[args.dimension] / args.offset shape_4d = list(data_3d.shape) shape_4d[args.dimension] = volumes_3d data_4d = scipy.zeros([args.offset] + shape_4d, dtype=data_3d.dtype) logger.debug( 'Separating {} slices into {} 3D volumes of thickness {}.'.format( data_3d.shape[args.dimension], volumes_3d, args.offset)) # iterate over 3D image and create sub volumes which are then added to the 4d volume for idx in range(args.offset): # collect the slices for sl in range(volumes_3d): idx_from = [slice(None), slice(None), slice(None)] idx_from[args.dimension] = slice(idx + sl * args.offset, idx + sl * args.offset + 1) idx_to = [slice(None), slice(None), slice(None)] idx_to[args.dimension] = slice(sl, sl + 1) #print 'Slice {} to {}.'.format(idx_from, idx_to) data_4d[idx][idx_to] = data_3d[idx_from] # flip dimensions such that the newly created is the last data_4d = scipy.swapaxes(data_4d, 0, 3) # save resulting 4D volume save(data_4d, args.output, False, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load 3d image data_3d, header_3d = load(args.input) # check if supplied dimension parameter is inside the images dimensions if args.dimension >= data_3d.ndim or args.dimension < 0: raise ArgumentError( 'The supplied cut-dimension {} exceeds the number of input volume dimensions {}.' .format(args.dimension, data_3d.ndim)) # check if the supplied offset parameter is a divider of the cut-dimensions slice number if not 0 == data_3d.shape[args.dimension] % args.offset: raise ArgumentError( 'The offset is not a divider of the number of slices in cut dimension ({} / {}).' .format(data_3d.shape[args.dimension], args.offset)) # prepare empty target volume volumes_3d = data_3d.shape[args.dimension] / args.offset shape_4d = list(data_3d.shape) shape_4d[args.dimension] = volumes_3d data_4d = scipy.zeros([args.offset] + shape_4d, dtype=data_3d.dtype) logger.debug( 'Separating {} slices into {} 3D volumes of thickness {}.'.format( data_3d.shape[args.dimension], volumes_3d, args.offset)) # iterate over 3D image and create sub volumes which are then added to the 4d volume for idx in range(args.offset): # collect the slices for sl in range(volumes_3d): idx_from = [slice(None), slice(None), slice(None)] idx_from[args.dimension] = slice(idx + sl * args.offset, idx + sl * args.offset + 1) idx_to = [slice(None), slice(None), slice(None)] idx_to[args.dimension] = slice(sl, sl + 1) #print 'Slice {} to {}.'.format(idx_from, idx_to) data_4d[idx][idx_to] = data_3d[idx_from] # flip dimensions such that the newly created is the last data_4d = scipy.swapaxes(data_4d, 0, args.dimension + 1) data_4d = scipy.rollaxis(data_4d, 0, 4) # save resulting 4D volume save(data_4d, args.output, header_3d, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) data_3d, _ = load(args.input) # check parameters if args.dimension >= data_3d.ndim or args.dimension < 0: raise ArgumentError('The image has only {} dimensions. The supplied target dimension {} exceeds this number.'.format( data_3d.ndim, args.dimension)) if not 0 == data_3d.shape[args.dimension] % args.offset: raise ArgumentError('The number of slices {} in the target dimension {} of the image shape {} is not dividable by the supplied number of consecutive slices {}.'.format( data_3d.shape[args.dimension], args.dimension, data_3d.shape, args.offset)) # prepare empty target volume volumes_3d = data_3d.shape[args.dimension] / args.offset shape_4d = list(data_3d.shape) shape_4d[args.dimension] = volumes_3d data_4d = scipy.zeros([args.offset] + shape_4d, dtype=data_3d.dtype) logger.debug('Separating {} slices into {} 3D volumes of thickness {}.'.format(data_3d.shape[args.dimension], volumes_3d, args.offset)) # iterate over 3D image and create sub volumes which are then added to the 4d volume for idx in range(args.offset): # collect the slices for sl in range(volumes_3d): idx_from = [slice(None), slice(None), slice(None)] idx_from[args.dimension] = slice(idx + sl * args.offset, idx + sl * args.offset + 1) idx_to = [slice(None), slice(None), slice(None)] idx_to[args.dimension] = slice(sl, sl+1) #print 'Slice {} to {}.'.format(idx_from, idx_to) data_4d[idx][idx_to] = data_3d[idx_from] # flip dimensions such that the newly created is the last data_4d = scipy.swapaxes(data_4d, 0, 3) # save resulting 4D volume save(data_4d, args.output, False, args.force) logger.info("Successfully terminated.")
def main(): # parse cmd arguments parser = getParser() parser.parse_args() args = getArguments(parser) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # check if output image exists (will also be performed before saving, but as the gradient might be time intensity, a initial check can save frustration) if not args.force: if os.path.exists(args.output): raise ArgumentError('The output image {} already exists.'.format( args.output)) # loading image data_input, header_input = load(args.input) logger.debug('Input array: dtype={}, shape={}'.format( data_input.dtype, data_input.shape)) # execute the gradient map filter logger.info('Applying gradient map filter...') data_output = filter.gradient_magnitude( data_input, header.get_pixel_spacing(header_input)) logger.debug('Resulting array: dtype={}, shape={}'.format( data_output.dtype, data_output.shape)) # save image save(data_output, args.output, header_input, args.force) logger.info('Successfully terminated.')
def getArguments(parser): "Provides additional validation of the arguments collected by argparse." args = parser.parse_args() if not '{}' in args.output: raise ArgumentError( args.output, 'The output argument string must contain the sequence "{}".') return args
def __create_markers(marker_data, marker_dim, di, do): """ Takes an image with markers inside the RV wall and returns two images with the inner repectively outer BG and FG markers. """ # constants contour_dimension = 2 time_dimension = 3 distance_inner = di # within the images 01-05 takes values down to 5, standard is 9 to leave some space distance_outer = do # within the images 01-05 takes values down to 6, standard is 10 to leave some space # prepare output volumes inner_data = scipy.zeros(marker_data.shape, scipy.uint8) outer_data = scipy.zeros(marker_data.shape, scipy.uint8) # prepare slicer slicer = [slice(None)] * marker_data.ndim # iterate over contour dimension and process each subvolume slice separately if marker_dim == contour_dimension: iter_dimension = time_dimension else: iter_dimension = contour_dimension for slice_id in range(marker_data.shape[iter_dimension]): slicer[contour_dimension] = slice(slice_id, slice_id+1) # extract subvolume marker_subvolume = scipy.squeeze(marker_data[slicer]) # skip step if no marker data present if 0 == len(marker_subvolume.nonzero()[0]): continue # check if the marker forms a closed circle marker_data_filled = scipy.ndimage.binary_fill_holes(marker_subvolume) if (marker_subvolume == marker_data_filled).all(): raise ArgumentError('The supplied marker does not form a closed structure!') # COMPUTE MARKERS FOR INNER WALL inner_data_subvolume = scipy.squeeze(inner_data[slicer]) marker_data_dilated = scipy.ndimage.binary_dilation(marker_subvolume, iterations = distance_inner) # dilate by distance_inner marker_data_dilated_filled = scipy.ndimage.binary_fill_holes(marker_data_dilated) # fill hole # fill hole of original and take inverse as inner bg marker (but erode first once for security) inner_data_subvolume[scipy.ndimage.binary_erosion(~scipy.ndimage.binary_fill_holes(marker_subvolume))] = 2 # if the dilation was to large to keep any markers, use less c = 0 while 0 == len(scipy.logical_xor(marker_data_dilated, marker_data_dilated_filled).nonzero()[0]): c += 1 marker_data_dilated = scipy.ndimage.binary_dilation(marker_subvolume, iterations = distance_inner - c) marker_data_dilated_filled = scipy.ndimage.binary_fill_holes(marker_data_dilated) # fill hole # take hole as inner fg marker inner_data_subvolume[scipy.logical_xor(marker_data_dilated, marker_data_dilated_filled)] = 1 # COMPUTE MARKERS FOR OUTER WALL outer_data_subvolume = scipy.squeeze(outer_data[slicer]) marker_data_dilated = scipy.ndimage.binary_dilation(marker_subvolume, iterations = distance_outer) # dilate by distance_outer marker_data_dilated_filled = scipy.ndimage.binary_fill_holes(marker_data_dilated) # fill hole # take inverse as outer bg marker outer_data_subvolume[~marker_data_dilated_filled] = 2 # fill hole of original and take as outer fg marker (but erode first twice for security) outer_data_subvolume[scipy.ndimage.binary_erosion(scipy.ndimage.binary_fill_holes(marker_subvolume), iterations=2)] = 1 return inner_data, outer_data
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load input image data_input, header_input = load(args.input) logger.debug('Original shape = {}.'.format(data_input.shape)) # check if supplied dimension parameters is inside the images dimensions if args.dimension1 >= data_input.ndim or args.dimension1 < 0: raise ArgumentError( 'The first swap-dimension {} exceeds the number of input volume dimensions {}.' .format(args.dimension1, data_input.ndim)) elif args.dimension2 >= data_input.ndim or args.dimension2 < 0: raise ArgumentError( 'The second swap-dimension {} exceeds the number of input volume dimensions {}.' .format(args.dimension2, data_input.ndim)) # swap axes data_output = scipy.swapaxes(data_input, args.dimension1, args.dimension2) # swap pixel spacing and offset ps = list(header.get_pixel_spacing(header_input)) ps[args.dimension1], ps[args.dimension2] = ps[args.dimension2], ps[ args.dimension1] header.set_pixel_spacing(header_input, ps) os = list(header.get_offset(header_input)) os[args.dimension1], os[args.dimension2] = os[args.dimension2], os[ args.dimension1] header.set_offset(header_input, os) logger.debug('Resulting shape = {}.'.format(data_output.shape)) # save resulting volume save(data_output, args.output, header_input, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # loading input images b0img, b0hdr = load(args.b0image) bximg, bxhdr = load(args.bximage) # check if image are compatible if not b0img.shape == bximg.shape: raise ArgumentError( 'The input images shapes differ i.e. {} != {}.'.format( b0img.shape, bximg.shape)) if not header.get_pixel_spacing(b0hdr) == header.get_pixel_spacing(bxhdr): raise ArgumentError( 'The input images voxel spacing differs i.e. {} != {}.'.format( header.get_pixel_spacing(b0hdr), header.get_pixel_spacing(bxhdr))) # check if supplied threshold value as well as the b value is above 0 if args.threshold is not None and not args.threshold >= 0: raise ArgumentError( 'The supplied threshold value must be greater than 0, otherwise a division through 0 might occur.' ) if not args.b > 0: raise ArgumentError('The supplied b-value must be greater than 0.') # compute threshold value if not supplied if args.threshold is None: b0thr = otsu(b0img, 32) / 2. # divide by 2 to decrease impact bxthr = otsu(bximg, 32) / 2. if 0 >= b0thr: raise ArgumentError( 'The supplied b0image seems to contain negative values.') if 0 >= bxthr: raise ArgumentError( 'The supplied bximage seems to contain negative values.') else: b0thr = bxthr = args.threshold logger.debug('thresholds={}/{}, b-value={}'.format(b0thr, bxthr, args.b)) # threshold b0 + bx DW image to obtain a mask # b0 mask avoid division through 0, bx mask avoids a zero in the ln(x) computation mask = (b0img > b0thr) & (bximg > bxthr) logger.debug( 'excluding {} of {} voxels from the computation and setting them to zero' .format(scipy.count_nonzero(mask), scipy.prod(mask.shape))) # compute the ADC adc = scipy.zeros(b0img.shape, b0img.dtype) adc[mask] = -1. * args.b * scipy.log(bximg[mask] / b0img[mask]) # saving the resulting image save(adc, args.output, b0hdr, args.force)
def extract_basal_slice(arr, head): """ Takes a 3D array, iterates over the first dimension and returns the first 2D plane which contains any non-zero values as we as its voxel spacing. """ for plane in arr: if scipy.any(plane): # get voxel spacing spacing = list(header.get_pixel_spacing(head)) spacing = spacing[1:3] # return plane and spacing return (plane, spacing) raise ArgumentError('The supplied array does not contain any object.')
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # loading input images (as image, header pairs) images = [] headers = [] for image_name in args.images: i, h = load(image_name) images.append(i) headers.append(h) # loading binary foreground masks if supplied, else create masks from threshold value if args.masks: masks = [load(mask_name)[0].astype(numpy.bool) for mask_name in args.masks] else: masks = [i > args.threshold for i in images] # if in application mode, load the supplied model and apply it to the images if args.lmodel: logger.info('Loading the model and transforming images...') with open(args.lmodel, 'r') as f: trained_model = pickle.load(f) if not isinstance(trained_model, IntensityRangeStandardization): raise ArgumentError('{} does not seem to be a valid pickled instance of an IntensityRangeStandardization object'.format(args.lmodel)) transformed_images = [trained_model.transform(i[m], surpress_mapping_check = args.ignore) for i, m in zip(images, masks)] # in in training mode, train the model, apply it to the images and save it else: logger.info('Training the average intensity model...') irs = IntensityRangeStandardization() trained_model, transformed_images = irs.train_transform([i[m] for i, m in zip(images, masks)], surpress_mapping_check = args.ignore) logger.info('Saving the trained model as {}...'.format(args.smodel)) with open(args.smodel, 'wb') as f: pickle.dump(trained_model, f) # save the transformed images if args.simages: logger.info('Saving intensity transformed images to {}...'.format(args.simages)) for ti, i, m, h, image_name in zip(transformed_images, images, masks, headers, args.images): i[m] = ti save(i, '{}/{}'.format(args.simages, image_name.split('/')[-1]), h, args.force) logger.info('Terminated.')
def main(): # parse cmd arguments parser = getParser() parser.parse_args() args = getArguments(parser) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load input image data_input, header_input = load(args.input) # check if the supplied dimension is valid if args.dimension >= data_input.ndim or args.dimension < 0: raise ArgumentError( 'The supplied cut-dimension {} exceeds the image dimensionality of 0 to {}.' .format(args.dimension, data_input.ndim - 1)) # prepare output file string name_output = args.output.replace('{}', '{:03d}') # compute the new the voxel spacing spacing = list(header.get_pixel_spacing(header_input)) del spacing[args.dimension] # iterate over the cut dimension slices = data_input.ndim * [slice(None)] for idx in range(data_input.shape[args.dimension]): # cut the current slice from the original image slices[args.dimension] = slice(idx, idx + 1) data_output = scipy.squeeze(data_input[slices]) # update the header and set the voxel spacing __update_header_from_array_nibabel(header_input, data_output) header.set_pixel_spacing(header_input, spacing) # save current slice save(data_output, name_output.format(idx), header_input, args.force) logger.info("Successfully terminated.")
def main(): # parse cmd arguments parser = getParser() parser.parse_args() args = getArguments(parser) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load input image logger.info('Loading source image {}...'.format(args.input)) try: input_data = scipy.squeeze(load(args.input).get_data()).astype( scipy.bool_) except ImageFileError as e: logger.critical( 'The region image does not exist or its file type is unknown.') raise ArgumentError( 'The region image does not exist or its file type is unknown.', e) # iterate over designated dimension and create for each such extracted slice a text file logger.info('Processing per-slice and writing to files...') idx = [slice(None)] * input_data.ndim for slice_idx in range(input_data.shape[args.dimension]): idx[args.dimension] = slice(slice_idx, slice_idx + 1) # 2009: IM-0001-0027-icontour-manual file_name = '{}/IM-0001-{:04d}-{}contour-auto.txt'.format( args.target, slice_idx + args.offset, args.ctype) # 2012: P01-0080-icontour-manual.txt file_name = '{}/P{}-{:04d}-{}contour-auto.txt'.format( args.target, args.id, slice_idx + args.offset, args.ctype) # check if output file already exists if not args.force: if os.path.exists(file_name): logger.warning( 'The output file {} already exists. Skipping.'.format( file_name)) continue # extract current slice image_slice = scipy.squeeze(input_data[idx]) # remove all objects except the largest image_labeled, labels = scipy.ndimage.label(image_slice) if labels > 1: logger.info( 'The slice {} contains more than one object. Removing the smaller ones.' .format(file_name)) # determine biggest biggest = 0 biggest_size = 0 for i in range(1, labels + 1): if len((image_labeled == i).nonzero()[0]) > biggest_size: biggest_size = len((image_labeled == i).nonzero()[0]) biggest = i # remove others for i in range(1, labels + 1): if i == biggest: continue image_labeled[image_labeled == i] = 0 # save to slice image_slice = image_labeled.astype(scipy.bool_) # perform some additional morphological operations image_slice = scipy.ndimage.morphology.binary_fill_holes(image_slice) footprint = scipy.ndimage.morphology.generate_binary_structure( image_slice.ndim, 3) image_slice = scipy.ndimage.morphology.binary_closing(image_slice, footprint, iterations=7) #image_slice = scipy.ndimage.morphology.binary_opening(image_slice, footprint, iterations=3) # if type == o, perform a dilation to increase the size slightly #if 'o' == args.ctype: # footprint = scipy.ndimage.morphology.generate_binary_structure(image_slice.ndim, 3) # image_slice = scipy.ndimage.morphology.binary_dilation(image_slice, iterations=3) # erode contour in slice input_eroded = scipy.ndimage.morphology.binary_erosion(image_slice, border_value=1) image_slice ^= input_eroded # xor # extract contour positions and put into right order contour_tmp = image_slice.nonzero() contour = [[] for i in range(len(contour_tmp[0]))] for i in range(len(contour_tmp[0])): for j in range(len(contour_tmp)): contour[i].append(contour_tmp[j][i]) # x, y, z, .... if 0 == len(contour): logger.warning( 'Empty contour for file {}. Skipping.'.format(file_name)) continue # create final points following along the contour (incl. linear sub-voxel precision) divider = 2 point = contour[0] point_pos = 0 processed = [point_pos] contour_final = [] while point: nearest_pos = __find_nearest(point, contour, processed) if False == nearest_pos: break contour_final.extend( __draw_line(point, contour[nearest_pos], divider)) processed.append(nearest_pos) point = contour[nearest_pos] # make connection between last and first point contour_final.extend(__draw_line(point, contour[0], divider)) # save contour to file logger.debug('Creating file {}...'.format(file_name)) with open(file_name, 'w') as f: for line in contour_final: f.write('{}\n'.format(' '.join(map(str, line)))) logger.info('Successfully terminated.')
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # constants temporal_dimension = 3 # check parameters if args.bg_threshold > 0.5: logger.warning( 'The supplied BG threshold is rather high. This might corrupt the results.' ) if args.fg_threshold < 0.5: logger.warning( 'The supplied FG threshold is rather low. This might corrupt the results.' ) # check if output file already exists if not args.force and os.path.exists(args.output): raise ArgumentError( 'The supplied output file {} already exists.'.format(args.output)) # loading atlas image atlas_data, atlas_header = load(args.atlas) # prepare neutral slice object slicer = [slice(None) for _ in range(atlas_data.ndim)] logger.info('Extracting ED and ES phase volumes...') # extract ED 3D volume slicer[temporal_dimension] = slice(0, 1) # first is ed phase ed_data_fg = scipy.squeeze(atlas_data[slicer]) # extract ES 3D volume es_data_fg = None for slidx in range(1, atlas_data.shape[temporal_dimension]): slicer[temporal_dimension] = slice(slidx, slidx + 1) if scipy.any(atlas_data[slicer]): es_data_fg = scipy.squeeze( atlas_data[slicer]) # first after ed phase is es phase es_slidx = slidx # conserve for later usage continue if None == es_data_fg: raise AttributeError( 'Could not find any data in the ES phase of the atlas image.') logger.debug('Extracted ed volume {} and es volume {}.'.format( ed_data_fg.shape, es_data_fg.shape)) # split into FG and BG volumes and convert to bool type logger.info( 'Splitting into FG and BG binary volumes representing the markers...') ed_data_bg = ed_data_fg.copy() es_data_bg = es_data_fg.copy() ed_data_fg[ed_data_fg <= args.fg_threshold] = 0 es_data_fg[es_data_fg <= args.fg_threshold] = 0 ed_data_bg[ed_data_bg <= args.bg_threshold] = 0 es_data_bg[es_data_bg <= args.bg_threshold] = 0 ed_data_fg = ed_data_fg.astype(scipy.bool_) es_data_fg = es_data_fg.astype(scipy.bool_) ed_data_bg = ~ed_data_bg.astype(scipy.bool_) es_data_bg = ~es_data_bg.astype(scipy.bool_) # see if any is empty if not scipy.any(es_data_fg): raise AttributeError('No foreground marker data for es phase.') elif not scipy.any(es_data_bg): raise AttributeError('No background marker data for es phase.') elif not scipy.any(ed_data_fg): raise AttributeError('No foreground marker data for ed phase.') elif not scipy.any(ed_data_bg): raise AttributeError('No background marker data for ed phase.') marker_data = scipy.zeros(atlas_data.shape, scipy.uint8) # enhance the markers by interpolating between binary foreground objects of ED and ES phase logger.info('Enhancing the markers from ED-first to ES phase...') slicer[temporal_dimension] = slice(0, es_slidx + 1) # first half marker_data[slicer] = interpolateBetweenBinaryObjects( ed_data_fg, es_data_fg, es_slidx - 1) logger.info('Enhancing the markers from ES to ED-second phase...') slicer[temporal_dimension] = slice(es_slidx + 1, None) # second half marker_data[slicer] = interpolateBetweenBinaryObjects( es_data_fg, ed_data_fg, marker_data.shape[temporal_dimension] - es_slidx - 3) # setting the background markers logger.info('Setting bg markers...') slicer[temporal_dimension] = slice(0, 1) # first ed phase marker_data[slicer][ed_data_bg] = 2 slicer[temporal_dimension] = slice(-1, None) # second ed phase marker_data[slicer][ed_data_bg] = 2 slicer[temporal_dimension] = slice(es_slidx, es_slidx + 1) # es phase marker_data[slicer][es_data_bg] = 2 save(marker_data, args.output, atlas_header, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load original example volume original_data, original_header = load(args.original) # prepare execution result_data = scipy.zeros(original_data.shape, scipy.uint8) del original_data # First step: Combine all marker images basename_old = False # iterate over marker images for marker_image in args.input: # extract information from filename and prepare slicr object basename, slice_dimension, slice_number = re.match(r'.*m(.*)_d([0-9])_s([0-9]{4}).*', marker_image).groups() slice_dimension = int(slice_dimension) slice_number = int(slice_number) # check basenames if basename_old and not basename_old == basename: logger.warning('The marker seem to come from different sources. Encountered basenames {} and {}. Continuing anyway.'.format(basename, basename_old)) basename_old = basename # prepare slicer slicer = [slice(None)] * result_data.ndim slicer[slice_dimension] = slice_number # load marker image marker_data, _ = load(marker_image) # add to marker image ONLY where this is zero! result_data_subvolume = result_data[slicer] mask_array = result_data_subvolume == 0 result_data_subvolume[mask_array] = marker_data[mask_array] if not 0 == len(marker_data[~mask_array].nonzero()[0]): logger.warning('The mask volume {} showed some intersection with previous mask volumes. Up to {} marker voxels might be lost.'.format(marker_image, len(marker_data[~mask_array].nonzero()[0]))) # Second step: Normalize and determine type of markers result_data[result_data >= 10] = 0 # remove markers with indices higher than 10 #result_data = relabel_non_zero(result_data) # relabel starting from 1, 0's are kept where encountered marker_count = len(scipy.unique(result_data)) if 3 > marker_count: # less than two markers raise ArgumentError('A minimum of two markers must be contained in the conjunction of all markers files (excluding the neutral markers of index 0).') # assuming here that 1 == inner marker, 2 = border marker and 3 = background marker inner_name = args.output.format('i') inner_data = scipy.zeros_like(result_data) inner_data[result_data == 1] = 1 inner_data[result_data == 2] = 2 inner_data[result_data == 3] = 2 save(inner_data, inner_name, original_header, args.force) outer_name = args.output.format('o') outer_data = scipy.zeros_like(result_data) outer_data[result_data == 1] = 1 outer_data[result_data == 2] = 1 outer_data[result_data == 3] = 2 save(outer_data, outer_name, original_header, args.force) # for marker in scipy.unique(result_data)[1:-1]: # first is neutral marker (0) and last overall background marker # output = args.output.format(marker) # _data = scipy.zeros_like(result_data) # _data += 2 # set all as BG markers # _data[result_data == marker] = 1 # _data[result_data == 0] = 0 # save(_data, output, original_header, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # constants spatial_dimension = 0 # loading input image input_data, input_header = load(args.input) # check if it has the right dimensions, otherwise cut down to expected number if 3 < input_data.ndim: _tmp = input_data.shape slicer = [slice(0, 1) for _ in range(input_data.ndim)] for slid in range(3): slicer[slid] = slice(None) input_data = scipy.squeeze(input_data[slicer]) logger.warning( 'Found more than the expected number of 3 dimensions in the input image. Reduced from shape {} to {}.' .format(_tmp, input_data.shape)) elif 3 > input_data.ndim: raise ArgumentError( 'Invalid number of image dimensions {}, expects 3.'.format( input_data.ndim)) # if input volume is of type manual markers, remove all above 1 if 'manual' == args.type: logger.info('Extracting foreground markers...') input_data[input_data > 1] = 0 # select the first basal slice logger.info('Selecting basal slice...') slicer = [slice(None) for _ in range(input_data.ndim)] basal_slice = None for slid in range(input_data.shape[spatial_dimension]): slicer[spatial_dimension] = slice(slid, slid + 1) if input_data[slicer].any(): basal_slice = input_data[slicer].astype(scipy.bool_) break # raise error if no slice with values found if None == basal_slice: raise ArgumentError( 'The supported input image contains no non-zero data.') logger.debug( 'Found basal slice of shape {} at spatial position {} with {} voxels.'. format(basal_slice.shape, slid, len(basal_slice.nonzero()[0]))) # filling eventual holes logger.debug('Filling eventual holes...') basal_slice[0] = binary_fill_holes(scipy.squeeze( basal_slice)) # !TODO: Use spatial_dimension instead of the 0 here # dilate the slice logger.info('Dilating...') basal_slice = scipy.ndimage.binary_dilation(basal_slice, iterations=args.dilations) logger.debug('{} voxels after dilation with {} iterations.'.format( len(basal_slice.nonzero()[0]), args.dilations)) # build up the tube logger.info('Constructing tube...') output_data = scipy.concatenate( [basal_slice for _ in range(input_data.shape[spatial_dimension])], axis=spatial_dimension) logger.debug('Shape of constructed tube is {}.'.format(output_data.shape)) # check if resulting shape is valid if input_data.shape != output_data.shape: raise Exception( 'The shape of the final data differs with {} of the shape of the original image {}.' .format(output_data.shape, input_data.shape)) # save resulting image save(output_data, args.output, input_header, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # constants colours = {'i': 10, 'o': 11} # prepare parameters image_directory = False contour_directory = False for item in os.listdir(args.input): if not os.path.isdir('{}/{}'.format(args.input, item)): continue if 'dicom' in item: image_directory = '{}/{}'.format(args.input, item) elif 'contours-manual' in item: contour_directory = '{}/{}'.format(args.input, item) if not (image_directory and contour_directory): raise ArgumentError( 'The supplied source directory {} is invalid.'.format(args.input)) # count total number of slices via counting the dicom files slice_number = len([ name for name in os.listdir(image_directory) if os.path.isfile('{}/{}'.format(image_directory, name)) ]) # extract other dimensionalities from first dicom image dicom_files = [ '{}/{}'.format(image_directory, name) for name in os.listdir(image_directory) if os.path.isfile('{}/{}'.format(image_directory, name)) and '.dcm' in name ] example_data, _ = load(dicom_files[0]) # create target image result_data = scipy.zeros(tuple([slice_number]) + example_data.shape, dtype=scipy.uint8) # iterate over contour files for contour_file in os.listdir(contour_directory): logger.info('Parsing contour file {}...'.format(contour_file)) # determine type of contour by filename _, slice_number, contour_type, _ = contour_file.split('-') slice_number = int(slice_number) contour_type = contour_type[0] # select "colour" colour = colours[contour_type] last_x = False last_y = False # iterate over file content with open('{}/{}'.format(contour_directory, contour_file), 'r') as f: for line in f.readlines(): # clean and test line line = line.strip() if 0 == len(line) or '#' == line[0]: continue # extract contour coordinates and round to full number x, y = map(int, map(round, map(float, line.split(' ')))) # paint contours result_data[slice_number, x, y] = colour # paint additional contours if jump was to large if last_x and last_y: for _x, _y in __get_hole_coordinates(last_x, last_y, x, y): result_data[slice_number, _x, _y] = colour # set current coordinates as last last_x = x last_y = y # save result contour volume save(result_data, args.output, False, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load original example volume original_data, original_header = load(args.original) # prepare execution inner_data = scipy.zeros(original_data.shape, scipy.uint8) outer_data = scipy.zeros(original_data.shape, scipy.uint8) del original_data # First step: Constructing all marker images logger.info('Constructing marker information...') basename_old = False # iterate over marker images for marker_image in args.input: # extract information from filename and prepare slicer object basename, slice_dimension, slice_number = re.match( r'.*m(.*)_d([0-9])_s([0-9]{4}).*', marker_image).groups() slice_dimension = int(slice_dimension) slice_number = int(slice_number) # check basenames if basename_old and not basename_old == basename: logger.warning( 'The marker seem to come from different sources. Encountered basenames {} and {}. Continuing anyway.' .fromat(basename, basename_old)) basename_old = basename # prepare slicer slicer = [slice(None)] * inner_data.ndim slicer[slice_dimension] = slice_number # load marker image and prepare (deleting markers with indices > 10 and mergin all others) marker_data, _ = load(marker_image) marker_data[marker_data >= 10] = 0 # create markers from sparse marker data marker_data_inner, marker_data_outer = __create_markers( marker_data.astype(scipy.bool_), slice_dimension, args.di, args.do) # add to resulting final marker images inner_data[slicer] += marker_data_inner outer_data[slicer] += marker_data_outer # check if any marker present if 0 == len(inner_data.nonzero()[0]): raise ArgumentError('No markers for the inner wall could be created.') if 0 == len(outer_data.nonzero()[0]): raise ArgumentError('No markers for the outer wall could be created.') # Second step: Thin out marker information by emptying every second slice if args.thinning: logger.info('Thinning out every second slice...') inner_data = __thin_out_markers(inner_data) outer_data = __thin_out_markers(outer_data) # Third step: Enhance marker information by propagating it over time logger.info('Enhancing marker information...') inner_data = __enhance_markers(inner_data, args.efg, args.ebg) outer_data = __enhance_markers(outer_data, args.di, args.do) # saving inner_name = args.output.format('i') save(inner_data, inner_name, original_header, args.force) outer_name = args.output.format('o') save(outer_data, outer_name, original_header, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # constants temporal_dimension = 3 spatial_dimension = 0 # slice-wise threshold values (starting from first basal slice downwards) # the first value of the tuple represents the threshold, the second the erosion's iterations # generic fixed to 0.0 (single threshold) #bg_ed = [(0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0)] #bg_es = [(0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0), (0.0, 0)] # rigid01-bg bg_ed = [(0.14, 10), (0.14, 8), (0.21, 10), (0.14, 6), (0.21, 6), (0.21, 8), (0.14, 6), (0.21, 6), (0.21, 4)] bg_es = [(0.0, 8), (0.07, 10), (0.14, 10), (0.14, 8), (0.21, 10), (0.07, 2), (0.21, 8), (0.07, 6)] # rigid01-fg maxcover fg_ed = [(0.52, 2), (0.52, 4), (0.52, 4), (0.52, 4), (0.72, 0), (0.72, 0), (0.52, 2), (0.32, 10), (0.39, 2)] fg_es = [(0.26, 8), (0.39, 4), (0.59, 4), (0.39, 8), (0.72, 2), (0.79, 2), (0.65, 0), (0.45, 2)] # rigid01-fg minerror #fg_ed = [(0.52, 4), (0.52, 4), (0.65, 4), (0.52, 6), (0.72, 2), (0.72, 2), (0.45, 4), (0.39, 10), (0.45, 0)] #fg_es = [(0.32, 8), (0.39, 6), (0.79, 2), (0.79, 2), (0.72, 4), (0.79, 2), (0.79, 0), (0.79, 2)] # rigid08-bg #bg_ed = [(0.07, 8), (0.07, 8), (0.21, 10), (0.14, 6), (0.21, 6), (0.14, 4), (0.14, 6), (0.21, 8), (0.21, 4)] #bg_es = [(0.0, 8), (0.07, 10), (0.14, 10), (0.21, 10), (0.21, 8), (0.07, 2), (0.21, 8), (0.07, 6)] # rigid08-fg maxcover #fg_ed = [(0.45, 4), (0.45, 4), (0.65, 2), (0.52, 4), (0.59, 2), (0.59, 2), (0.52, 2), (0.39, 8), (0.39, 2)] #fg_es = [(0.26, 8), (0.39, 4), (0.39, 8), (0.45, 6), (0.72, 2), (0.72, 2), (0.59, 2), (0.65, 0)] # rigid09-bg #bg_ed = [(0.07, 6), (0.14, 10), (0.21, 10), (0.21, 6), (0.21, 6), (0.14, 6), (0.21, 10), (0.21, 8), (0.21, 2)] #bg_es = [(0.0, 8), (0.07, 10), (0.14, 10), (0.21, 10), (0.21, 8), (0.14, 4), (0.21, 6), (0.07, 6)] # rigid09-fg maxcover #fg_ed = [(0.52, 2), (0.45, 4), (0.52, 4), (0.52, 4), (0.52, 4), (0.52, 4), (0.52, 2), (0.85, 0), (0.26, 4)] #fg_es = [(0.19, 8), (0.32, 6), (0.39, 8), (0.39, 8), (0.59, 6), (0.85, 0), (0.59, 2), (0.52, 0)] # check if output file already exists if not args.force and os.path.exists(args.output): raise ArgumentError('The supplied output file {} already exists.'.format(args.output)) # loading atlas image atlas_data, atlas_header = load(args.atlas) logger.info('Extracting ED and ES phase volumes...') # extract ED 3D volume slicer = [slice(None) for _ in range(atlas_data.ndim)] slicer[temporal_dimension] = slice(0,1) # first is ed phase ed_data_fg = scipy.squeeze(atlas_data[slicer]) # extract ES 3D volume es_data_fg = None for slidx in range(1, atlas_data.shape[temporal_dimension]): slicer[temporal_dimension] = slice(slidx, slidx + 1) if scipy.any(atlas_data[slicer]): es_data_fg = scipy.squeeze(atlas_data[slicer]) # first after ed phase is es phase es_slidx = slidx # conserve for later usage continue if None == es_data_fg: raise AttributeError('Could not find any data in the ES phase of the atlas image.') logger.debug('Extracted ed volume {} and es volume {}.'.format(ed_data_fg.shape, es_data_fg.shape)) # iterate over slices of ED and ES phases and apply the respective thresholding and erosion operations logger.info('Splitting into FG and BG binary volumes representing the markers...') ed_data_bg = ed_data_fg.copy() es_data_bg = es_data_fg.copy() for slid in range(0, ed_data_fg.shape[spatial_dimension]): # create fg markers if slid < len(fg_ed): ed_data_fg[slid] = extract_fg_markers(ed_data_fg[slid], *fg_ed[slid]) else: ed_data_fg[slid] = 0 if slid < len(fg_es): es_data_fg[slid] = extract_fg_markers(es_data_fg[slid], *fg_es[slid]) else: es_data_fg[slid] = 0 # create bg markers if slid < len(bg_ed): ed_data_bg[slid] = extract_bg_markers(ed_data_bg[slid], *bg_ed[slid]) else: ed_data_bg[slid] = 0 if slid < len(bg_es): es_data_bg[slid] = extract_bg_markers(es_data_bg[slid], *bg_es[slid]) else: es_data_bg[slid] = 0 ed_data_fg = ed_data_fg.astype(scipy.bool_) es_data_fg = es_data_fg.astype(scipy.bool_) ed_data_bg = ed_data_bg.astype(scipy.bool_) es_data_bg = es_data_bg.astype(scipy.bool_) # see if any is empty if not scipy.any(es_data_fg): raise AttributeError('No foreground marker data for es phase.') elif not scipy.any(es_data_bg): raise AttributeError('No background marker data for es phase.') elif not scipy.any(ed_data_fg): raise AttributeError('No foreground marker data for ed phase.') elif not scipy.any(ed_data_bg): raise AttributeError('No background marker data for ed phase.') marker_data = scipy.zeros(atlas_data.shape, scipy.uint8) # enhance the markers by interpolating between binary foreground objects of ED and ES phase logger.info('Enhancing the markers from ED-first to ES phase...') slicer[temporal_dimension] = slice(0, es_slidx + 1) # first half marker_data[slicer] = interpolateBetweenBinaryObjects(ed_data_fg, es_data_fg, es_slidx - 1) logger.info('Enhancing the markers from ES to ED-second phase...') slicer[temporal_dimension] = slice(es_slidx + 1, None) # second half marker_data[slicer] = interpolateBetweenBinaryObjects(es_data_fg, ed_data_fg, marker_data.shape[temporal_dimension] - es_slidx - 3) # setting the background markers logger.info('Setting bg markers...') slicer[temporal_dimension] = slice(0,1) # first ed phase marker_data[slicer][ed_data_bg] = 2 slicer[temporal_dimension] = slice(-1, None) # second ed phase marker_data[slicer][ed_data_bg] = 2 slicer[temporal_dimension] = slice(es_slidx, es_slidx + 1) # es phase marker_data[slicer][es_data_bg] = 2 save(marker_data, args.output, atlas_header, args.force) logger.info("Successfully terminated.")
def main(): args = getArguments(getParser()) # prepare logger logger = Logger.getInstance() if args.debug: logger.setLevel(logging.DEBUG) elif args.verbose: logger.setLevel(logging.INFO) # load input images data_fixed, header_fixed = load(args.fixed) data_moving, header_moving = load(args.moving) # convert to binary arrays data_fixed = data_fixed.astype(scipy.bool_) data_moving = data_moving.astype(scipy.bool_) # check that they are 3D volumes and contain an object if not 3 == data_fixed.ndim: raise ArgumentError( 'The fixed image has {} instead of the expected 3 dimensions.'. format(data_fixed.ndim)) if not 3 == data_moving.ndim: raise ArgumentError( 'The moving image has {} instead of the expected 3 dimensions.'. format(data_moving.ndim)) if not scipy.any(data_fixed): raise ArgumentError('The fixed image contains no binary object.') if not scipy.any(data_moving): raise ArgumentError('The moving image contains no binary object.') # get voxel spacing of fixed image fixed_spacing = header.get_pixel_spacing(header_fixed) # extract the first basal slices form both RV objects basal_fixed, basal_fixed_spacing = extract_basal_slice( data_fixed, header_fixed) basal_moving, basal_moving_spacing = extract_basal_slice( data_moving, header_moving) logger.debug( 'Extracted basal slices fixed: {} and moving: {} with voxel spacing {} resp. {}.' .format(basal_fixed.shape, basal_moving.shape, basal_fixed_spacing, basal_moving_spacing)) # get points of interest fixed_basep, fixed_otherp = get_points_of_interest(basal_fixed) moving_basep, moving_otherp = get_points_of_interest(basal_moving) logger.debug( 'Points of interest found are fixed: {} / {} and moving: {} / {}.'. format(fixed_basep, fixed_otherp, moving_basep, moving_otherp)) # translate all points of interest to physical coordinate system fixed_basep = [x * y for x, y in zip(fixed_basep, basal_fixed_spacing)] fixed_otherp = [x * y for x, y in zip(fixed_otherp, basal_fixed_spacing)] moving_basep = [x * y for x, y in zip(moving_basep, basal_moving_spacing)] moving_otherp = [ x * y for x, y in zip(moving_otherp, basal_moving_spacing) ] logger.debug( 'Points of interest translated to real-world coordinates are fixed: {} / {} and moving: {} / {}.' .format(fixed_basep, fixed_otherp, moving_basep, moving_otherp)) # determine shift to unite the two base-points shift = (fixed_basep[0] - moving_basep[0], fixed_basep[1] - moving_basep[1]) logger.debug('Shift to unite base-point is {}.'.format(shift)) # shift the vector end-point of the moving object's vector so that it shares the # same base as the fixed vector moving_otherp = shiftp(moving_otherp, shift) logger.debug('Shifted vector end-point of moving image is {}.'.format( moving_otherp)) # assure correctness of shift if not scipy.all( [x == y for x, y in zip(fixed_basep, shiftp(moving_basep, shift))]): raise ArgumentError( 'Aligning base-point through shifting failed due to unknown reason: {} does not equal {}.' .format(shiftp(moving_basep, shift), fixed_basep)) # shift both vector end-points to origin base fixed_otherp = shiftp(fixed_otherp, map(lambda x: -1. * x, fixed_basep)) moving_otherp = shiftp(moving_otherp, map(lambda x: -1. * x, fixed_basep)) # determine angle angle = angle_between_vectors(fixed_otherp, moving_otherp) logger.debug('Angle set to {} degree in radians.'.format( math.degrees(angle))) # determine angle turn point turn_point = fixed_basep logger.debug( 'Turn point set to {} in real-world coordinates.'.format(turn_point)) # reverse shift to fit into the 'elastix' view shift = [-1. * x for x in shift] # print results print '// {}'.format(math.degrees(angle)) print """ //# SOME NOTES ON 'ELASTIX' //# 1. 'elastix' performs shifting before rotation! //# 2. 'elastix' works on the real world coordinates (i.e. with voxel spacing of 0) """ print transform_string(data_fixed.shape, fixed_spacing, [0] + list(shift), [angle, 0, 0], [0] + list(turn_point)) logger.info("Successfully terminated.")