Source code for imzml_writer.imzML_Scout

import pyimzml.ImzMLParser as imzmlp
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from RangeSlider.RangeSlider import RangeSliderV
import os
import sys
from matplotlib.figure import Figure 
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_tools import ToolBase, ToolToggleBase 
import warnings
import numpy as np
import pandas as pd
import json

from imzml_writer import scout_utils
from imzml_writer.analyte_list_cleanup import *
from imzml_writer import __version__



[docs] def main(tgt_file:str = "",initial_mz:float=104.1070): """Main control loop for imzML Scout GUI. Callable either with no arguments (find file via GUI) or by passing the file path to the target imzML. :param tgt_file: Path to imzML file for visualization.""" ##Colors and FONTS TEAL = "#2da7ad" BEIGE = "#dbc076" FONT = ("HELVETICA", 18, 'bold') def getionimage_wrapper(img_file:imzmlp.ImzMLParser,tgt_mz:float,window:float): image = imzmlp.getionimage(p=img_file, mz_value=tgt_mz, tol=window) if tgt_mz >= low_range and tgt_mz <= high_range: return image else: dims = image.shape image = np.zeros(shape=dims) return image def browse_for_file(): """Launch dialog box for user to find and select a target imzML file""" path_to_file = filedialog.askopenfilename(initialdir=os.getcwd(),filetypes=[("imzML Files","*.imzML")]) file_entry.delete(0,tk.END) file_entry.insert(0,path_to_file) def get_aspect_ratio(img_file): """Tries to extract aspect ratio information (relative pixel dimensions) from the imzML. Uses 1x1 pixels if it fails""" try: metadata = img_file.metadata.pretty() x_pix_size = metadata["scan_settings"]["scanSettings1"]["pixel size (x)"] y_pix_size = metadata["scan_settings"]["scanSettings1"]["pixel size y"] max_x_dimension = metadata["scan_settings"]["scanSettings1"]["max dimension x"] return y_pix_size/x_pix_size, x_pix_size, y_pix_size, max_x_dimension except: return 1, 1, 1, 1 def get_scan_range(img_file:imzmlp.ImzMLParser): global low_range, high_range low_range = np.inf high_range = np.inf * -1 for i in range(100): scan = img_file.getspectrum(i) if np.min(scan[0]) < low_range: low_range = np.min(scan[0]) if np.max(scan[0]) > high_range: high_range = np.max(scan[0]) def plot_ion_image(*_): """Deals with data update for plotting a new ion image, retrieving targets from the user input fields""" global raw_ion_image, aspect_ratio,x_pix,y_pix, imzML_object, max_x_dimension #Get the target data for the iamge target_mz = float(mz_entry.get()) tolerance = float(tolerance_entry.get()) filename = file_entry.get() mz_window=target_mz*tolerance/1e6 #Open the imzML datafile if needed with warnings.catch_warnings(action="ignore"): # coordinate_map = [] if "imzML_object" in globals(): if imzML_object.filename != filename: imzML_object = imzmlp.ImzMLParser(filename=filename,parse_lib='lxml') else: imzML_object = imzmlp.ImzMLParser(filename=filename,parse_lib='lxml') get_scan_range(imzML_object) #Check if TIC image was requested, view entire spectrum if so. Otherwise generate datagrid of requested m/z and tolerance combo if view_tic_option.get(): ion_image = getionimage_wrapper(imzML_object,tgt_mz=200,window=9999) else: ion_image = getionimage_wrapper(imzML_object,tgt_mz=target_mz,window=mz_window) #Get aspect ratio [aspect_ratio, x_pix, y_pix, max_x_dimension] = get_aspect_ratio(imzML_object) #Normalize ion image (or don't) as specified in GUI norm_method = normalization_method.get() if norm_method == "custom": norm_mz = float(normalize_custom_entry.get()) norm_window = norm_mz * tolerance / 1e6 norm_grid = getionimage_wrapper(imzML_object,tgt_mz=norm_mz,window=norm_window) ion_image = np.divide(ion_image,norm_grid,out=np.zeros_like(ion_image),where=norm_grid!=0) elif norm_method == "TIC": norm_grid = getionimage_wrapper(imzML_object,tgt_mz=200,window=9999) ion_image = np.divide(ion_image,norm_grid,out=np.zeros_like(ion_image),where=norm_grid!=0) #Initiate new raw data variable so we can freely manipulate the ion image as needed raw_ion_image = ion_image scout_utils.raw_ion_image = raw_ion_image fig = update_ion_image() return fig def update_ion_image(*_): """Updates the actual ion image in the GUI, with various checks for user input settings and handling to remove old images""" global raw_ion_image,aspect_ratio,x_pix,y_pix,canvas_ionimage,title_label,fig,ion_image, plot1, last_selected_patch, last_selected_pixel, red_highlight_patch, color_NL ##Retrieve contrast cutoffs for low-end and top-end low_thres = v_bottom.get() up_thres=v_top.get() target_mz = float(mz_entry.get()) tolerance = float(tolerance_entry.get()) ion_image = raw_ion_image if apply_ROI_mask.get(): ion_image = ion_image * scout_utils.roi_mask #Check is smoothing is to be applied, if so do it if smooth_state.get(): ion_image = scout_utils.smooth_image(ion_image, aspect_ratio) ##Apply the cutoffs, setting those below to 0 and those above to high_cutoff value low_cutoff = np.percentile(ion_image,low_thres*100) up_cutoff = np.percentile(ion_image,up_thres*100) ion_image = np.where(ion_image > up_cutoff,up_cutoff,ion_image) ion_image = np.where(ion_image < low_cutoff,0,ion_image) #If custom normalization set, apply it to the colormap if NL_state.get(): color_NL = norm_value.get() else: color_NL= up_cutoff ##Initiate and raw ion image fig = Figure(dpi=100,facecolor=TEAL,layout='tight') plot1 = fig.add_subplot() plot1.imshow(ion_image,aspect=aspect_ratio,interpolation="none",vmin=0,vmax=color_NL,cmap=cmap_selected.get()) plot1.axis('off') #Remove old image objects before inserting a new one if not first_img.get(): canvas_ionimage.get_tk_widget().destroy() title_label.destroy() else: last_selected_pixel = None last_selected_patch = None red_highlight_patch = None #Add the ion image to the tkinter window canvas_ionimage = FigureCanvasTkAgg(fig,master=window_scout) #Draw the selected patch if available on update of the ion image if last_selected_patch != None: draw_last_selected_patch() canvas_ionimage.draw() toolbar = NavigationToolbar2Tk(canvas_ionimage, pack_toolbar=False) toolbar.update() canvas_ionimage.get_tk_widget().grid(row=5,column=0,columnspan=3) ##Draw a label for the ion image with target m/z, tolerance, and pixel dimensions title_string=[] title_string = f"{int(round(x_pix,0))} µm x {int(round(y_pix,1))} µm pixels; m/z {target_mz} @ {tolerance} ppm" title_label = tk.Label(window_scout,text=title_string,bg=TEAL,font=FONT) title_label.grid(row=6,column=0,columnspan=4) #Initiate callbacks for when users mouse over the ion image for viewing/selecting highlighted pixel fig.canvas.mpl_connect("motion_notify_event",image_move) fig.canvas.callbacks.connect('button_press_event',report_coordinates) first_img.set(False) return fig def draw_last_selected_patch(): """Draws a red patch wherever the last selected pixel was""" global last_selected_patch, last_selected_pixel lx, ly = last_selected_pixel # Prepare the green overlay for the selected pixel selected_overlay = np.zeros((ion_image.shape[0], ion_image.shape[1], 4)) # New overlay num_lines, pixels_per_line = ion_image.shape selected_min_y = int(max(0, ly)) selected_max_y = int(min(num_lines, ly + 1)) selected_min_x = int(max(0, lx - pixels_per_line*0.008)) selected_max_x = int(min(pixels_per_line, lx + 1)) # Set the green color with some opacity for the selection overlay selected_overlay[selected_min_y:selected_max_y, selected_min_x:selected_max_x] = [1, 0, 0, 0.8] # Green with 80% opacity last_selected_patch = plot1.imshow(selected_overlay, aspect=aspect_ratio, interpolation="none") def image_move(event): """Handle mouse movement over the ion image and raws a green patch over the currently selectable region.""" global ion_image, plot1, last_selected_patch, red_highlight_patch,canvas_ionimage, color_NL # Track selected and red patches if event.xdata is not None and event.ydata is not None: # Get the pixel index x_index = int(event.xdata) y_index = int(event.ydata) # Create a copy of the original image to modify for hover highlight_image = np.zeros((ion_image.shape[0], ion_image.shape[1], 4)) # RGBA image for highlight # Define bounds for the highlight area pixels_per_line = ion_image.shape min_y = int(max(0, y_index)) max_y = int(min(ion_image.shape[0], y_index+1)) min_x = int(max(0, x_index - pixels_per_line[1]*0.008)) max_x = int(min(ion_image.shape[1], x_index + 1)) # Set the green color with varying opacity for the hover overlay highlight_image[min_y:max_y, min_x:max_x] = [0, 1, 0, 0.8] # Green with 80% opacity # Draw the ion image first plot1.imshow(ion_image, aspect=aspect_ratio, interpolation="none", vmin=np.min(ion_image), vmax=color_NL, cmap=cmap_selected.get()) # Overlay the red highlight if red_highlight_patch is not None: red_highlight_patch.remove() # Remove previous red highlight if it exists red_highlight_patch = plot1.imshow(highlight_image, aspect=aspect_ratio, interpolation="none") # Store new red highlight # If we have a last selected pixel, keep it drawn if last_selected_pixel: draw_last_selected_patch() # Refresh the canvas to show updates canvas_ionimage.draw() def export_csv(): """Exports the current image data as a csv file, prompts the user as to where they'd like to save it.""" global raw_ion_image dataframe = pd.DataFrame(raw_ion_image) path = os.path.dirname(file_entry.get()) file_name = filedialog.asksaveasfilename(initialdir=path,filetypes=[("CSV file",".csv")]) dataframe.to_csv(path_or_buf=file_name,header=False,index=False) def bulk_export_csv(): """Exports a batch of images as csv files, prompts the user for a spreadsheet of target m/z and names""" global raw_ion_image ##Prompt user for a spreadsheet of target m/z and names to give them target_list_file = filedialog.askopenfilename(initialdir=os.getcwd(),filetypes=[("Excel Spreadsheet",".xlsx"),("CSV File",".csv")]) target_list = pd.read_excel(target_list_file) target_list=cleanup_table(target_list,target_list_file) ##Generate ion image for each entry, write the raw data to a CSV file for iter,row in target_list.iterrows(): mz_entry.delete(0,tk.END) mz_entry.insert(0,row.values[1]) plot_ion_image() folder_name = os.path.join(os.path.dirname(file_entry.get()),"ion_images") img_name_base = f"{row.values[0]}-{str(row.values[1]).split(".")[0]}" if iter == 0: if os.path.exists(folder_name): messagebox.showwarning(title="Folder already exists!",message="You already have an ion image folder here, please rename, move, or delete it") break os.mkdir(folder_name) # file = filedialog.asksaveasfilename(initialdir=folder_name,filetypes=[("CSV", ".csv")],initialfile=img_name_base) used_extension="csv" # used_extension = file.split(".")[-1] # if file.endswith("csv") or file.endswith("CSV"): # file = file + ".csv" file = os.path.join(folder_name,f"{img_name_base}.{used_extension}") dataframe = pd.DataFrame(raw_ion_image) dataframe.to_csv(path_or_buf=file,header=False,index=False) ##Optionally, write the TIC image as well if include_TIC_var.get(): view_tic_check.invoke() file = os.path.join(folder_name,f"TIC_Image.{used_extension}") dataframe = pd.DataFrame(raw_ion_image) dataframe.to_csv(path_or_buf=file,header=False,index=False) def find_scan_idx(event): """Based on where the user clicks, find the corresponding scan index in the imzML file""" factor = 3 if smooth_state.get(): x_coord = int(event.xdata / factor) + 1 y_coord = int(event.ydata / factor) + 1 else: x_coord = int(event.xdata) + 1 y_coord = int(event.ydata) + 1 search_coords = (x_coord, y_coord, 1) if search_coords in imzML_object.coordinates: return imzML_object.coordinates.index(search_coords) else: return None def hide_patches(): global red_highlight_patch, last_selected_patch if red_highlight_patch != None: red_highlight_patch.set_visible(False) if last_selected_patch !=None: last_selected_patch.set_visible(False) def show_patches(): global red_highlight_patch, last_selected_patch if red_highlight_patch != None: red_highlight_patch.set_visible(True) if last_selected_patch !=None: last_selected_patch.set_visible(True) def export_image(fig): """Export the currently viewed image as an image file (tif, png, jpg), prompt the user for where to put it""" hide_patches() #Prompt the user for where to save it file = filedialog.asksaveasfilename(initialdir=os.getcwd(),filetypes=[("TIF", ".tif"),("PNG",".png"),("JPG", ".jpg")]) quality = qual_var.get() try: quality = float(quality) except: messagebox.showwarning(title="Invalid dpi setting...",message="dpi should be specified as a number - proceeding with default (dpi = 100)") quality = 100 if file: #Save the file file_format = file.split(".")[-1] if file_format not in ["tif", "jpg", "png","tiff"]: file_format = "tif" file = f"{file}.tif" fig.savefig(fname=file, transparent=True, dpi=quality, format=file_format, bbox_inches="tight", pad_inches=0) show_patches() def check_normalization(): """Handles contextual display on whether a custom normalization m/z is being applied""" if normalization_method.get() == "custom": ##If custom NL, add an entry for that m/z and listeners to update the ion image when it changes normalize_custom_entry.grid(row=2,column=6) normalize_custom_entry.bind("<Return>",plot_ion_image) normalize_custom_entry.bind("<FocusOut>",plot_ion_image) else: try: normalize_custom_entry.grid_remove() #Remove the box for space if the box is unchecked except: pass try: plot_ion_image() except: pass def report_coordinates(event): """Reports the current pixel x/y and updates the mass spectrum from the scan_idx""" global scan_idx, last_selected_pixel if event.xdata != None: scan_idx = find_scan_idx(event) if scan_idx != None: last_selected_pixel = (event.xdata, event.ydata) update_plot_for_selected_pixel() plot_mass_spectrum(scan_idx) def update_plot_for_selected_pixel(): """Update the plot with the new selected pixel and draw the previous selection in red.""" global ion_image, plot1, canvas_ionimage, last_selected_pixel, last_selected_patch # Draw the ion image plot1.imshow(ion_image, aspect=aspect_ratio, interpolation="none", vmin=np.min(ion_image), vmax=color_NL, cmap=cmap_selected.get()) # Remove old highlights if last_selected_patch is not None: last_selected_patch.remove() # Add red highlight, if a pixel is selected if last_selected_pixel: draw_last_selected_patch() # Refresh the canvas canvas_ionimage.draw() def plot_mass_spectrum(scan_idx): """Plot a mass spectrum and add listeners to watch for mouseovers/clicks to update the ion image to the nearest detectable m/z""" global imzML_object, mz, intensities, MS_vline, plot2, canvas_mass_spectrum ##Retrieve the data for the target spectrum [mz, intensities] = imzML_object.getspectrum(scan_idx) ##Plot the actual mass spectrum using vlines fig_spectrum = Figure(figsize=(4,4),dpi=100,facecolor=TEAL) plot2 = fig_spectrum.add_subplot() plot2.vlines(x=mz,ymin=0,ymax=intensities) plot2.set_ylim(0,plot2.get_ylim()[1]) if not first_MS.get(): #Set the x lims and y lims, as input by the user plot2.set_xlim(float(start_var.get()),float(end_var.get())) in_bounds = [idx for idx, value in enumerate(mz) if float(start_var.get()) < value < float(end_var.get())] new_ylim = np.max(intensities[in_bounds]) *1.3 plot2.set_ylim(0,new_ylim) plot2.set_xlabel("m/z") plot2.set_ylabel("Intensity") try: ##Remove old mass spectra so they aren't layered on top of each other canvas_mass_spectrum.destroy() except: pass ##Add mass spectrum to the GUI window canvas_mass_spectrum = FigureCanvasTkAgg(fig_spectrum,master=window_scout) canvas_mass_spectrum.get_tk_widget().grid(row=5,column=6,columnspan=4) if first_MS.get(): ##Handling for start/stop xlims entries, listeners to update when these are changed, etc. MS_vline = None start_mz = tk.Label(window_scout,text="Start m/z",bg=TEAL,font=FONT) end_mz = tk.Label(window_scout, text = "End m/z",bg=TEAL,font=FONT) start_mz.grid(row=6,column=6) end_mz.grid(row=6,column=8) start_var.set(f"{plot2.get_xlim()[0]:.1f}") start_mz_entry = tk.Entry(window_scout,textvariable=start_var,highlightbackground=TEAL,background=BEIGE,fg="black",justify='center',width=10) end_var.set(f"{plot2.get_xlim()[1]:.1f}") end_mz_entry = tk.Entry(window_scout,textvariable=end_var,highlightbackground=TEAL,background=BEIGE,fg='black',justify='center',width=10) start_mz_entry.grid(row=6,column=7) end_mz_entry.grid(row=6,column=9) start_mz_entry.bind("<Return>",lambda event:plot_mass_spectrum(scan_idx=scan_idx)) start_mz_entry.bind("<FocusOut>",lambda event:plot_mass_spectrum(scan_idx=scan_idx)) end_mz_entry.bind("<Return>",lambda event:plot_mass_spectrum(scan_idx=scan_idx)) end_mz_entry.bind("<FocusOut>",lambda event:plot_mass_spectrum(scan_idx=scan_idx)) ##Show previously selected m/z as needed current_mz = mz_entry.get() current_mz = float(current_mz) xlim = plot2.get_xlim() percentage = (current_mz - xlim[0]) / (xlim[1] - xlim[0]) if percentage > 0.75: ha = 'right' # align label to the right if near the end label_x_pos = current_mz - 0.5 # adjust label position to the left else: ha = 'left' # align label to the left otherwise label_x_pos = current_mz + 0.5 y_position = 4/5 * plot2.get_ylim()[1] plot2.axvline(x=current_mz,color='red',linestyle='--') plot2.text(label_x_pos, y_position, f"{current_mz:.4f}", color='red', fontsize=10, ha=ha, va='center') canvas_mass_spectrum.draw() ##Add listeners for mouseover and click on the mass spectrum fig_spectrum.canvas.mpl_connect("motion_notify_event",on_MS_move) fig_spectrum.canvas.callbacks.connect("button_press_event",change_target_mz) first_MS.set(False) def on_MS_move(event): """Listener for mouseover events on the mass spectrum, finds the nearest m/z when you do so and displays it on the window""" new_mz = event.xdata if new_mz is not None: new_target = find_target_mz(new_mz) global MS_vline, plot2, canvas_mass_spectrum, MS_label if MS_vline is not None: MS_vline.remove() if 'MS_label' in globals() and MS_label is not None: MS_label.remove() xlim = plot2.get_xlim() percentage = (new_target - xlim[0]) / (xlim[1] - xlim[0]) #percentage along x-axis y_position = 2 / 3 * plot2.get_ylim()[1] # Contextual align label left/right of the bar depending on how close to the end of viewing window we are if percentage > 0.75: ha = 'right' # align label to the right if near the end label_x_pos = new_target - 0.5 # adjust label position to the left else: ha = 'left' # align label to the left otherwise label_x_pos = new_target + 0.5 # adjust label position to the right MS_vline = plot2.axvline(x=new_target, color='black', linestyle='--') # Create the label with adjusted properties MS_label = plot2.text(label_x_pos, y_position, f"{new_target:.4f}", color='black', fontsize=10, ha=ha, va='center') canvas_mass_spectrum.draw() def find_target_mz(new_mz): """Finds the target m/z value based on surrounding, detectable m/z values.""" if new_mz is None: return None # Handle case for None input iter = 1 new_target = [] # Loop until a target value is found while len(new_target) == 0: low_pass = new_mz - (0.005 * iter) high_pass = new_mz + (0.005 * iter) iter += 1 # Find indices of values within the low and high pass range matches_idx = [idx for idx, value in enumerate(mz) if low_pass < value < high_pass] filt_mz = mz[matches_idx] filt_int = intensities[matches_idx] # Get index of the maximum intensity value if len(filt_int) > 0: max_idx = [idx for idx, val in enumerate(filt_int) if val == max(filt_int)] if len(max_idx) > 1: max_idx = max_idx[0] new_target = filt_mz[max_idx] return new_target.item() # Return the found target m/z value def change_target_mz(event): """Update ion image with the newly selected m/z""" global scan_idx new_mz = event.xdata if new_mz != None: new_target = find_target_mz(new_mz) mz_entry.delete(0,tk.END) mz_entry.insert(0,round(new_target,4)) plot_ion_image() plot_mass_spectrum(scan_idx) def bulk_export(): """Export a series of ion images with the currently selected view settings. Prompts the user for a spreadsheet with target m/z and labels.""" global fig #prompt the user for the target list target_list_file = filedialog.askopenfilename(initialdir=os.getcwd(),filetypes=[("Excel Spreadsheet",".xlsx"),("CSV File",".csv")]) target_list = pd.read_excel(target_list_file) #Contextual code to clean up table depending on whether headers were included, column order, etc. target_list=cleanup_table(target_list,target_list_file) quality = qual_var.get() try: quality = float(quality) except: messagebox.showwarning(title="Invalid dpi setting...",message="dpi should be specified as a number - proceeding with default (dpi = 100)") quality = 100 ##Iterate through each target m/z, drawing and then writing the ion image for iter,row in target_list.iterrows(): mz_entry.delete(0,tk.END) mz_entry.insert(0,row.values[1]) plot_ion_image() hide_patches() folder_name = os.path.join(os.path.dirname(file_entry.get()),"ion_images") img_name_base = f"{row.values[0]}-{str(row.values[1]).split(".")[0]}" if iter == 0: if os.path.exists(folder_name): messagebox.showwarning(title="Folder already exists!",message="You already have an ion image folder here, please rename, move, or delete it") break os.mkdir(folder_name) #Prompt user for file extension to use file = filedialog.asksaveasfilename(initialdir=folder_name,filetypes=[("TIF", ".tif"),("PNG",".png"),("JPG", ".jpg")],initialfile=img_name_base) used_extension = file.split(".")[-1] if used_extension not in ["tiff, tif, png, jpg"]: used_extension='tif' file = f"{file}.tif" else: file = os.path.join(folder_name,f"{img_name_base}.{used_extension}") fig.savefig(fname=file, transparent=True, dpi=quality, format=used_extension, bbox_inches="tight", pad_inches=0) show_patches() ##optionally, export a TIC image if selected if include_TIC_var.get(): view_tic_check.invoke() file = os.path.join(folder_name,f"TIC_Image.{used_extension}") fig.savefig(fname=file, transparent=True, dpi=quality, format=used_extension, bbox_inches="tight", pad_inches=0) def view_tic(): if view_tic_option.get(): draw_tic_image() else: plot_ion_image() def draw_tic_image(): plot_ion_image() def custom_NL(): """Applies custom normalization limit as specified by the user""" ##0 = no custom, 1 = custom NL global NL_entry, norm_value, raw_ion_image custom_NL_desired = NL_state.get() if not custom_NL_desired: try: NL_entry.destroy() #remove entries for custom normalization limit when unchecked update_NL_button.destroy() except: pass update_ion_image() elif custom_NL_desired: ##Add the entries when checked norm_value = tk.StringVar(window_scout) if norm_value.get()=="": norm_value.set(np.percentile(raw_ion_image,v_top.get()*100)) NL_entry = tk.Entry(window_scout,textvariable=norm_value,highlightbackground=TEAL,bg=TEAL,background=BEIGE,fg="black",justify='center') NL_entry.grid(row=4,column=1) update_NL_button = tk.Button(window_scout,text="Get this NL",bg=TEAL,highlightbackground=TEAL,command=update_NL) update_NL_button.grid(row=4,column=2) NL_entry.bind("<Return>",update_ion_image) NL_entry.bind("<FocusOut>",update_ion_image) def update_NL(): norm_value.set(np.percentile(raw_ion_image,v_top.get()*100)) update_ion_image() def more_cmaps(): def add_a_map(): try: new_map = all_options.selection_get() except: new_map = None if new_map != None and not new_map in colormap_options: colormap_options.append(new_map) selected_options.insert(tk.END,new_map) def remove_a_map(): try: remove_map = selected_options.selection_get() except: remove_map = None if remove_map != None: for idx, map in enumerate(colormap_options): if remove_map == map: del_idx = idx colormap_options.pop(del_idx) selected_options.delete(del_idx) def save_exit(): mod_path = os.path.dirname(os.path.abspath(__file__)) config_file = os.path.join(mod_path,"cmap_targets.json") json_dump = {"options":colormap_options} with open(config_file,'w') as file: json.dump(json_dump,file,indent=4) cmap_selected.set(colormap_options[0]) cmap_selector['menu'].delete(0,'end') for map in colormap_options: cmap_selector['menu'].add_command(label=map,command=tk._setit(cmap_selected,map)) cmap_window.destroy() cmap_window = tk.Tk() cmap_window.title("Colormap Options...") cmap_window.config(padx=5,pady=5,bg=TEAL) #Listbox for all cmap options avail_colmaps = tk.Label(cmap_window,text="Available Colormaps",bg=TEAL,font=FONT) all_options = tk.Listbox(cmap_window,bg=BEIGE,fg="black",height=10,highlightcolor=TEAL,width=35,justify='center') avail_colmaps.grid(row=0,column=1) all_options.grid(row=1,column=1,rowspan=4) cmaps = plt.colormaps() for idx, map in enumerate(cmaps): all_options.insert(idx,map) #Listbox for cmap options to choose from selected_colmaps = tk.Label(cmap_window,text="Selected Colormaps",bg=TEAL,font=FONT) selected_options = tk.Listbox(cmap_window,bg=BEIGE,fg="black",height=10,highlightcolor=TEAL,width=35,justify='center') selected_colmaps.grid(row=0,column=3) selected_options.grid(row=1,column=3,rowspan=4) mod_path = os.path.dirname(os.path.abspath(__file__)) config_file = os.path.join(mod_path,"cmap_targets.json") with open(config_file,'r') as file: cmap_options = json.load(file) colormap_options = cmap_options["options"] for idx, map in enumerate(colormap_options): selected_options.insert(idx,map) ##Add button add_button=tk.Button(cmap_window,text=">>>",bg=TEAL,highlightbackground=TEAL, command=add_a_map) add_button.grid(row=2,column=2) ##Remove button remove_button=tk.Button(cmap_window,text="<<<",bg=TEAL,highlightbackground=TEAL, command=remove_a_map) remove_button.grid(row=3,column=2) ##Save button save_button=tk.Button(cmap_window,text="Save & Exit",bg=TEAL,highlightbackground=TEAL, command=save_exit) save_button.grid(row=5,column=2) ##Build the GUI window window_scout = tk.Tk() window_scout.title(f"imzML Scout v{__version__}") window_scout.config(padx=5,pady=5,bg=TEAL) style = ttk.Style() style.theme_use('clam') ##Target image: file_var = tk.StringVar(window_scout) file_var.set(tgt_file) file_button=tk.Button(window_scout,text="Browse for file",bg=TEAL,highlightbackground=TEAL, command=browse_for_file) file_entry = tk.Entry(window_scout,textvariable=file_var,highlightbackground=TEAL,background=BEIGE,fg="black",justify='center') file_var.trace_add('write',plot_ion_image) file_button.grid(row=1,column=0) file_entry.grid(row=1,column=1) ##mz entry mz_var = tk.StringVar(window_scout) mz_var.set(initial_mz) mz_label=tk.Label(window_scout,text="Target m/z:",bg=TEAL,font=FONT) mz_entry = tk.Entry(window_scout,textvariable=mz_var,highlightbackground=TEAL,background=BEIGE,fg="black",justify='center') mz_entry.bind("<Return>",plot_ion_image) mz_entry.bind("<FocusOut>",plot_ion_image) mz_label.grid(row=2,column=0) mz_entry.grid(row=2,column=1) ##Tolerance entry tol_var = tk.StringVar(window_scout) tol_var.set("10") tolerance_label=tk.Label(window_scout,text="Tolerance (ppm):",bg=TEAL,font=FONT) tolerance_entry = tk.Entry(window_scout,textvariable=tol_var,highlightbackground=TEAL,background=BEIGE,fg="black",justify='center') #tol_var.trace_add('write',plot_ion_image) tolerance_entry.bind("<Return>",plot_ion_image) tolerance_entry.bind("<FocusOut>",plot_ion_image) tolerance_label.grid(row=3,column=0) tolerance_entry.grid(row=3,column=1) ##Normalization buttons normalize_custom_entry=tk.Entry(window_scout,highlightbackground=TEAL,background=BEIGE,fg="black",justify='center') normalization_method = tk.StringVar(window_scout) no_normalization = tk.Radiobutton(window_scout,bg=TEAL,command=check_normalization,fg="white",selectcolor=TEAL,text="No Normalization",variable=normalization_method,value="none") no_normalization.grid(row=1,column=4) no_normalization.select() custom_method = tk.Radiobutton(window_scout,bg=TEAL,command=check_normalization,fg="white",selectcolor=TEAL,text="Custom Normalize",variable=normalization_method,value="custom") custom_method.grid(row=2,column=4) tic_method = tk.Radiobutton(window_scout,bg=TEAL,command=check_normalization,fg="white",selectcolor=TEAL,text="TIC Normalize",variable=normalization_method,value="TIC") tic_method.grid(row=3,column=4) #Smooth button smooth_state = tk.BooleanVar(window_scout) smooth_button = tk.Checkbutton(window_scout,text="Smooth Image?", bg=TEAL, font=FONT, variable=smooth_state, command=plot_ion_image) smooth_button.grid(row=4, column=4) ##Slider v_top = tk.DoubleVar(window_scout,value=0.96) v_bottom = tk.DoubleVar(window_scout,value=0.05) v_slider = RangeSliderV(window_scout,[v_bottom,v_top],padY=12,bgColor=TEAL,valueSide="RIGHT",font_color='#ffffff',font_family="Helvetica",line_s_color=BEIGE,digit_precision='.2f',step_size=0.01) v_slider.grid(row=4,column=4,rowspan=4) v_top.trace_add('write',update_ion_image) v_bottom.trace_add('write',update_ion_image) ##Custom normalization limit NL_state = tk.BooleanVar(window_scout) NL_checkbox = tk.Checkbutton(window_scout,text="Custom normalization limit?",bg=TEAL,font=FONT,variable=NL_state,command=custom_NL) NL_checkbox.grid(row=4,column=0) ##Colormap set try: mod_path = os.path.dirname(os.path.abspath(__file__)) config_file = os.path.join(mod_path,"cmap_targets.json") with open(config_file,'r') as file: cmap_options = json.load(file) colormap_options = cmap_options["options"] except: mod_path = os.path.dirname(os.path.abspath(__file__)) config_file = os.path.join(mod_path,"cmap_targets.json") json_dump = {'options':["viridis","plasma","cividis","hot","jet"]} with open(config_file,'w') as file: json.dump(json_dump,file,indent=4) mod_path = os.path.dirname(os.path.abspath(__file__)) config_file = os.path.join(mod_path,"cmap_targets.json") with open(config_file,'r') as file: cmap_options = json.load(file) colormap_options = cmap_options["options"] cmap_selected = tk.StringVar(window_scout) cmap_selected.set(colormap_options[0]) cmap_selector = tk.OptionMenu(window_scout,cmap_selected,*colormap_options) cmap_selector.grid(row=1,column=2) cmap_selected.trace_add('write',update_ion_image) ##cmap options cmap_options_button = tk.Button(window_scout,text="More cmaps...",bg=TEAL,font=TEAL,highlightbackground=TEAL,command=more_cmaps) cmap_options_button.grid(row=2,column=2) ##View TIC image view_tic_option = tk.BooleanVar(window_scout) view_tic_check = tk.Checkbutton(window_scout,text="View TIC?",bg=TEAL,font=FONT,var=view_tic_option,command=view_tic) view_tic_check.grid(row=3,column=2) ##Export buttons export_button=tk.Button(window_scout,text="Export Image",bg=TEAL,highlightbackground=TEAL,command=lambda:export_image(fig)) export_button.grid(row=7,column=2) b_export = tk.Button(window_scout,text="Bulk Export",bg=TEAL,highlightbackground=TEAL,command=bulk_export) b_export.grid(row=7,column=0) csv_export = tk.Button(window_scout,text="csv Export",bg=TEAL,highlightbackground=TEAL,command=export_csv) csv_export.grid(row=8,column=2) b_csv_export = tk.Button(window_scout,text="Bulk csv export",bg=TEAL,highlightbackground=TEAL,command=bulk_export_csv) b_csv_export.grid(row=8,column=0) include_TIC_var = tk.BooleanVar(window_scout) include_tic = tk.Checkbutton(window_scout,text="Include TIC?",bg=TEAL,font=FONT,var=include_TIC_var) include_tic.grid(row=9,column=0) masked_imzml_button = tk.Button(window_scout,text="Write masked imzML", bg=TEAL, highlightbackground=TEAL,command=lambda:scout_utils.write_masked_imzml_handler(file_var.get())) masked_imzml_button.grid(row=9,column=3) ##Export quality quality_label = tk.Label(window_scout,text="Export quality (dpi):",bg=TEAL,font=FONT) qual_var = tk.StringVar(window_scout) qual_var.set("100") quality_entry = tk.Entry(window_scout,textvariable=qual_var,highlightbackground=TEAL,background=BEIGE,fg="black",justify='center') quality_label.grid(row=7, column=1) quality_entry.grid(row=8,column=1) #ROI Select load_ROI = tk.Button(window_scout, text="load ROI mask", bg=TEAL, highlightbackground=TEAL, command=scout_utils.load_ROI) load_ROI.grid(row=7,column=3) save_ROI = tk.Button(window_scout, text="save ROI mask", bg=TEAL, highlightbackground=TEAL, command=scout_utils.save_ROI) save_ROI.grid(row=8,column=3) ROI_select = tk.Button(window_scout, text="ROI Select", bg=TEAL, highlightbackground=TEAL, command=lambda:scout_utils.ROI_select(ROI_select,ion_image,aspect_ratio,color_NL)) ROI_select.grid(row=7, column = 4) apply_ROI_mask = tk.BooleanVar(window_scout) apply_ROI = tk.Checkbutton(window_scout, text="Apply ROI mask?", bg=TEAL, font=FONT, var=apply_ROI_mask, command=update_ion_image) apply_ROI.grid(row=8, column=4) start_var = tk.StringVar(window_scout) start_var.set(None) end_var = tk.StringVar(window_scout) end_var.set(None) first_img = tk.BooleanVar(window_scout) first_img.set(True) first_MS = tk.BooleanVar(window_scout) first_MS.set(True) on_startup = True if on_startup: if tgt_file != "": plot_ion_image() on_startup=False window_scout.mainloop()
if __name__ == "__main__": try: main(sys.argv[1]) except: main()