Module movemeter.tkgui
A tkinter/tk GUI for Movemeter the motion analysis tool.
Features In Short
- Load, view and exclude images
- Draw variously shaped ROIs that are made from small, rectangular (square) cross-correlation windows, and allow grouping these ROIs
- Perform motion analysis and save results
- View and save motion analysis heatmaps
This file contains most of the GUI elements extra logic (such as saving results) that are not present in the movemeter.py file.
Functions
def main()
-
Initialize tkinter and start the Movemeter GUI.
Classes
class ColormapSelector (tk_parent, callback, startmap=None)
-
Widget to preview and select a matplotlib colormap.
tk_parent : object Tkinter parent widget callback : callable When selected, the colormap passed to this callback function startmap : string Name of the colormap to start with.
Expand source code
class ColormapSelector(tk.Frame): ''' Widget to preview and select a matplotlib colormap. ''' def __init__(self, tk_parent, callback, startmap=None): ''' tk_parent : object Tkinter parent widget callback : callable When selected, the colormap passed to this callback function startmap : string Name of the colormap to start with. ''' tk.Frame.__init__(self, tk_parent) self._callback = callback # Dict of all availbale colormap objects self.colormaps = {name: getattr(matplotlib.cm, name) for name in dir(matplotlib.cm) if isinstance( getattr(matplotlib.cm, name), matplotlib.colors.Colormap)} self.listbox = Listbox(self, list(self.colormaps.keys()), callback=self.on_selection) self.listbox.grid(row=1, column=1, sticky='NSWE') self.plotter = CanvasPlotter(self, text='Preview', figsize=(0.5,5)) self.plotter.grid(row=1, column=2, sticky='NSWE') data = np.linspace(0,10)[:, np.newaxis] self.plotter.imshow(data) if startmap: self.on_selection(startmap) self.select_button = tk.Button(self, text='Ok', command=self.on_ok) self.select_button.grid(row=2, column=1, columnspan=2, sticky='NSWE') self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(1, weight=10) self.grid_columnconfigure(1, weight=1) def on_selection(self, name): self._current = name self.plotter.imshow_obj.cmap = self.colormaps[name] self.plotter.update() def on_ok(self): self._callback(self.colormaps[self._current])
Ancestors
- tkinter.Frame
- tkinter.Widget
- tkinter.BaseWidget
- tkinter.Misc
- tkinter.Pack
- tkinter.Place
- tkinter.Grid
Methods
def on_ok(self)
def on_selection(self, name)
class MovemeterSettings (tk_parent)
-
Movemeter settings widget, semi-automatically inspected from the Movemeter.init method.
Use get_current method to retrive the setting dictionary.
Attributes
tickboxes
:object
- tk_steroids TickboxFrame containing False/True options.
maxmovement_slider
,blur_slider
,cores_slider
,upscale_slider
:object
- tkinter Slider widgets
tk_parent Tkinter parent widget
Expand source code
class MovemeterSettings(tk.Frame): ''' Movemeter settings widget, semi-automatically inspected from the Movemeter.__init__ method. Use get_current method to retrive the setting dictionary. Attributes ---------- tickboxes : object tk_steroids TickboxFrame containing False/True options. maxmovement_slider, blur_slider, cores_slider, upscale_slider: object tkinter Slider widgets ''' def __init__(self, tk_parent): ''' tk_parent Tkinter parent widget ''' tk.Frame.__init__(self, tk_parent) self.columnconfigure(2, weight=1) # Movemeter True/False options; Automatically inspect from Movemeter.__init__ moveinsp = inspect.getfullargspec(Movemeter.__init__) moveargs = [] movedefaults = [] for i in range(1, len(moveinsp.args)): arg = moveinsp.args[i] default = moveinsp.defaults[i-1] if isinstance(default, bool) and arg not in ['multiprocess']: moveargs.append(arg) movedefaults.append(default) # GUI elements next # True/false - motion analysis options self.tickboxes = TickboxFrame(self, moveargs, defaults=movedefaults) self.tickboxes.grid(row=2, column=1, columnspan=2) # Preprocessing options tk.Label(self, text='Gaussian blur').grid(row=3, column=1) self.blur_slider = tk.Scale(self, from_=0, to=32, orient=tk.HORIZONTAL) self.blur_slider.set(0) self.blur_slider.grid(row=3, column=2, sticky='NSWE') # Numerical value - motion analysis options tk.Label(self, text='Maximum movement').grid(row=4, column=1) self.maxmovement_slider = tk.Scale(self, from_=1, to=100, orient=tk.HORIZONTAL) self.maxmovement_slider.set(10) self.maxmovement_slider.grid(row=4, column=2, sticky='NSWE') tk.Label(self, text='Upscale').grid(row=5, column=1) self.upscale_slider = tk.Scale(self, from_=0.1, to=10, orient=tk.HORIZONTAL, resolution=0.1) self.upscale_slider.set(5) self.upscale_slider.grid(row=5, column=2, sticky='NSWE') tk.Label(self, text='Parallel processes').grid(row=6, column=1) self.cores_slider = tk.Scale(self, from_=1, to=os.cpu_count(), orient=tk.HORIZONTAL) self.cores_slider.set(max(1, int(os.cpu_count()/2))) self.cores_slider.grid(row=6, column=2, sticky='NSWE') def get_current(self): ''' Returns a dictionary of the current settings that can be directly passed to the Movemeter.__init__ method. ''' settings = {'upscale': float(self.upscale_slider.get()), 'max_movement': int(self.maxmovement_slider.get()), 'multiprocess': int(self.cores_slider.get())} if settings['multiprocess'] == 1: settings['multiprocess'] = False return {**self.tickboxes.states, **settings}
Ancestors
- tkinter.Frame
- tkinter.Widget
- tkinter.BaseWidget
- tkinter.Misc
- tkinter.Pack
- tkinter.Place
- tkinter.Grid
Methods
def get_current(self)
-
Returns a dictionary of the current settings that can be directly passed to the Movemeter.init method.
class MovemeterTkGui (tk_parent)
-
Main widget for the Movemeter tkinter GUI.
Attributes
self.parent : object tkinter parent widget
folders
:list
- List of opened directories
folders_listbox
:object
- tk_steroids Listbox of opened directories
current_folder
:string
- The currently selected folder from self.folder
image_fns
:string
- List of image filenames in th current folder.
images
:list
ofNones
orlist
ofndarray
- Initially list of Nones, as long as many images there are. Incrimentally, becomes a list of images (numpy array).
exclude_images
:list
- List of image filenames or indices to exclude from the analysis.
fs
:int
orfloat
- Sampling rate of the images, in Hz (1/s). Global for all data.
filename_extensions
:tuple
ofstrings
- Accepted filename extensions for images (or videos).
self.N_frames : dict For video files of image stacks, contains the amount of frames per each file and the filenames are the keys.
selections
:list
Construct a frame widget with the parent MASTER.
Valid resource names: background, bd, bg, borderwidth, class, colormap, container, cursor, height, highlightbackground, highlightcolor, highlightthickness, relief, takefocus, visual, width.
Expand source code
class MovemeterTkGui(tk.Frame): ''' Main widget for the Movemeter tkinter GUI. ATTRIBUTES ----------- self.parent : object tkinter parent widget folders : list List of opened directories folders_listbox : object tk_steroids Listbox of opened directories current_folder : string The currently selected folder from self.folder image_fns : string List of image filenames in th current folder. images : list of Nones or list of ndarray Initially list of Nones, as long as many images there are. Incrimentally, becomes a list of images (numpy array). exclude_images : list List of image filenames or indices to exclude from the analysis. fs : int or float Sampling rate of the images, in Hz (1/s). Global for all data. filename_extensions : tuple of strings Accepted filename extensions for images (or videos). self.N_frames : dict For video files of image stacks, contains the amount of frames per each file and the filenames are the keys. selections : list ''' def __init__(self, tk_parent): tk.Frame.__init__(self, tk_parent) self.parent = tk_parent # Data and images self.folders = [] self.current_folder = None self.image_fns = [] self.images = None self.exclude_images = [] self.fs = 100 self.filename_extensions = ('.tiff', '.tif', '.mp4') self.N_frames = {} # Selections and ROIs self.selections = [] self.roi_groups = [] self.current_roi_group = 0 self.roi_patches = [] self.colors = matplotlib.cm.ScalarMappable(cmap=matplotlib.cm.tab10) self.colors.set_clim(0,10) # Motion analysis self.movemeter = Movemeter() self.results = [] self.heatmap_images = [] self.batch_name = 'batch_name' self.show_controls = False # Other self.use_mask_image = False self.mask_image = None # Top menu # -------------------------------- self.menu = tk.Menu(self) filemenu = tk.Menu(self) filemenu.add_command(label='Add directory...', command=self.open_directory) filemenu.add_separator() filemenu.add_command(label='Load ROIs', command=lambda: self.apply_movzip(rois=True)) filemenu.add_command(label='Save ROIs', command=lambda: self._save_movzip(only=['rois', 'selections'])) filemenu.add_separator() filemenu.add_command(label='Save ROI view', command=self.save_roiview) filemenu.add_command(label='Save ROIs only view', command=lambda: self.save_roiview(only_rois=True)) filemenu.add_separator() filemenu.add_command(label='Quit', command=self.parent.destroy) self.menu.add_cascade(label='File', menu=filemenu) editmenu = tk.Menu(self) editmenu.add_command(label='Undo (latest ROI)', command=self.undo) editmenu.add_separator() editmenu.add_command(label='Global settings', command=self.open_settings) self.menu.add_cascade(label='Edit', menu=editmenu) viewmenu = tk.Menu(self) viewmenu.add_command(label='Show image controls', command=self.toggle_controls) self.menu.add_cascade(label='View', menu=viewmenu) batchmenu = tk.Menu(self) batchmenu.add_command(label='Batch measure & save all', command=self.batch_process) batchmenu.add_separator() batchmenu.add_command(label='Reprocess rectangular selection (with current block settings)', command=self.recalculate_old) batchmenu.add_command(label='Replot heatmap', command=self.replot_heatmap) self.menu.add_cascade(label='Batch', menu=batchmenu) toolmenu = tk.Menu(self) toolmenu.add_command(label='Heatmap tool', command=lambda: open_httool(self)) self.menu.add_cascade(label='Tools', menu=toolmenu) self.parent.config(menu=self.menu) # Input folders self.folview = tk.LabelFrame(self, text='Input folders') self.folview.rowconfigure(2, weight=1) self.folview.columnconfigure(1, weight=1) self.folview.grid(row=0, column=1, sticky='NSWE') self.folders_listbox = Listbox(self.folview, ['No folders selected'], self.folder_selected) self.folders_listbox.listbox.config(height=10) self.folders_listbox.grid(row=2, column=1, columnspan=2, sticky='NSWE') self.imview_buttons = ButtonsFrame(self.folview, ['Add...', 'Remove', 'FS'], [self.open_directory, self.remove_directory, self.set_fs]) self.imview_buttons.grid(row=0, column=1) self.fs_button = self.imview_buttons.buttons[2] self.set_fs(fs=self.fs) # Operations view # ------------------------- self.opview = tk.LabelFrame(self, text='Command center') self.opview.grid(row=0, column=2, sticky='NSWE') self.tabs = Tabs(self.opview, ['Style', 'ROI creation', 'Motion analysis', 'Brightness analysis'], draw_frame = True) self.tabs.grid(row=0, column=1, columnspan=2, sticky='NSWE') self.tabs.set_page(1) self.styleview = self.tabs.tabs[0] self.styleview.columnconfigure(2, weight=1) self.colormap_label = tk.Label(self.styleview, text='Colormap') self.colormap_label.grid(row=1, column=1) self.colormap_selection = tk.Button(self.styleview, text=self.colors.get_cmap().name, command=self.open_colormap_selection) self.colormap_selection.grid(row=1, column=2) tk.Label(self.styleview, text='Line width').grid(row=2, column=1) self.patch_lw_slider = tk.Scale(self.styleview, from_=0, to_=10, orient=tk.HORIZONTAL) self.patch_lw_slider.set(1) self.patch_lw_slider.grid(row=2, column=2, sticky='NSWE') tk.Label(self.styleview, text='Fill strength').grid(row=3, column=1) self.patch_fill_slider = tk.Scale(self.styleview, from_=0, to=100, orient=tk.HORIZONTAL) self.patch_fill_slider.grid(row=3, column=2, sticky='NSWE') self.patch_fill_slider.set(40) self.roiview = self.tabs.tabs[1] self.roiview.columnconfigure(2, weight=1) self.roi_drawtypes = {'box': 'box', 'ellipse': 'ellipse', 'line': 'line', 'polygon': 'polygon', 'arc_from_points': 'polygon', 'concentric_arcs_from_points': 'polygon', 'radial_lines_from_points': 'polygon'} tk.Label(self.roiview, text='Selection mode').grid(row=1, column=1) self.selmode_frame = tk.Frame(self.roiview) self.selmode_frame.grid(row=1, column=2) self.roitype_selection = DropdownList(self.selmode_frame, ['box', 'ellipse', 'line', 'polygon', 'arc_from_points', 'concentric_arcs_from_points', 'radial_lines_from_points'], ['Box', 'Ellipse', 'Line', 'Polygon', 'Arc from points', 'Concentric Arcs (++RG)', 'Radial lines (++RG)'], single_select=True, callback=self.update_roitype_selection) self.roitype_selection.grid(row=1, column=2) self.drawmode_selection = TickboxFrame(self.selmode_frame, ['add', 'remove'], ['Add', 'Remove'], single_select=True ) self.drawmode_selection.grid(row=1, column=1) tk.Label(self.roiview, text='Block size').grid(row=3, column=1) self.blocksize_slider = tk.Scale(self.roiview, from_=16, to=128, orient=tk.HORIZONTAL) self.blocksize_slider.set(32) self.blocksize_slider.grid(row=3, column=2, sticky='NSWE') tk.Label(self.roiview, text='Block distance').grid(row=4, column=1) self.overlap_slider = tk.Scale(self.roiview, from_=1, to=128, orient=tk.HORIZONTAL, resolution=1) self.overlap_slider.set(32) self.overlap_slider.grid(row=4, column=2, sticky='NSWE') self.distance_label = tk.Label(self.roiview, text='Line-block distance') self.distance_label.grid(row=5, column=1) self.distance_slider = tk.Scale(self.roiview, from_=1, to=128, orient=tk.HORIZONTAL, resolution=1) self.distance_slider.set(32) self.distance_slider.grid(row=5, column=2, sticky='NSWE') self.nroi_label = tk.Label(self.roiview, text='Count') self.nroi_label.grid(row=6, column=1) self.nroi_label.grid_remove() self.nroi_slider = tk.Scale(self.roiview, from_=1, to=128, orient=tk.HORIZONTAL, resolution=1) self.nroi_slider.grid(row=6, column=2, sticky='NSWE') self.nroi_slider.grid_remove() self.radial_len_label = tk.Label(self.roiview, text='Radial line length') self.radial_len_label.grid(row=7, column=1) self.radial_len_label.grid_remove() self.radial_len_slider = tk.Scale(self.roiview, from_=1, to=1024, orient=tk.HORIZONTAL, resolution=1) self.radial_len_slider.grid(row=7, column=2, sticky='NSWE') self.radial_len_slider.grid_remove() self.roi_buttons = ButtonsFrame(self.roiview, ['Update', 'Max grid', 'Clear', 'Undo', 'New group'], [self.update_grid, self.fill_grid, self.clear_selections, self.undo, self.new_group]) self.roi_buttons.grid(row=8, column=1, columnspan=2) self.parview = self.tabs.tabs[2] self.parview.columnconfigure(1, weight=1) self.movemeter_settings = MovemeterSettings(self.parview) self.movemeter_settings.grid(column=1,sticky='NSWE') # Brightness self.brightness_view = self.tabs.tabs[3] self.brightness_view.columnconfigure(1, weight=1) self.brightness_tickboxes = {} for name, options in self.movemeter.measure_brightness_opt.items(): frame = TickboxFrame( self.brightness_view, options, single_select=True) frame.grid() self.brightness_tickboxes[name] = frame # ACTIONS FRAME self.actframe = tk.LabelFrame(self.opview, text='Measure') self.actframe.grid(row=1, column=1, columnspan=2) self.calculate_button = tk.Button(self.actframe, text='Movement', command=self.measure_movement) self.calculate_button.grid(row=1, column=1) self.brightness_do_button = tk.Button(self.actframe, text='Brightness', command=self.measure_brightness) self.brightness_do_button.grid(row=1, column=2) self.stop_button = tk.Button(self.actframe, text='Stop', command=self.stop) self.stop_button.grid(row=1, column=3) self.export_button = tk.Button(self.opview, text='Export results', command=self.export_results) self.export_button.grid(row=4, column=1) self.export_name = tk.Entry(self.opview, width=50) self.export_name.insert(0, "enter export name") self.export_name.grid(row=4, column=2) # Images view: Image looking and ROI selection # ------------------------------------------------- self.imview = tk.LabelFrame(self, text='Images and ROI') self.imview.grid(row=1, column=1, sticky='NSWE') self.imview.columnconfigure(1, weight=1) self.imview.rowconfigure(3, weight=1) self.imview_buttons = ButtonsFrame(self.imview, ['Exclude image', 'Exclude index'], [self.toggle_exclude, lambda: self.toggle_exclude(by_index=True)]) self.imview_buttons.grid(row=1, column=1) self.image_slider = tk.Scale(self.imview, from_=0, to=0, orient=tk.HORIZONTAL, command=self.change_image) self.image_slider.grid(row=2, column=1, sticky='NSWE') self.images_plotter = CanvasPlotter(self.imview) self.images_plotter.grid(row=3, column=1, sticky='NSWE') ax = self.images_plotter.ax self.excludetext = ax.text(0.5, 0.5, '', transform=ax.transAxes, fontsize=24, ha='center', va='center', color='red') # Results view: Analysed traces # ------------------------------------ self.tabs = Tabs(self, ['Displacement', 'Heatmap']) self.tabs.grid(row=1, column=2, sticky='NSWE') self.resview = self.tabs.pages[0] self.heatview = self.tabs.pages[1] self.resview.rowconfigure(2, weight=1) self.resview.columnconfigure(1, weight=1) self.heatview.columnconfigure(2, weight=1) self.heatview.rowconfigure(2, weight=1) self.results_plotter = CanvasPlotter(self.resview) self.results_plotter.grid(row=2, column=1, sticky='NSWE') # Results show options self.results_plotter_opts = TickboxFrame( self.resview, ['show_individual', 'show_mean', 'show_toolbar'], defaults=[True,True,False], callback=self.plot_results) self.results_plotter_opts.grid(row=1, column=1, sticky='NSWE') self.heatmap_plotter = CanvasPlotter(self.heatview) self.heatmap_plotter.grid(row=2, column=2, sticky='NSWE') self.heatmap_slider = tk.Scale(self.heatview, from_=0, to=0, orient=tk.HORIZONTAL, command=self.change_heatmap) self.heatmap_slider.grid(row=0, column=1, sticky='NSWE') self.heatmapcap_slider = tk.Scale(self.heatview, from_=0.1, to=100, orient=tk.HORIZONTAL, resolution=0.1, command=self.change_heatmap) self.heatmapcap_slider.set(20) self.heatmapcap_slider.grid(row=0, column=2, sticky='NSWE') self.heatmap_firstcap_slider = tk.Scale(self.heatview, from_=0.1, to=100, orient=tk.HORIZONTAL, resolution=0.1, command=self.change_heatmap) self.heatmap_firstcap_slider.set(20) self.heatmap_firstcap_slider.grid(row=1, column=2, sticky='NSWE') self.status = tk.Label(self, text='Nothing to do') self.status.grid(row=2, column=1, columnspan=2) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) self.rowconfigure(1, weight=1) def _imread(self, fn): ''' Use Movemeter to open image/video. ''' images = self.movemeter._imread(fn) return images def stop(): ''' Stop any ongoing motion analysis. ''' self.exit=True if self.movemeter: self.movemeter.stop() def set_fs(self, fs=None): ''' Opens a dialog to set the image sampling frequency (frame rate) so that time axises come correctly. ''' if fs is None: fs = simpledialog.askfloat('Imaging frequency (Hz)', 'How many images were taken per second') if fs: self.fs = fs self.fs_button.configure(text='fs = {} Hz'.format(self.fs)) def open_settings(self): ''' Placeholder for the settings dialog. ''' raise NotImplementedError def open_directory(self, directory=None): ''' Open a dialog to select a data directory and adds it to the list of open directories. ''' if directory is None: try: with open(os.path.join(MOVEDIR, 'last_directory.txt'), 'r') as fp: previous_directory = fp.read().rstrip('\n') except FileNotFoundError: previous_directory = os.getcwd() print(previous_directory) if os.path.exists(previous_directory): directory = filedialog.askdirectory(title='Select directory with the images', initialdir=previous_directory) else: directory = filedialog.askdirectory(title='Select directory with the images') if directory: if not os.path.isdir(MOVEDIR): os.makedirs(MOVEDIR) with open(os.path.join(MOVEDIR, 'last_directory.txt'), 'w') as fp: fp.write(directory) # Check if folder contains any images; If not and it contains folders, append # The folders in this folder contents = os.listdir(directory) noimages = [fn for fn in os.listdir(directory) if fn.endswith(self.filename_extensions)] == [] has_subfolders = any([os.path.isdir(os.path.join(directory, fn)) for fn in contents]) if noimages and has_subfolders: directories = [os.path.join(directory, fn) for fn in os.listdir(directory)] self.set_status('Added {} new directories'.format(len(directories))) else: directories = [directory] self.set_status('Added directory {}'.format(directory)) for directory in directories: self.folders.append(directory) self.folders_listbox.set_selections(self.folders) self.folder_selected(directory) def remove_directory(self): ''' Closes a directory from the list of open data directories. ''' self.folders.remove(self.current_folder) self.folders_listbox.set_selections(self.folders) self.set_status('Closed directory {}'.format(self.current_folder)) def folder_selected(self, folder): ''' When the user selects a folder from the list of open data directories (that is self.folders_listbox) ''' self.current_folder = folder print('Selected folder {}'.format(folder)) self.image_fns = [os.path.join(folder, fn) for fn in os.listdir(folder) if fn.endswith(self.filename_extensions)] self.image_fns.sort() self.N_frames = {} total_frames = 0 for fn in self.image_fns: N = len(self._imread(fn)) total_frames += N if N > 1: self.N_frames[fn] = N N_images = total_frames self.images = [None for i in range(N_images)] self.mask_image = None self.change_image(slider_value=1) self.image_slider.config(from_=1, to=N_images) self.export_name.delete(0, tk.END) self.export_name.insert(0, os.path.basename(folder.rstrip('/'))) def toggle_exclude(self, by_index=False): ''' Look at the currently shown image and toggle its excludance. Arguments --------- by_index : bool If true, toggle exclude for all images with this index. If false, exclude the filename only. ''' indx = int(self.image_slider.get()) - 1 if by_index: fn = indx else: fn = self.image_fns[indx] if fn not in self.exclude_images: self.exclude_images.append(fn) self.set_status('Removed image {} from the analysis'.format(fn)) else: self.exclude_images.remove(fn) self.set_status('Added image {} back to the analysis'.format(fn)) self.mask_image = None self.change_image(slider_value=self.image_slider.get()) def toggle_controls(self): ''' Show/hide image brightness/contrast controls. ''' self.show_controls = not(self.show_controls) self.change_image() def recalculate_old(self, directory=None): ''' Load old movzip, look the ROI extremes, and draw a new ROI but using the current block settings (block size and distance). Useful for testing how the results change when the selected area remains approximately the same but the block settings change. ''' if directory == None: directory = filedialog.askdirectory() if not directory: return None if not self._ask_batchname(): return None self.exit = False for root, dirs, fns in os.walk(directory): if self.exit: break movzip = [fn for fn in os.listdir(root) if fn.startswith('movemeter') and fn.endswith('.zip')] if movzip: settings, filenames, selections, rois, movements = self._load_movzip(os.path.join(root, movzip[0])) self.folder_selected(os.path.dirname(filenames[0])) x1, y1 = np.min(rois, axis=0)[0:2] x2, y2 = np.max(rois, axis=0)[0:2] + rois[0][3] self.set_roi(x1,y1,x2,y2) self.measure_movement() self.export_results(batch_name=self.batch_name) self.set_status('Results recalculated :)') def replot_heatmap(self, directory=None): ''' Like recalculate old, but relies in the old movement analysis results ''' if directory == None: directory = filedialog.askdirectory() if not directory: return None if not self._ask_batchname(): return None self.exit = False for root, dirs, fns in os.walk(directory): if self.exit: break movzip = [fn for fn in os.listdir(root) if fn.startswith('movemeter') and fn.endswith('.zip')] if movzip: settings, filenames, self.selections, self.roi_groups, self.results = self._load_movzip(os.path.join(root, movzip[0])) self.folder_selected(os.path.dirname(filenames[0])) self.set_settings(settings) self.plot_results() self.calculate_heatmap() self.change_heatmap(1) self.export_results(batch_name=self.batch_name) self.set_status('Heatmaps replotted :)') def _ask_batchname(self): name = simpledialog.askstring('Batch name', 'Name new folder') if name: self.batch_name = name return True else: return False def batch_process(self, fill_maxgrid=False): ''' fill_maxgrid : bool If True, ignore current ROIs and fill a full frame grid using the current slider options. ''' if not self._ask_batchname(): return None self.exit = False for folder in self.folders: if self.exit: break self.folder_selected(folder) if fill_maxgrid: self.fill_grid() self.measure_movement() self.export_results(batch_name=self.batch_name) def measure_movement(self, target=None): ''' Run motion analysis for the images in the currently selected directory, using the drawn ROIs. ''' if target is None: target = lambda: self.movemeter.measure_movement(0, optimized=True) if self.image_fns and self.roi_groups: print('Started roi measurements') self.results = [] self.movemeter = Movemeter(print_callback=self.set_status, **self.movemeter_settings.get_current()) for rois in self.roi_groups: # Set movemeted data images = [self._included_image_fns()] self.movemeter.set_data(images, [rois]) self.results.append( target() ) self.plot_results() self.calculate_heatmap() self.change_heatmap(1) else: self.set_status('No images or ROIs selected') def measure_brightness(self): kwargs = {} for name, frame in self.brightness_tickboxes.items(): kwargs[name] = frame.ticked[0] bmes = lambda: self.movemeter.measure_brightness(0, **kwargs) self.measure_movement(target=bmes) @property def image_shape(self): slider_value = int(self.image_slider.get()) image_i = int(slider_value) -1 if self.images[image_i] is None: self.images[image_i] = self._imread(self.image_fns[image_i])[0] return self.images[image_i].shape def open_colormap_selection(self): ''' Start ColormapSelector widget in a toplevel window. ''' top = tk.Toplevel(self) top.title('Select colormap') sel = ColormapSelector(top, callback=self.apply_colormap, startmap=self.colors.get_cmap().name) sel.grid(row=0, column=0, sticky='NSWE') top.rowconfigure(0, weight=1) top.columnconfigure(0, weight=1) top.mainloop() def apply_colormap(self, colormap): if hasattr(colormap, 'colors'): self.colors.set_clim(0, len(colormap.colors)) else: self.colors.set_clim(0, 10) self.colors.set_cmap(colormap) self.colormap_selection.config(text=colormap.name) self.update_grid() def undo(self): ''' Undo a ROI selection made by the user. ''' if len(self.selections) == 0: self.set_status('Nothing to undo') return None # Index of the roigroup to be undone i_roigroup = self.selections[-1][-1]['i_roigroup'] # Clear the previous selection data self.selections = self.selections[:-1] # Clear the corresponding ROI patches N_rois_remove = len(self.roi_patches[-1]) for patch in self.roi_patches[-1]: patch.remove() self.roi_patches = self.roi_patches[:-1] # Clear the actual ROIs self.roi_groups[i_roigroup] = self.roi_groups[i_roigroup][:-N_rois_remove] self.images_plotter.update() self.set_status('Undone windows {} in ROI group {}'.format(N_rois_remove, i_roigroup)) def update_roitype_selection(self): ''' When user selects a certain ROI type (box, circle, ...) to draw some of the sliders can be hidden. ''' selected = self.roitype_selection.ticked[0] if selected in ['concentric_arcs_from_points', 'radial_lines_from_points']: self.nroi_label.grid() self.nroi_slider.grid() if selected == 'radial_lines_from_points': self.radial_len_label.grid() self.radial_len_slider.grid() else: self.nroi_label.grid_remove() self.nroi_slider.grid_remove() self.radial_len_label.grid_remove() self.radial_len_slider.grid_remove() self.change_image() def clear_selections(self): ''' Clear current user selections and ROIs (fresh start) ''' self.selections = [] self.update_grid() self.roi_groups = [] self.current_roi_group = 0 def update_grid(self, *args): # Updating the image also needed now to update the selector # type drawn while selecting (box or line) self.change_image() # Clear any previous patches for group in self.roi_patches: for patch in group: patch.remove() self.roi_patches = [] self.roi_groups = [] if self.selections: for selection in self.selections: self.set_roi(*selection, user_made=False) else: self.images_plotter.update() def fill_grid(self): ''' Create a selection spanning the whole image and distribute cross-correlation windows everywhere. ''' self.set_roi(0,0,*reversed(self.image_shape)) def new_group(self): ''' Advance to the next ROI group. ''' self.current_roi_group += 1 def set_roi(self, x1=None,y1=None,x2=None,y2=None, params=None, user_made=True, recursion_data=None): ''' Add (or "remove") a ROI based on user selection. Arguments --------- x1, y1, x2, y2 : None or int params : none or dict user_made : bool Is this an user made selection. recursion_data : None or something Internal use. ''' if params is None: params = {} params['roitype'] = [s for s, b in self.roitype_selection.states.items() if b][0] params['blocksize'] = 2*[self.blocksize_slider.get()] params['distance'] = self.distance_slider.get() params['relstep'] = float(self.overlap_slider.get())/params['blocksize'][0] params['count'] = self.nroi_slider.get() params['rlen'] = self.radial_len_slider.get() params['i_roigroup'] = int(self.current_roi_group) params['mode'] = self.drawmode_selection.ticked[0] roitype, block_size, distance, rel_step, i_roigroup, count, mode, rlen = [ params[key] for key in ['roitype','blocksize','distance','relstep', 'i_roigroup', 'count', 'mode', 'rlen']] if user_made: self.selections.append( (x1, y1, x2, y2, params) ) if roitype in ['polygon', 'arc_from_points', 'concentric_arcs_from_points', 'radial_lines_from_points']: vertices = x1 if roitype == 'polygon': rois = [] for i_vertex in range(len(vertices)-1): pA, pB = vertices[i_vertex:i_vertex+2] rois.extend( grid_along_line(pA, pB, distance, block_size, step=rel_step) ) elif roitype == 'arc_from_points': rois = grid_arc_from_points((0,0,*reversed(self.image_shape)), block_size, step=rel_step, points=vertices) elif roitype in ['concentric_arcs_from_points', 'radial_lines_from_points']: if recursion_data is None: recursion_data = _workout_circle(vertices) if int(self.current_roi_group) < count-1: self.current_roi_group += 1 cp, R = recursion_data if roitype == 'concentric_arcs_from_points': new_recursion_data = (cp, R-distance) elif roitype == 'radial_lines_from_points': new_recursion_data = (cp, R) self.set_roi(x1=x1,y1=y1,x2=x2,y2=y2, params={**params, **{'i_roigroup': self.current_roi_group}}, user_made=False, recursion_data=new_recursion_data) self.current_roi_group -= 1 if roitype == 'concentric_arcs_from_points': rois = grid_arc_from_points((0,0,*reversed(self.image_shape)), block_size, step=rel_step, circle=recursion_data, lw=distance) elif roitype == 'radial_lines_from_points': rois = grid_radial_line_from_points((0,0,*reversed(self.image_shape)), block_size, step=rel_step, circle=recursion_data, line_len=rlen, i_segment=self.current_roi_group, n_segments=count) else: raise ValueError('unkown roitype {}'.format(roitype)) else: w = x2-x1 h = y2-y1 if roitype == 'line': rois = grid_along_line((x1, y1), (x2, y2), distance, block_size, step=rel_step) elif roitype == 'ellipse': rois = grid_along_ellipse((x1,y1,w,h), block_size, step=rel_step) else: rois = gen_grid((x1,y1,w,h), block_size, step=rel_step) while len(self.roi_groups) <= i_roigroup: self.roi_groups.append([]) if mode == 'add': self.roi_groups[i_roigroup].extend(rois) # Draw ROIs if len(rois) < 3000: self.set_status('Plotting all ROIs...') else: self.set_status('Too many ROIs, plotting only 3 000 first...') fig, ax = self.images_plotter.get_figax() color = self.colors.to_rgba(i_roigroup%self.colors.get_clim()[1]) patches = [] lw = self.patch_lw_slider.get() fill = self.patch_fill_slider.get()/100 fcolor = (color[0], color[1], color[2], color[3]*fill) for roi in rois[:3000]: patch = matplotlib.patches.Rectangle((float(roi[0]), float(roi[1])), float(roi[2]), float(roi[3]), fill=True, edgecolor=color, facecolor=fcolor, lw=lw) patches.append(patch) ax.add_patch(patch) self.roi_patches.append(patches) elif mode == 'remove': def _overlaps(a, b): return not (a[0]+a[2] < b[0] or b[0]+b[2] < a[0] or a[1]+a[3] < b[1] or b[1]+b[3] < a[1]) for i_rgroup in range(len(self.roi_groups)) : # Remove ROIs remove_indices = [] for i_old, old_roi in enumerate(self.roi_groups[i_rgroup]): for new_roi in rois: if _overlaps(old_roi, new_roi): remove_indices.append(i_old) break print('removing {} in rg {}'.format(remove_indices, i_rgroup)) for i_rm in remove_indices[::-1]: self.roi_groups[i_rgroup].pop() #self.roi_patches[i_rgroup].pop() # Remove patches separetly # Potential optimization if needed: Not sure if this is faster or # slower than the own _overlaps # Anyway quite risky if rois and patches become unsynced # (should be made in one-to-one correspondence) new_bboxes = [matplotlib.transforms.Bbox([[x, y],[x+w,y+h]]) for x,y,w,h in rois] for patches, selections in zip(self.roi_patches, self.selections): remove_indices = [] for i_patch, patch in enumerate(patches): if patch.get_bbox().count_overlaps(new_bboxes): patch.remove() remove_indices.append(i_patch) for i_rm in remove_indices[::-1]: patches.pop(i_rm) else: raise ValueError('unkown mode {}'.format(mode)) self.images_plotter.update() self.set_status('ROIs plotted :)') def _get_fn_and_frame(self, i_image): ''' Workaround needed for video/stack files, getting the correct filename and frame for the ith image. Arguments --------- i_image : int Index of the image. Returns ------- i_fn : int Index of the file name in self.image_fns i_frame : int Index of the frame in the video/stack file. ''' total_frames = 0 for i_fn, fn in enumerate(self.image_fns): frames = self.N_frames.get(fn, 1) total_frames += frames if total_frames >= i_image: return i_fn, frames - (total_frames - i_image) - 1 def change_image(self, slider_value=None): ''' Change the currently shown data image. ''' slider_value = int(self.image_slider.get()) image_i = int(slider_value) -1 if not 0 <= image_i < len(self.images): return None if self.use_mask_image: if self.mask_image is None: for i in range(len(self.images)): self.images[i] = self._imread(self.image_fns[i]) self.mask_image = np.inf * np.ones(self.image_shape) for image in self.images: self.mask_image = np.min([self.mask_image, image], axis=0) i_fn, i_frame = self._get_fn_and_frame(image_i) if self.images[image_i] is None: self.images[image_i] = self._imread(self.image_fns[i_fn])[i_frame] if image_i in self.exclude_images or self.image_fns[i_fn] in self.exclude_images: self.excludetext.set_text('EXCLUDED') else: self.excludetext.set_text('') if self.use_mask_image: showimage = self.images[image_i] - self.mask_image else: showimage = self.images[image_i] self.images_plotter.imshow(showimage, roi_callback=self.set_roi, cmap='gray', slider=self.show_controls, roi_drawtype=self.roi_drawtypes[self.roitype_selection.ticked[0]]) @staticmethod def get_displacements(results): ''' Returns the directionless mangitude of the motion (displacement). ''' return [np.sqrt(np.array(x)**2+np.array(y)**2) for x,y in results] @staticmethod def get_destructive_displacement_mean(results): ''' Takes first the mean of the x and y components separately, and then calculates the directionless magnitude (displacement). This way the "random walk" does not pollute the mean so much as when taking the mean of the directionless magnitudes. ''' x = [x for x,y in results] y = [y for x,y in results] return np.sqrt(np.mean(x, axis=0)**2 + np.mean(y, axis=0)**2) def plot_results(self): ''' Plots (time, displacement). ''' self.results_plotter.set_toolbar_visibility( 'show_toolbar' in self.results_plotter_opts.ticked) self.results_plotter.ax.clear() for i_roi_group, result in enumerate(self.results): color = self.colors.to_rgba(i_roi_group%self.colors.get_clim()[1]) displacements = [np.sqrt(np.array(x)**2+np.array(y)**2) for x,y in result] if 'show_individual' in self.results_plotter_opts.ticked: N_toplot = max( len(displacements), 50 ) for d in displacements[0:N_toplot]: self.results_plotter.plot(d, ax_clear=False, color=color, lw=0.5) if 'show_mean' in self.results_plotter_opts.ticked: self.results_plotter.plot(self.get_destructive_displacement_mean(result), ax_clear=False, color=color, lw=2) def _included_image_fns(self): return [fn for i_fn, fn in enumerate(self.image_fns) if fn not in self.exclude_images and i_fn not in self.exclude_images] def _len_included_frames(self): return sum([self.N_frames.get(fn, 1) for fn in self._included_image_fns()]) def calculate_heatmap(self): ''' Produce minimum size heatmap. ''' self.heatmap_images = [] # FIXME Heatmap for ROI groups not implemented properly # Currently just take the first nonempty ROI group i_roigroup = [i for i, rois in enumerate(self.roi_groups) if len(rois) != 0] if not i_roigroup: return None else: i_roigroup = i_roigroup[0] rois = self.roi_groups[i_roigroup] results = self.results[i_roigroup] roi_w, roi_h = rois[0][2:] roi_max_x = np.max([z[0] for z in rois]) roi_min_x = np.min([z[0] for z in rois]) roi_max_y = np.max([z[1] for z in rois]) roi_min_y = np.min([z[1] for z in rois]) step = int(self.overlap_slider.get()) max_movement = float(self.movemeter_settings.maxmovement_slider.get()) N = self._len_included_frames() for i_frame in range(N): image = np.zeros( (int((roi_max_y-roi_min_y)/step)+1, int((roi_max_x-roi_min_x)/step)+1) ) for ROI, (x,y) in zip(rois, results): values = (np.sqrt(np.array(x)**2+np.array(y)**2)) value = values[i_frame] cx = int((ROI[0]-roi_min_x)/step) cy = int((ROI[1]-roi_min_y)/step) try: image[cy, cx] = value except: print(image.shape) print('cx {} cy {}'.format(cx, cy)) raise ValueError if np.max(image) < 0.01: image[0,0] = 1 self.heatmap_images.append(image) self.heatmap_slider.config(from_=1, to=len(self.heatmap_images)) self.heatmap_slider.set(1) maxcapval = np.max(self.heatmap_images) self.heatmapcap_slider.config(from_=0, to=maxcapval) self.heatmapcap_slider.set(maxcapval) def change_heatmap(self, slider_value=None, only_return_image=False): ''' When moving the slider to select the heatmap frame to show. ''' #if slider_value == None: slider_value = int(self.heatmap_slider.get()) i_image = int(slider_value) - 1 image = np.copy(self.heatmap_images[i_image]) # Total max value cap allframemax = np.max(self.heatmap_images, axis=0) image[allframemax > float(self.heatmapcap_slider.get())] = 0 # First value max cap firstframemax = np.max(self.heatmap_images[0:3], axis=0) #image[firstframemax > float(self.heatmap_firstcap_slider.get())] = 0 #image = image / float(self.heatmapcap_slider.get()) #image[np.isnan(image)] = 0 image = image / np.max(image) if np.isnan(image).any(): image = np.ones(image.shape) image[0][0] = 0 if only_return_image: return image else: self.heatmap_plotter.imshow(image, normalize=False) def set_settings(self, settings): ''' Apply the given settings. Arguments ---------- settings : dict A dictionary of settings. ''' for key, value in settings.items(): if key == 'block_size': self.blocksize_slider.set(value) elif key == 'block_distance': self.overlap_slider.set(value) elif key == 'maximum_movement': self.movemeter_settings.maxmovement_slider.set(value) elif key == 'upscale': self.movemeter_settings.upscale_slider.set(value) elif key == 'cpu_cores': self.movemeter_settings.cores_slider.set(value) elif key == 'exclude_images': self.exclude_images = value elif key == 'measurement_parameters': self.movemeter_settings.tickboxes.states = value def set_status(self, text): ''' Shows info text at the window bottom. ''' self.status.config(text=text) self.status.update_idletasks() def apply_movzip(self, fn=None, rois=False): ''' Load parts of a movzip and apply settings from it to the current session. ''' if fn is None: fn = filedialog.askopenfilename(parent=self, title='Select a movzip', initialdir=MOVEDIR) settings, filenames, selections, roi_groups, movements = self._load_movzip(fn) if rois: self.selections = selections self.rois_groups = roi_groups self.update_grid() def _save_movzip(self, fn=None, only=None): ''' Saves a movzip containg data/settings about the ran motion analysis. Arguments --------- fn : string or None If None, ask the filename. only : bool, string or list of strings Select to save only certain parts. Possible values are 'metadata', 'image_filenames', 'selections', 'rois', 'movements' or any list combinations of these. ''' if isinstance(only, str): only = [only] if fn is None: if only: title = 'Save '+','.join(only) else: title = 'Save movzip' fn = filedialog.asksaveasfilename(parent=self, title=title, initialdir=MOVEDIR) if not fn.endswith('.zip'): fn = fn+'.zip' # Dump GUI settings settings = {} settings['block_size'] = self.blocksize_slider.get() settings['block_distance'] = self.overlap_slider.get() settings['movemeter_settings'] = self.movemeter_settings.get_current() settings['export_time'] = str(datetime.datetime.now()) settings['movemeter_version'] = __version__ settings['exclude_images'] = self.exclude_images if self.images: settings['images_shape'] = self.image_shape movzip = {'metadata': settings, 'image_filenames': self._included_image_fns(), 'selections': self.selections, 'rois': self.roi_groups, 'movements': self.results} self.set_status('Saving movzip...') with zipfile.ZipFile(fn, 'w') as savezip: for pfn, obj in movzip.items(): if only and pfn not in only: continue with savezip.open(pfn+'.json', 'w') as fp: fp.write(json.dumps(obj).encode('utf-8')) self.set_status('Mozip saved.') def _load_movzip(self, fn): ''' Load a movzip, returning its contents. Returns ------- settings, image_filenames, selections, rois, movements ''' movzip = [] with zipfile.ZipFile(fn, 'r') as loadzip: for pfn in ['metadata', 'image_filenames', 'selections', 'rois', 'movements']: try: with loadzip.open(pfn+'.json', 'r') as fp: movzip.append( json.loads(fp.read()) ) except KeyError: movzip.append(None) return (*movzip,) def save_roiview(self, only_rois=False): ''' Save the current image view with ROIs. Arguments --------- only_rois : bool If True, hide the image and show ROIs in the saved image. ''' savefn = filedialog.asksaveasfilename() if savefn: fig = self.images_plotter.figure if only_rois: self.images_plotter.imshow_obj.set_visible(False) fig.savefig(savefn, dpi=600, transparent=only_rois) if only_rois: self.images_plotter.imshow_obj.set_visible(True) def export_results(self, batch_name=None): ''' Creates a folder containing motion analysis results - movzip - csv files - images ''' savename = self.export_name.get() zipsavename = savename save_root = MOVEDIR if batch_name is not None: save_root = os.path.join(save_root, 'batch', batch_name) save_directory = os.path.join(save_root, savename) os.makedirs(save_directory, exist_ok=True) self._save_movzip(os.path.join(save_directory, 'movemeter_{}.zip'.format(zipsavename))) means = [] for i_roigroup, results in enumerate(self.results): fn = os.path.join(save_directory, 'movements_{}_rg{}.csv'.format(zipsavename, i_roigroup)) displacements = self.get_displacements(results) if not displacements: continue dm_displacement = self.get_destructive_displacement_mean(results) with open(fn, 'w') as fp: writer = csv.writer(fp, delimiter=',') writer.writerow(['time (s)', 'mean displacement (pixels)', 'destructive mean displacement (pixels)'] + ['ROI{} displacement (pixels)'.format(k) for k in range(len(displacements))]) for i in range(len(displacements[0])): row = [displacements[j][i] for j in range(len(displacements))] row.insert(0, dm_displacement[i]) row.insert(0, np.mean(row)) row.insert(0, i/self.fs) writer.writerow(row) if i_roigroup == 0: N = len(dm_displacement) means.append(np.linspace(0, (N-1)/self.fs, N)) means.append(dm_displacement) with open(os.path.join(save_directory, 'summary_desctructive_{}.csv'.format(zipsavename)), 'w') as fp: writer = csv.writer(fp, delimiter=',') writer.writerow(['time (s)'] +['roi group {} (pixels)'.format(i) for i in range(len(means)-1)]) for i in range(len(means[0])): row = [m[i] for m in means] writer.writerow(row) slider_i = int(self.image_slider.get()) self.image_slider.set(int(len(self._included_image_fns()))/2) # Image of the ROIs self.set_status('Saving the image view') fig, ax = self.images_plotter.get_figax() fig.savefig(os.path.join(save_directory, 'movemeter_imageview.jpg'), dpi=400, pil_kwargs={'optimize': True}) self.image_slider.set(slider_i) # Image of the result traces self.set_status('Saving the results view') fig, ax = self.results_plotter.get_figax() fig.savefig(os.path.join(save_directory, 'movemeter_resultsview.jpg'), dpi=400, pil_kwargs={'optimize': True}) def save_heatmaps(heatmaps, image_fns, savedir): for fn, image in zip(image_fns, heatmaps): tifffile.imsave(os.path.join(savedir, 'ht_{}'.format(os.path.basename(fn))), image.astype('float32')) # Save mean heatmap image with scale bar using matplotlib # FIXME Expose option for how many last images to save the mean for meanimage = np.mean(heatmaps[-min(5, len(heatmaps)):], axis=0) if False: # This was used to clip heatmap values # FIXME Expose option in the GUI if 'musca' in save_directory: meanimage = np.clip(meanimage, 0, 50) if np.max(meanimage) < 50: meanimage[0,0] = 50 else: meanimage = np.clip(meanimage, 0, 6) if np.max(meanimage) < 6: meanimage[0,0] = 6 fig, ax = plt.subplots() imshow = ax.imshow(meanimage) ax.set_axis_off() divider = make_axes_locatable(ax) cax = divider.append_axes('right', size='5%', pad=0.05) fig.colorbar(imshow, cax=cax) fig.savefig(os.path.join(savedir, 'ht_mean.png'), dpi=800) plt.show(block=False) plt.pause(0.01) plt.close(fig) self.set_status('Saving heatmaps') subsavedir = os.path.join(save_directory, 'heatmap_tif') os.makedirs(subsavedir, exist_ok=True) save_heatmaps(self.heatmap_images, self.image_fns, subsavedir) self.set_status('DONE Saving :)')
Ancestors
- tkinter.Frame
- tkinter.Widget
- tkinter.BaseWidget
- tkinter.Misc
- tkinter.Pack
- tkinter.Place
- tkinter.Grid
Static methods
def get_destructive_displacement_mean(results)
-
Takes first the mean of the x and y components separately, and then calculates the directionless magnitude (displacement).
This way the "random walk" does not pollute the mean so much as when taking the mean of the directionless magnitudes.
def get_displacements(results)
-
Returns the directionless mangitude of the motion (displacement).
Instance variables
prop image_shape
-
Expand source code
@property def image_shape(self): slider_value = int(self.image_slider.get()) image_i = int(slider_value) -1 if self.images[image_i] is None: self.images[image_i] = self._imread(self.image_fns[image_i])[0] return self.images[image_i].shape
Methods
def apply_colormap(self, colormap)
def apply_movzip(self, fn=None, rois=False)
-
Load parts of a movzip and apply settings from it to the current session.
def batch_process(self, fill_maxgrid=False)
-
fill_maxgrid : bool If True, ignore current ROIs and fill a full frame grid using the current slider options.
def calculate_heatmap(self)
-
Produce minimum size heatmap.
def change_heatmap(self, slider_value=None, only_return_image=False)
-
When moving the slider to select the heatmap frame to show.
def change_image(self, slider_value=None)
-
Change the currently shown data image.
def clear_selections(self)
-
Clear current user selections and ROIs (fresh start)
def export_results(self, batch_name=None)
-
Creates a folder containing motion analysis results - movzip - csv files - images
def fill_grid(self)
-
Create a selection spanning the whole image and distribute cross-correlation windows everywhere.
def folder_selected(self, folder)
-
When the user selects a folder from the list of open data directories (that is self.folders_listbox)
def measure_brightness(self)
def measure_movement(self, target=None)
-
Run motion analysis for the images in the currently selected directory, using the drawn ROIs.
def new_group(self)
-
Advance to the next ROI group.
def open_colormap_selection(self)
-
Start ColormapSelector widget in a toplevel window.
def open_directory(self, directory=None)
-
Open a dialog to select a data directory and adds it to the list of open directories.
def open_settings(self)
-
Placeholder for the settings dialog.
def plot_results(self)
-
Plots (time, displacement).
def recalculate_old(self, directory=None)
-
Load old movzip, look the ROI extremes, and draw a new ROI but using the current block settings (block size and distance).
Useful for testing how the results change when the selected area remains approximately the same but the block settings change.
def remove_directory(self)
-
Closes a directory from the list of open data directories.
def replot_heatmap(self, directory=None)
-
Like recalculate old, but relies in the old movement analysis results
def save_roiview(self, only_rois=False)
-
Save the current image view with ROIs.
Arguments
only_rois
:bool
- If True, hide the image and show ROIs in the saved image.
def set_fs(self, fs=None)
-
Opens a dialog to set the image sampling frequency (frame rate) so that time axises come correctly.
def set_roi(self, x1=None, y1=None, x2=None, y2=None, params=None, user_made=True, recursion_data=None)
-
Add (or "remove") a ROI based on user selection.
Arguments
x1
,y1
,x2
,y2
:None
orint
params
:none
ordict
user_made
:bool
- Is this an user made selection.
recursion_data
:None
orsomething
- Internal use.
def set_settings(self, settings)
-
Apply the given settings.
Arguments
settings
:dict
- A dictionary of settings.
def set_status(self, text)
-
Shows info text at the window bottom.
def stop()
-
Stop any ongoing motion analysis.
def toggle_controls(self)
-
Show/hide image brightness/contrast controls.
def toggle_exclude(self, by_index=False)
-
Look at the currently shown image and toggle its excludance.
Arguments
by_index
:bool
- If true, toggle exclude for all images with this index. If false, exclude the filename only.
def undo(self)
-
Undo a ROI selection made by the user.
def update_grid(self, *args)
def update_roitype_selection(self)
-
When user selects a certain ROI type (box, circle, …) to draw some of the sliders can be hidden.