def test_collage_class(): img_path = pjoin(base_dir, '3569_bl_PPMI.nii') img = read_image(img_path, None) scaled = scale_0to1(img) c = Collage(num_slices=15, view_set=(0, 1), num_rows=3) try: c.attach(scaled) except: raise ValueError('Attach does not work') try: c.transform_and_attach(scaled, np.square) except: raise ValueError('transform_and_attach does not work') try: print(c) except: raise ValueError('repr implementation failed')
class RatingWorkflowT1(BaseWorkflowVisualQC, ABC): """ Rating workflow without any overlay. """ def __init__(self, id_list, in_dir, out_dir, issue_list, mri_name, in_dir_type, images_for_id, outlier_method, outlier_fraction, outlier_feat_types, disable_outlier_detection, prepare_first, vis_type, views, num_slices_per_view, num_rows_per_view): """Constructor""" super().__init__(id_list, in_dir, out_dir, outlier_method, outlier_fraction, outlier_feat_types, disable_outlier_detection) self.vis_type = vis_type self.issue_list = issue_list self.mri_name = mri_name self.in_dir_type = in_dir_type self.images_for_id = images_for_id self.expt_id = 'rate_mri_{}'.format(self.mri_name) self.suffix = self.expt_id self.current_alert_msg = None self.prepare_first = prepare_first self.init_layout(views, num_rows_per_view, num_slices_per_view) self.init_getters() def preprocess(self): """ Preprocess the input data e.g. compute features, make complex visualizations etc. before starting the review process. """ if not self.disable_outlier_detection: print('Preprocessing data - please wait .. ' '\n\t(or contemplate the vastness of universe! )') self.extract_features() self.detect_outliers() # no complex vis to generate - skipping def prepare_UI(self): """Main method to run the entire workflow""" self.open_figure() self.add_UI() self.add_histogram_panel() def init_layout(self, views, num_rows_per_view, num_slices_per_view, padding=cfg.default_padding): plt.style.use('dark_background') # vmin/vmax are controlled, because we rescale all to [0, 1] self.display_params = dict(interpolation='none', aspect='equal', origin='lower', cmap='gray', vmin=0.0, vmax=1.0) self.figsize = cfg.default_review_figsize self.collage = Collage(view_set=views, num_slices=num_slices_per_view, num_rows=num_rows_per_view, display_params=self.display_params, bounding_rect=cfg.bounding_box_review, figsize=self.figsize) self.fig = self.collage.fig self.fig.canvas.set_window_title('VisualQC T1 MRI : {} {} ' ''.format(self.in_dir, self.mri_name)) self.padding = padding def init_getters(self): """Initializes the getters methods for input paths and feature readers.""" from visualqc.features import extract_T1_features self.feature_extractor = extract_T1_features if self.vis_type is not None and ( self.vis_type in cfg.freesurfer_vis_types or self.in_dir_type in [ 'freesurfer', ]): self.path_getter_inputs = lambda sub_id: realpath( pjoin(self.in_dir, sub_id, 'mri', self.mri_name)) else: if self.in_dir_type.upper() in ('BIDS', ): self.path_getter_inputs = lambda sub_id: self.images_for_id[ sub_id]['image'] else: self.path_getter_inputs = lambda sub_id: realpath( pjoin(self.in_dir, sub_id, self.mri_name)) def open_figure(self): """Creates the master figure to show everything in.""" plt.show(block=False) def add_UI(self): """Adds the review UI with defaults""" # two keys for same combinations exist to account for time delays in key presses map_key_to_callback = {'alt+s': self.show_saturated, 's+alt': self.show_saturated, 'alt+b': self.show_background_only, 'b+alt': self.show_background_only, 'alt+t': self.show_tails_trimmed, 't+alt': self.show_tails_trimmed, 'alt+o': self.show_original, 'o+alt': self.show_original} self.UI = T1MriInterface(self.collage.fig, self.collage.flat_grid, self.issue_list, next_button_callback=self.next, quit_button_callback=self.quit, processing_choice_callback=self.process_and_display, map_key_to_callback=map_key_to_callback) # connecting callbacks self.con_id_click = self.fig.canvas.mpl_connect('button_press_event', self.UI.on_mouse) self.con_id_keybd = self.fig.canvas.mpl_connect('key_press_event', self.UI.on_keyboard) # con_id_scroll = self.fig.canvas.mpl_connect('scroll_event', self.UI.on_scroll) self.fig.set_size_inches(self.figsize) def add_histogram_panel(self): """Extra axis for histogram""" self.ax_hist = plt.axes(cfg.position_histogram_t1_mri) self.ax_hist.set_xticks(cfg.xticks_histogram_t1_mri) self.ax_hist.set_yticks([]) self.ax_hist.set_autoscaley_on(True) self.ax_hist.set_prop_cycle('color', cfg.color_histogram_t1_mri) self.ax_hist.set_title(cfg.title_histogram_t1_mri, fontsize='small') def update_histogram(self, img): """Updates histogram with current image data""" nonzero_values = img.ravel()[np.flatnonzero(img)] _, _, patches_hist = self.ax_hist.hist(nonzero_values, density=True, bins=cfg.num_bins_histogram_display) self.ax_hist.relim(visible_only=True) self.ax_hist.autoscale_view(scalex=False) # xlim fixed to [0, 1] self.UI.data_handles.extend(patches_hist) def update_alerts(self): """Keeps a box, initially invisible.""" if self.current_alert_msg is not None: h_alert_text = self.fig.text(cfg.position_outlier_alert_t1_mri[0], cfg.position_outlier_alert_t1_mri[1], self.current_alert_msg, **cfg.alert_text_props) # adding it to list of elements to cleared when advancing to next subject self.UI.data_handles.append(h_alert_text) def add_alerts(self): """Brings up an alert if subject id is detected to be an outlier.""" flagged_as_outlier = self.current_unit_id in self.by_sample if flagged_as_outlier: alerts_list = self.by_sample.get(self.current_unit_id, None) # None, if id not in dict print('\n\tFlagged as a possible outlier by these measures:\n\t\t{}' ''.format('\t'.join(alerts_list))) strings_to_show = ['Flagged as an outlier:', ] + alerts_list self.current_alert_msg = '\n'.join(strings_to_show) self.update_alerts() else: self.current_alert_msg = None def load_unit(self, unit_id): """Loads the image data for display.""" # starting fresh for attr in ('current_img_raw', 'current_img', 'saturated_img', 'tails_trimmed_img', 'background_img'): if hasattr(self, attr): delattr(self, attr) t1_mri_path = self.path_getter_inputs(unit_id) self.current_img_raw = read_image(t1_mri_path, error_msg='T1 mri') # crop and rescale self.current_img = scale_0to1(crop_image(self.current_img_raw, self.padding)) self.currently_showing = None skip_subject = False if np.count_nonzero(self.current_img) == 0: skip_subject = True print('MR image is empty!') # # where to save the visualization to # out_vis_path = pjoin(self.out_dir, 'visual_qc_{}_{}'.format(self.vis_type, unit_id)) return skip_subject def display_unit(self): """Adds slice collage to the given axes""" # showing the collage self.collage.attach(self.current_img) # updating histogram self.update_histogram(self.current_img) def process_and_display(self, user_choice): """Updates the display after applying the chosen method.""" if user_choice in ('Saturate',): self.show_saturated(no_toggle=True) elif user_choice in ('Background only',): self.show_background_only(no_toggle=True) elif user_choice in ('Tails_trimmed', 'Tails trimmed'): self.show_tails_trimmed(no_toggle=True) elif user_choice in ('Original',): self.show_original() else: print('Chosen option seems to be not implemented!') def show_saturated(self, no_toggle=False): """Callback for ghosting specific review""" if not self.currently_showing in ['saturated', ] or no_toggle: if not hasattr(self, 'saturated_img'): self.saturated_img = saturate_brighter_intensities( self.current_img, percentile=cfg.saturate_perc_t1) self.collage.attach(self.saturated_img) self.currently_showing = 'saturated' else: self.show_original() def show_background_only(self, no_toggle=False): """Callback for ghosting specific review""" if not self.currently_showing in ['Background only', ] or no_toggle: self._compute_background() self.collage.attach(self.background_img) self.currently_showing = 'Background only' else: self.show_original() def _compute_background(self): """Computes the background image for the current image.""" if not hasattr(self, 'background_img'): # need to scale the mask, as Collage class does NOT automatically rescale self.foreground_mask = mask_image(self.current_img, out_dtype=bool) temp_background_img = np.copy(self.current_img) temp_background_img[self.foreground_mask] = 0.0 self.background_img = scale_0to1(temp_background_img, exclude_outliers_below=1, exclude_outliers_above=1) def show_tails_trimmed(self, no_toggle=False): """Callback for ghosting specific review""" if not self.currently_showing in ['tails_trimmed', ] or no_toggle: if not hasattr(self, 'tails_trimmed_img'): self.tails_trimmed_img = scale_0to1(self.current_img, exclude_outliers_below=1, exclude_outliers_above=0.05) self.collage.attach(self.tails_trimmed_img) self.currently_showing = 'tails_trimmed' else: self.show_original() def show_original(self): """Show the original""" self.collage.attach(self.current_img) self.currently_showing = 'original' def cleanup(self): """Preparating for exit.""" # save ratings before exiting self.save_ratings() self.fig.canvas.mpl_disconnect(self.con_id_click) self.fig.canvas.mpl_disconnect(self.con_id_keybd) plt.close('all')