From f17e4e3630b8596a29c67a60fd9442bdf687732c Mon Sep 17 00:00:00 2001 From: "markus.kuehbach" <markus.kuehbach@hu-berlin.de> Date: Mon, 17 Apr 2023 17:14:50 +0200 Subject: [PATCH 1/4] Updated apmtools container with APTyzer, paraprobe-toolbox v0.4, and new tool APAV --- docker/apmtools/APT_analyzer.ipynb | 1214 ---------------------------- docker/apmtools/Cheatsheet.ipynb | 332 ++++++-- docker/apmtools/Dockerfile | 214 ++--- docker/apmtools/FAIRmatNewLogo.png | Bin 0 -> 24588 bytes docker/apmtools/FAIRmat_S.png | Bin 6890 -> 0 bytes docker/apmtools/NOMADOasisLogo.png | Bin 0 -> 12991 bytes tools.json | 8 +- 7 files changed, 351 insertions(+), 1417 deletions(-) delete mode 100644 docker/apmtools/APT_analyzer.ipynb create mode 100644 docker/apmtools/FAIRmatNewLogo.png delete mode 100644 docker/apmtools/FAIRmat_S.png create mode 100644 docker/apmtools/NOMADOasisLogo.png diff --git a/docker/apmtools/APT_analyzer.ipynb b/docker/apmtools/APT_analyzer.ipynb deleted file mode 100644 index a329a8d..0000000 --- a/docker/apmtools/APT_analyzer.ipynb +++ /dev/null @@ -1,1214 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c15820a3", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "%reset -f\n", - "from tkinter import *\n", - "from tkinter.ttk import Combobox\n", - "from tkinter import messagebox\n", - "from tkinter import filedialog\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib.figure import Figure\n", - "from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)\n", - "import numpy as np\n", - "import apt_importers as apt\n", - "import math\n", - "from matplotlib import gridspec\n", - "import re\n", - "from tkinter.filedialog import asksaveasfile\n", - "import tkinter.scrolledtext as tkst\n", - "import webbrowser\n", - "from mpl_toolkits.mplot3d import proj3d\n", - "import pandas as pd\n", - "import h5py\n", - "\n", - "def data_for_cylinder_along_z(center_x,center_y,center_z,radius,height_z):\n", - " z = np.linspace(center_z-height_z/2, center_z+height_z/2, 50)\n", - " theta = np.linspace(0, 2*np.pi, 50)\n", - " theta_grid, z_grid=np.meshgrid(theta, z)\n", - " x_grid = radius*np.cos(theta_grid) + center_x\n", - " y_grid = radius*np.sin(theta_grid) + center_y\n", - " return x_grid,y_grid,z_grid\n", - "\n", - "def gerade(k,x,d):\n", - " return k*x+d\n", - "\n", - "class APT_analyzer(): # define class object \n", - " def __init__(self,tk): # initialize when starting tkinter\n", - " self.plot_exist=0\n", - " self.conc_check=0\n", - " self.excess_check=0\n", - " self.invert=0\n", - " self.data_color=['blue','red','green','yellow','purple','black','white','grey','brown'] # colors of cylinder to be seletected\n", - " self.color_cp=['blue','red','green','yellow','purple','black','white','grey','brown']\n", - " self.frame=Frame(tk) # setting up frame\n", - " self.frame.pack(expand=True,fill=BOTH) # placing frame\n", - " self.canvas_all=Canvas(self.frame,width=1400,height=800,scrollregion=(0,0,1550,850)) #setting up canvas onto frame\n", - " ################# scroll bars vertical and horizontal ################\n", - " self.hbar=Scrollbar(self.frame,orient=HORIZONTAL) # setting up horizontal scrollbar\n", - " self.hbar.pack(side=TOP,fill=X) # placing horizontal scrollbar on the top side\n", - " self.hbar.config(command=self.canvas_all.xview) # configurating horizontal scrollbar\n", - " self.vbar=Scrollbar(self.frame,orient=VERTICAL) # setting up vertical scrollbar\n", - " self.vbar.pack(side=LEFT,fill=Y) # placing vertical scrollbar on the left side\n", - " self.vbar.config(command=self.canvas_all.yview) # configurating vertical scrollbar\n", - " self.canvas_all.config(yscrollcommand=self.vbar.set,xscrollcommand=self.hbar.set) #configurating canvas\n", - " \n", - "\n", - " self.b_page1_loading = Button(self.canvas_all, text=\"1. Load\", command=self.page_1,font=('helvetica',12,'bold')) # button for tip calculation\n", - " self.canvas_all.create_window(75,20,window=self.b_page1_loading)\n", - " \n", - " self.b_page2_visualize = Button(self.canvas_all, text=\"2. Visualize\", command=self.page_2,font=('helvetica',12,'bold')) # button for tip calculation\n", - " self.canvas_all.create_window(180,20,window=self.b_page2_visualize)\n", - " \n", - " self.b_page3_cylinder = Button(self.canvas_all, text=\"3. Calculate\", command=self.page_3,font=('helvetica',12,'bold')) # button for tip calculation\n", - " self.canvas_all.create_window(300,20,window=self.b_page3_cylinder)\n", - "\n", - " ############## initiate values #######################################\n", - " #### page 1 #### \n", - " self.succ=0\n", - " self.utext0=Label(self.canvas_all,text='Welcome to the APT-analyzer!') # title of segment\n", - " self.utext1=Label(self.canvas_all,text='This open source tool enables you to look at and analyze APT tips ') # title of segment\n", - " self.utext2=Label(self.canvas_all,text='All you need is a .pos (or .epos) file and a .rrng file') # title of segment\n", - " self.utext3=Label(self.canvas_all,text='Select the files you want to look at below and press \"Load files\"') # title of segment\n", - " self.utext4=Label(self.canvas_all,text='------------- Initiate files --------------') # title of segment\n", - " self.b_pos_file = Button(self.canvas_all,bg='green',fg='white', text=\"Select .pos /.epos file\", command=self.search_pos) # button for pos file initiation\n", - " self.b_rrng_file = Button(self.canvas_all,bg='green',fg='white', text=\"Select .rrng file\", command=self.search_rrng) # button for rrng file initiation\n", - " self.b_calc_tip = Button(self.canvas_all, text=\"Load files\", command=self.calculate_tip,bg='blue',fg='white',font=('helvetica',14,'bold')) # button for tip calculation\n", - " self.dtext0=Label(self.canvas_all,text='The loading of the files may take some time.') # title of segment\n", - " self.dtext1=Label(self.canvas_all,text='When \"Sucessfully loaded files!\" appears underneath the Load button') # title of segment\n", - " self.dtext2=Label(self.canvas_all,text='then everthing is working as intendet') # title of segment\n", - " self.dtext3=Label(self.canvas_all,text='and a preview image of the tip should appear on the right') # title of segment \n", - " self.dtext4=Label(self.canvas_all,text='You may now go to section 2.Visualize (top middle)') # title of segment\n", - " self.dtext5=Label(self.canvas_all,text='where you have more options for the visalization') # title of segment\n", - " self.dtext6=Label(self.canvas_all,text='as well as the ability to set control points and print them') # title of segment\n", - " self.dtext7=Label(self.canvas_all,text='After that you can go to section 3.Calculate (top right)') # title of segment\n", - " self.dtext8=Label(self.canvas_all,text='To calculate the concentration profile and excess profile') # title of segment\n", - " self.dtext9=Label(self.canvas_all,text='of a cylinder area which you can select within the tip') # title of segment\n", - " self.label_help=Label(self.canvas_all,text='if you need help press this button:') # title of segment\n", - " self.b_help = Button(self.canvas_all, text=\"help\",bg='orange',fg='black',command=self.help_web,font=('helvetica',14,'bold')) # button for tip calculation\n", - " \n", - " #### page 2 ####\n", - " self.cb_atom = Combobox(tk, values='') \n", - "\n", - " self.diss=0\n", - " self.diss2=0\n", - " self.slider_image_size_cv=DoubleVar()\n", - " self.slider_image_size = Scale(self.canvas_all,from_=1,to=5,orient='horizontal',variable=self.slider_image_size_cv,showvalue=0)\n", - " self.label_plot_tip=Label(self.canvas_all,text='------------- plot options ---------------') # title of segment\n", - " self.label_image_size=Label(self.canvas_all,text='Select image size') # label for resolution\n", - " self.label_slider_res=Label(self.canvas_all,text='small')\n", - " self.label_slider_res2=Label(self.canvas_all,text='large') \n", - " self.slider_image_size.set(3)\n", - " self.b_plot1 = Button(self.canvas_all, text=\"plot tip\", command=self.plot_tip,bg='blue',fg='white',font=('helvetica',14,'bold')) # button for plotting the tip\n", - " self.var_axis=IntVar()\n", - " self.check_axis = Checkbutton(self.canvas_all,text=\"Hide Axes\", onvalue=1, offvalue=0,variable=self.var_axis,font=('helvetica', 10,'bold'))\n", - " self.label_resolution=Label(self.canvas_all,text='Select number of atoms') # label for resolution\n", - " self.label_select_atom=Label(self.canvas_all,text='Select atom type')\n", - " self.slider_res_cv=DoubleVar()\n", - " self.label_slider_res3=Label(self.canvas_all,text='small')\n", - " self.label_slider_res4=Label(self.canvas_all,text='large')\n", - " self.slider_res = Scale(self.canvas_all,from_=1,to=5,orient='horizontal',variable=self.slider_res_cv,showvalue=0)\n", - " self.slider_res.set(1)\n", - " self.label_select_atom_size=Label(self.canvas_all,text='Select atom size')\n", - " self.slider_size_cv=DoubleVar()\n", - " self.slider_size = Scale(self.canvas_all,from_=1,to=200,orient='horizontal',variable=self.slider_size_cv,showvalue=0)\n", - " self.label_slider_res5=Label(self.canvas_all,text='small')\n", - " self.label_slider_res6=Label(self.canvas_all,text='large') \n", - " self.slider_size.set(50)\n", - " self.i_plot=IntVar()\n", - " self.cb_inverse=Checkbutton(self.canvas_all, text='Flip Tip', onvalue=1, offvalue=0, variable=self.i_plot, font=('helvetica', 10,'bold')) \n", - " self.control=IntVar()\n", - " self.label_control=Checkbutton(self.canvas_all, text='create control points by clicking on screen', onvalue=1, offvalue=0, variable=self.control, font=('helvetica', 10,'bold')) \n", - " self.text_box = Text(self.canvas_all,height=10, width=35)\n", - " self.b_control_print = Button(self.canvas_all, text=\"print\",bg='green', command=self.save,fg='white',font=('helvetica',12,'bold')) # button for pos file initiation\n", - " self.b_control_clear = Button(self.canvas_all, text=\"clear\",bg='red', command=self.clear_text,fg='white',font=('helvetica',12,'bold')) # button for pos file initiation\n", - " self.var_points=IntVar()\n", - " self.check_points = Checkbutton(self.canvas_all,text=\"plot with points\", onvalue=1, offvalue=0,variable=self.var_points,font=('helvetica', 10,'bold'))\n", - " self.b_adjust_cly = Button(self.canvas_all, text=\"adjust cylinder according to points\",bg='yellow',fg='black', command=self.adjust,font=('helvetica',10,'bold')) # button for pos file initiation \n", - " self.cb_color_cp = Combobox(tk, values=self.color_cp, width=10) \n", - " self.cb_color_cp.current(0)\n", - " self.var_plane=IntVar()\n", - " self.label_color_cp=Label(self.canvas_all,text='color of control points') \n", - " self.check_plane = Checkbutton(self.canvas_all,text=\"show fit plane and normal vector\", onvalue=1, offvalue=0,variable=self.var_plane,font=('helvetica', 10,'bold'))\n", - " \n", - " #### page 3 ####\n", - " self.var_cyl=IntVar()\n", - " self.label_conc=Label(self.canvas_all,text='------------ concentration calculation option ------------')#.place(x=20,y=400) # title of segment\n", - " self.var_cyl=IntVar()\n", - " self.check_cylinder = Checkbutton(self.canvas_all,text=\"show cylinder for concentration calculation\", onvalue=1, offvalue=0,variable=self.var_cyl,font=('helvetica', 10,'bold'))#.place(x=20,y=220)\n", - " \n", - " self.b_calc_conc = Button(self.canvas_all, text=\"plot concentration\", command=self.calc_con,bg='blue',fg='white',font=('helvetica',12,'bold')) # button for calculating the concentration of cylinder\n", - " self.b_calc_excess = Button(self.canvas_all, text=\"plot excess\", command=self.calc_excess,bg='blue',fg='white',font=('helvetica',12,'bold')) # button for calculating the concentration of cylinder\n", - " self.b_plot_cyl = Button(self.canvas_all, text=\"plot cylinder\", command=self.calc_zoom,bg='blue',fg='white',font=('helvetica',13,'bold')) # button for calculating the concentration of cylinder\n", - " self.b_print_ex = Button(self.canvas_all, text=\"print excess\", command=self.save_excess,bg='green',fg='white',font=('helvetica',13,'bold')) # button for calculating the concentration of cylinder\n", - " \n", - " self.label_cly_pos=Label(self.canvas_all,text='cylinder center position')\n", - " self.label_cly_x=Label(self.canvas_all,text='x')\n", - " self.cly_x=StringVar(value='0')\n", - " self.entry_cly_x=Entry(self.canvas_all,textvariable=self.cly_x,width=10) \n", - " self.label_cly_y=Label(self.canvas_all,text='y') \n", - " self.cly_y=StringVar(value='0')\n", - " self.entry_cly_y=Entry(self.canvas_all,textvariable=self.cly_y,width=10) \n", - " self.label_cly_z=Label(self.canvas_all,text='z')\n", - " self.cly_z=StringVar(value='0')\n", - " self.entry_cly_z=Entry(self.canvas_all,textvariable=self.cly_z,width=10)\n", - " self.label_cly_radius=Label(self.canvas_all,text='radius of cylinder') \n", - " self.radius=StringVar(value='10')\n", - " self.entry_cly_radius=Entry(self.canvas_all,textvariable=self.radius) \n", - " self.label_cly_height=Label(self.canvas_all,text='height of cylinder') \n", - " self.height=StringVar(value='30')\n", - " self.entry_cly_height=Entry(self.canvas_all,textvariable=self.height) \n", - " self.label_cly_beta=Label(self.canvas_all,text='tilt along x-axis')\n", - " self.beta=StringVar(value='0')\n", - " self.entry_cly_beta=Entry(self.canvas_all,textvariable=self.beta)\n", - " self.label_cly_alpha=Label(self.canvas_all,text='tilt along y-axis')\n", - " self.alpha=StringVar(value='0')\n", - " self.entry_cly_alpha=Entry(self.canvas_all,textvariable=self.alpha)\n", - " self.label_color_cyl=Label(self.canvas_all,text='color of the cylinder') # label for cylinder color\n", - " self.cb_color = Combobox(self.canvas_all, values=self.data_color)\n", - " self.cb_color.current(0) \n", - " self.label_inter=Label(self.canvas_all,text='Select interval') # label for resolution\n", - " self.slider_inter_cv=DoubleVar()\n", - " self.label_slider_inter=Label(self.canvas_all,text='small large') # title of segment\n", - " self.slider_inter = Scale(self.canvas_all,from_=1,to=20,orient='horizontal',variable=self.slider_inter_cv,showvalue=0)#Combobox(self.canvas_all, values=self.data_res) # combobox for resolution\n", - " self.slider_inter.set(15)\n", - " self.b_plot2 = Button(self.canvas_all, text=\"plot tip\", command=self.plot_tip,bg='blue',fg='white',font=('helvetica',13,'bold')) # button for plotting the tip\n", - " self.b_print_conc = Button(self.canvas_all, text=\"print concentration\", command=self.save_conc,bg='green',fg='white',font=('helvetica',13,'bold')) # button for plotting the tip\n", - " self.var_norm=IntVar()\n", - " self.check_norm = Checkbutton(self.canvas_all,text=\"normalize Excess\", onvalue=1, offvalue=0,variable=self.var_norm)\n", - " self.var_switch=IntVar()\n", - " self.check_switch = Checkbutton(self.canvas_all,text=\"set cylinders as bulk*\", onvalue=1, offvalue=0,variable=self.var_switch)\n", - " self.label_astrix=Label(self.canvas_all,text='* the conc. of each segment is calculated in relation to the ends') \n", - " self.data_bulk=['start','end']\n", - " self.cb_bulk = Combobox(tk, values=self.data_bulk, width=4) \n", - " self.cb_bulk.current(0)\n", - " self.text_box_ex = Text(self.canvas_all,height=6, width=25)\n", - " #self.cb_exp=Checkbutton(self.canvas_all, text='create points for excess calculation', onvalue=1, offvalue=0, variable=self.control, font=('helvetica', 10,'bold')) \n", - " self.label_exp=Label(self.canvas_all,text='For calcuating the excess select 4 points') \n", - " \n", - " \n", - " ############## setting up page 1 ##################\n", - " self.a1=self.canvas_all.create_window(200,50,window=self.utext0) \n", - " self.a2=self.canvas_all.create_window(200,70,window=self.utext1) \n", - " self.a3=self.canvas_all.create_window(200,90,window=self.utext2) \n", - " self.a4=self.canvas_all.create_window(200,110,window=self.utext3) \n", - " self.a5=self.canvas_all.create_window(200,140,window=self.utext4) \n", - " self.a6=self.canvas_all.create_window(120,165,window=self.b_pos_file) \n", - " self.a7=self.canvas_all.create_window(280,165,window=self.b_rrng_file) \n", - " self.a8=self.canvas_all.create_window(200,230,window=self.b_calc_tip)\n", - " self.a9=self.canvas_all.create_window(200,300,window=self.dtext0) \n", - " self.a10=self.canvas_all.create_window(200,320,window=self.dtext1) \n", - " self.a11=self.canvas_all.create_window(200,340,window=self.dtext2) \n", - " self.a12=self.canvas_all.create_window(200,360,window=self.dtext3) \n", - " self.a13=self.canvas_all.create_window(200,400,window=self.dtext4) \n", - " self.a14=self.canvas_all.create_window(200,420,window=self.dtext5) \n", - " self.a15=self.canvas_all.create_window(200,440,window=self.dtext6)\n", - " self.a16=self.canvas_all.create_window(200,460,window=self.dtext7)\n", - " self.a17=self.canvas_all.create_window(200,480,window=self.dtext8)\n", - " self.a18=self.canvas_all.create_window(200,500,window=self.dtext9)\n", - " self.a19=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a20=self.canvas_all.create_window(200,560,window=self.b_help)\n", - " self.a21=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a22=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a23=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a24=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a25=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a26=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a27=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a28=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a29=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a30=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a31=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a32=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a33=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a34=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " \n", - " ###################### setup figures ################################# \n", - " self.fig=Figure(figsize=(17,13)) # defining figure size\n", - " self.canvas=FigureCanvasTkAgg(self.fig,master=self.canvas_all) # setting up canvas for figure\n", - " self.toolbar=NavigationToolbar2Tk(self.canvas, self.canvas_all) # setting up toolbar for figure\n", - " self.canvas_all.create_window(400,10,window=self.canvas.get_tk_widget(),anchor=N+W,tags='canvas') #placing figure canvas on general canvas\n", - " self.canvas_all.pack(expand=True,fill=BOTH) # placing general canvas at the end\n", - " self.E=2 # setting up variable to check if .pos/.epos file is selected\n", - " self.R=2 # setting up variable to check if .rrng file is selected \n", - " \n", - " def page_1(self):\n", - " self.canvas_all.delete(self.a1,self.a2,self.a3,self.a4,self.a5,self.a6,self.a7,self.a8,self.a9,self.a10,self.a11,self.a12,self.a13,self.a14,self.a15,self.a16,self.a17,self.a18,self.a19,self.a20,self.a21,self.a22,self.a23,self.a24,self.a25,self.a26,self.a27,self.a28,self.a29,self.a30,self.a31,self.a32,self.a33,self.a34) \n", - " if self.succ==1:self.a_succ=self.canvas_all.create_window(200,260,window=self.label_succ) #placing label on canvas\n", - " if self.R==1:self.aRS=self.canvas_all.create_window(280,190,window=self.label_rrng_selected)\n", - " if self.R==0:self.aR=self.canvas_all.create_window(280,190,window=self.label_rrng_selected)\n", - " if self.E==0:self.aE=self.canvas_all.create_window(120,190,window=self.label_pos_selected)\n", - " if self.E==1:self.aES=self.canvas_all.create_window(120,190,window=self.label_pos_selected)\n", - " \n", - " self.a1=self.canvas_all.create_window(200,50,window=self.utext0) \n", - " self.a2=self.canvas_all.create_window(200,70,window=self.utext1) \n", - " self.a3=self.canvas_all.create_window(200,90,window=self.utext2) \n", - " self.a4=self.canvas_all.create_window(200,110,window=self.utext3) \n", - " self.a5=self.canvas_all.create_window(200,140,window=self.utext4) \n", - " self.a6=self.canvas_all.create_window(120,165,window=self.b_pos_file) \n", - " self.a7=self.canvas_all.create_window(280,165,window=self.b_rrng_file) \n", - " self.a8=self.canvas_all.create_window(200,230,window=self.b_calc_tip)\n", - " self.a9=self.canvas_all.create_window(200,300,window=self.dtext0) \n", - " self.a10=self.canvas_all.create_window(200,320,window=self.dtext1) \n", - " self.a11=self.canvas_all.create_window(200,340,window=self.dtext2) \n", - " self.a12=self.canvas_all.create_window(200,360,window=self.dtext3) \n", - " self.a13=self.canvas_all.create_window(200,400,window=self.dtext4) \n", - " self.a14=self.canvas_all.create_window(200,420,window=self.dtext5) \n", - " self.a15=self.canvas_all.create_window(200,440,window=self.dtext6)\n", - " self.a16=self.canvas_all.create_window(200,460,window=self.dtext7)\n", - " self.a17=self.canvas_all.create_window(200,480,window=self.dtext8)\n", - " self.a18=self.canvas_all.create_window(200,500,window=self.dtext9)\n", - " self.a19=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a20=self.canvas_all.create_window(200,560,window=self.b_help)\n", - " self.a21=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a22=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a23=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a24=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a25=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a26=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a27=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a28=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a29=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a30=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a31=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a32=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a33=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " self.a34=self.canvas_all.create_window(200,520,window=self.label_help)\n", - " \n", - " def page_2(self):\n", - " self.canvas_all.delete(self.a1,self.a2,self.a3,self.a4,self.a5,self.a6,self.a7,self.a8,self.a9,self.a10,self.a11,self.a12,self.a13,self.a14,self.a15,self.a16,self.a17,self.a18,self.a19,self.a20,self.a21,self.a22,self.a23,self.a24,self.a25,self.a26,self.a27,self.a28,self.a29,self.a30,self.a31,self.a32,self.a33,self.a34) \n", - " if self.R==1:self.canvas_all.delete(self.aRS)\n", - " if self.R==0:self.canvas_all.delete(self.aR)\n", - " if self.E==0:self.canvas_all.delete(self.aE)\n", - " if self.E==1:self.canvas_all.delete(self.aES)\n", - " if self.succ==1: self.canvas_all.delete(self.a_succ)\n", - " ############## setting up buttons, Scales, comboboxes, etc. for plot options ################### \n", - " self.a1=self.canvas_all.create_window(200,50,window=self.label_plot_tip) \n", - " self.a2=self.canvas_all.create_window(100,80,window=self.label_image_size) \n", - " self.a3=self.canvas_all.create_window(30,105,window=self.label_slider_res) \n", - " self.a4=self.canvas_all.create_window(170,105,window=self.label_slider_res2) \n", - " self.a5=self.canvas_all.create_window(100,105,window=self.slider_image_size) \n", - " self.a6=self.canvas_all.create_window(250,300,window=self.b_plot1) \n", - " self.a7=self.canvas_all.create_window(280,170,window=self.check_axis) \n", - " self.a8=self.canvas_all.create_window(100,145,window=self.label_resolution) \n", - " self.a9=self.canvas_all.create_window(280,80,window=self.label_select_atom) \n", - " self.a10=self.canvas_all.create_window(30,170,window=self.label_slider_res3) \n", - " self.a11=self.canvas_all.create_window(170,170,window=self.label_slider_res4) \n", - " self.a12=self.canvas_all.create_window(100,170,window=self.slider_res) \n", - " self.a13=self.canvas_all.create_window(100,210,window=self.label_select_atom_size) \n", - " self.a14=self.canvas_all.create_window(30,230,window=self.label_slider_res5) \n", - " self.a15=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a16=self.canvas_all.create_window(100,230,window=self.slider_size) \n", - " self.a17=self.canvas_all.create_window(280,230,window=self.cb_inverse) \n", - " self.a18=self.canvas_all.create_window(280,110,window=self.cb_atom) # putting combobox with element selection on canvas\n", - " self.a19=self.canvas_all.create_window(200,350,window=self.label_control) \n", - " self.a20=self.canvas_all.create_window(200,450,window=self.text_box) \n", - " self.a21=self.canvas_all.create_window(100,560,window=self.b_control_print) \n", - " self.a22=self.canvas_all.create_window(300,560,window=self.b_control_clear) \n", - " self.a23=self.canvas_all.create_window(200,560,window=self.check_points) \n", - " self.a24=self.canvas_all.create_window(200,600,window=self.b_adjust_cly)\n", - " self.a25=self.canvas_all.create_window(100,310,window=self.cb_color_cp)\n", - " self.a26=self.canvas_all.create_window(100,290,window=self.label_color_cp)\n", - " self.a27=self.canvas_all.create_window(200,630,window=self.check_plane) \n", - " self.a28=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a29=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a30=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a31=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a32=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a33=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " self.a34=self.canvas_all.create_window(170,230,window=self.label_slider_res6)\n", - " def page_3(self):\n", - " self.canvas_all.delete(self.a1,self.a2,self.a3,self.a4,self.a5,self.a6,self.a7,self.a8,self.a9,self.a10,self.a11,self.a12,self.a13,self.a14,self.a15,self.a16,self.a17,self.a18,self.a19,self.a20,self.a21,self.a22,self.a23,self.a24,self.a25,self.a26,self.a27,self.a28,self.a29,self.a30,self.a31,self.a32,self.a33,self.a34) \n", - " if self.R==1:self.canvas_all.delete(self.aRS)\n", - " if self.R==0:self.canvas_all.delete(self.aR)\n", - " if self.E==0:self.canvas_all.delete(self.aE)\n", - " if self.E==1:self.canvas_all.delete(self.aES)\n", - " if self.succ==1: self.canvas_all.delete(self.a_succ)\n", - " ########### setting up buttons, Scales, comboboxes etc. for cylinder selection and concentration calculations ###################\n", - " self.label_exp=Label(self.canvas_all,text='For calcuating the excess select 4 points') \n", - " self.entry_cly_x=Entry(self.canvas_all,textvariable=self.cly_x,width=10) \n", - " self.entry_cly_y=Entry(self.canvas_all,textvariable=self.cly_y,width=10)\n", - " self.entry_cly_z=Entry(self.canvas_all,textvariable=self.cly_z,width=10)\n", - " self.entry_cly_alpha=Entry(self.canvas_all,textvariable=self.alpha)\n", - " self.entry_cly_beta=Entry(self.canvas_all,textvariable=self.beta)\n", - " \n", - " self.a1=self.canvas_all.create_window(200,50,window=self.label_conc) \n", - " self.a2=self.canvas_all.create_window(200,90,window=self.check_cylinder) \n", - " self.a3=self.canvas_all.create_window(100,450,window=self.b_calc_conc) \n", - " self.a4=self.canvas_all.create_window(100,500,window=self.b_calc_excess) \n", - " self.a5=self.canvas_all.create_window(300,400,window=self.b_plot_cyl) \n", - " self.a6=self.canvas_all.create_window(300,500,window=self.b_print_ex) \n", - " self.a7=self.canvas_all.create_window(200,120,window=self.label_cly_pos) \n", - " self.a8=self.canvas_all.create_window(100,140,window=self.label_cly_x) \n", - " self.a9=self.canvas_all.create_window(100,170,window=self.entry_cly_x) \n", - " self.a10=self.canvas_all.create_window(200,140,window=self.label_cly_y) \n", - " self.a11=self.canvas_all.create_window(200,170,window=self.entry_cly_y) \n", - " self.a12=self.canvas_all.create_window(300,140,window=self.label_cly_z) \n", - " self.a13=self.canvas_all.create_window(300,170,window=self.entry_cly_z)\n", - " self.a14=self.canvas_all.create_window(100,200,window=self.label_cly_radius)\n", - " self.a15=self.canvas_all.create_window(100,225,window=self.entry_cly_radius) \n", - " self.a16=self.canvas_all.create_window(300,200,window=self.label_cly_height) \n", - " self.a17=self.canvas_all.create_window(300,225,window=self.entry_cly_height) \n", - " self.a18=self.canvas_all.create_window(100,255,window=self.label_cly_beta) \n", - " self.a19=self.canvas_all.create_window(100,280,window=self.entry_cly_beta) \n", - " self.a20=self.canvas_all.create_window(300,255,window=self.label_cly_alpha) \n", - " self.a21=self.canvas_all.create_window(300,280,window=self.entry_cly_alpha) \n", - " self.a22=self.canvas_all.create_window(100,330,window=self.label_color_cyl) \n", - " self.a23=self.canvas_all.create_window(100,355,window=self.cb_color) \n", - " self.a24=self.canvas_all.create_window(300,310,window=self.label_inter) \n", - " self.a25=self.canvas_all.create_window(300,330,window=self.label_slider_inter) \n", - " self.a26=self.canvas_all.create_window(300,355,window=self.slider_inter) \n", - " self.a27=self.canvas_all.create_window(100,400,window=self.b_plot2) \n", - " self.a28=self.canvas_all.create_window(300,450,window=self.b_print_conc)\n", - " self.a29=self.canvas_all.create_window(100,550,window=self.check_norm)\n", - " self.a30=self.canvas_all.create_window(260,550,window=self.check_switch)\n", - " self.a31=self.canvas_all.create_window(220,580,window=self.label_astrix)\n", - " self.a32=self.canvas_all.create_window(280,550,window=self.cb_bulk)\n", - " self.a33=self.canvas_all.create_window(200,610,window=self.label_exp)\n", - " self.a34=self.canvas_all.create_window(200,675,window=self.text_box_ex)\n", - " \n", - " def help_web(self):\n", - " url='https://github.com/areichm/APT_analyzer'\n", - " new=1\n", - " webbrowser.open(url,new=new)\n", - " \n", - " def clear_text(self): \n", - " self.text_box.delete('1.0', END)\n", - " \n", - " def save(self):\n", - " points=self.text_box.get(1.0,END)\n", - " points=points.split('\\n')\n", - " points=[x for x in points if x!='']\n", - " xyz=[]\n", - " for i in range(0,len(points)):\n", - " points_inner=points[i].split(',')\n", - " for j in points_inner:\n", - " m=j.split('=')\n", - " xyz.append(m[1])\n", - " # with np.unique now contains only the unique data\n", - " data=np.unique(np.array(xyz).reshape(int(len(xyz)/3),3), axis=0)\n", - " # ##MK::sprint 09\n", - " # using here df.to_hdf via pandas needs pandas==1.4.4 with pytables compiled under the hood !\n", - " # pytables however is not directly available as a compiled package via pypi\n", - " # in conda pytables is precompiled inside the conda package already\n", - " # label=('x','y','z') \n", - " # df=pd.DataFrame(data,columns=label)\n", - " h5File = \"controlpoints.h5\"\n", - " # df.to_hdf(h5File,'data')\n", - " # instead use h5py directly\n", - " h5w = h5py.File(h5File, 'w')\n", - " dst = h5w.create_dataset('/xyz', data=np.asarray(data, np.float32))\n", - " dst.attrs[\"units\"] = \"nm\"\n", - " h5w.close()\n", - " messagebox.showinfo(title='APT_analyzer',message='Control points saved to hdf5 file: \"controlpoints.h5\" inside the APT-analyzer folder.')\n", - "\n", - " def save_conc(self):\n", - " if self.conc_check==0:messagebox.showinfo(title='APT_analyzer',message='please plot concentration first')\n", - " else: \n", - " self.df_con.to_csv('APT_analyzer_concentration.csv')\n", - " messagebox.showinfo(title='APT_analyzer',message='Control points saved to csv file: \"APT_analyzer_concentration.csv\" inside the APT-analyzer folder.')\n", - " \n", - " def save_excess(self):\n", - " if self.excess_check==0:messagebox.showinfo(title='APT_analyzer',message='please plot excess first')\n", - " else: \n", - " self.df_ex.to_csv('APT_analyzer_excess.csv')\n", - " messagebox.showinfo(title='APT_analyzer',message='Control points saved to csv file: \"APT_analyzer_excess.csv\" inside the APT-analyzer folder.')\n", - " \n", - " def adjust(self): # adjust cylinder according to controlpoints\n", - " self.var_cyl.set(1)\n", - " XX=[] # setting up arrays\n", - " YY=[]\n", - " ZZ=[]\n", - " points=self.text_box.get(1.0,END) # get text from text box\n", - " points=points.split('\\n') # split the text into each row\n", - " points=[x for x in points if x!=''] # remove any rows that dont contain information\n", - " for i in range(0,len(points)): # loop over all rows\n", - " points_inner=points[i].split(',') # split each row into parts of each coordinates\n", - " xyz2=[]\n", - " for j in points_inner: # loop over all values inside row\n", - " m=j.split('=') # isolate actual number\n", - " xyz2.append(m[1]) # save number\n", - " XX.append(float(xyz2[0])) # save x,y and z coordinate\n", - " YY.append(float(xyz2[1]))\n", - " if self.i_plot.get()==0:ZZ.append(float(xyz2[2]))\n", - " if self.i_plot.get()==1:ZZ.append(-float(xyz2[2])) # if inverse tip is selected reverse z coordinate\n", - " \n", - " if len(XX)>=3: # if 3 or more control points are chosen\n", - " center_x=round(sum(XX)/len(XX),3) # calculate center in x, y and z\n", - " center_y=round(sum(YY)/len(YY),3)\n", - " if self.i_plot.get()==0:center_z=round(sum(ZZ)/len(ZZ),3)\n", - " if self.i_plot.get()==1:center_z=round(-sum(ZZ)/len(ZZ),3)\n", - " self.cly_x=StringVar(value=center_x) #save the center as coordinates for cylinder\n", - " self.cly_y=StringVar(value=center_y)\n", - " self.cly_z=StringVar(value=center_z)\n", - " tmp_C=[]\n", - " for i in range(len(XX)):\n", - " tmp_C.append([XX[i],YY[i],ZZ[i]]) #write x,y and z into one matrix and transpose it below\n", - " C=np.array(tmp_C).T\n", - " svd=np.linalg.svd(C - np.mean(C, axis=1, keepdims=True)) # using singular value decomposition to find fit\n", - " left=svd[0] # upper line has to substract out the centroid first\n", - " fit=left[:,-1]\n", - " normal=np.array([fit[0],fit[1],fit[2]])\n", - " norm=np.linalg.norm(normal)\n", - " n_n=normal/norm # select fitted values\n", - " aa=np.arcsin(-n_n[1])\n", - " bb=math.atan2(n_n[0],n_n[2]) # this is done by applying rotation matrix in reverse order onto normal verctor\n", - " # and then solving the equation for alpha and beta\n", - " #al=round(math.degrees(aa),4) # that rotate the normal vector into x=0 and y=0\n", - " #if normal[2]<0:al=round(math.degrees(-aa),4)\n", - " al=round(math.degrees(aa),4)\n", - " be=round(math.degrees(bb),4)\n", - " self.alpha=StringVar(value=al) # then transforming into degree and \n", - " self.beta=StringVar(value=be) # saving alpha and beta values into entry field\n", - " else: messagebox.showinfo(title='APT_analyzer',message='Choose at least 3 control points') # if less than 3 points are selected show message\n", - " \n", - " def search_pos(self): # function for searching for pos/epos file\n", - " if self.E==0:self.canvas_all.delete(self.aE)\n", - " if self.E==1:self.canvas_all.delete(self.aES)\n", - " self.filename=filedialog.askopenfilename() # getting file from search\n", - " S='.POS' in self.filename # checking if .POS is in the filename\n", - " ES='.EPOS' in self.filename # checking if .EPOS is in the filename\n", - " s='.pos' in self.filename # checking if .pos is in the filename\n", - " es='.epos' in self.filename # checking if .epos is in the filename\n", - " if S or s is True: # if .POS or .pos was selected\n", - " self.label_pos_selected=Label(text='pos file selected') # create label for pos\n", - " self.aE=self.canvas_all.create_window(120,190,window=self.label_pos_selected) #placing label on canvas\n", - " self.E=0 # set variable for later recognition\n", - " if self.succ==1: self.canvas_all.delete(self.a_succ) \n", - " self.succ=0\n", - " elif ES or es is True: # if .EPOS or .epos was selected\n", - " self.label_pos_selected=Label(text='epos file selected') # creating label for epos\n", - " self.aES=self.canvas_all.create_window(120,190,window=self.label_pos_selected) #put label on canvas\n", - " self.E=1 # set variable for later recognition\n", - " if self.succ==1: self.canvas_all.delete(self.a_succ) \n", - " self.succ=0\n", - " else: # if any other filetype was selected\n", - " messagebox.showinfo(title='APT_analyzer',message='select .pos or .epos file. Warning: Notation has to be either .pos /.epos or .POS / .EPOS')\n", - " self.filename=[] # leave filename empty\n", - " self.E=2 # set variable back to 2\n", - " \n", - " def search_rrng(self): # function for searching for rrng file\n", - " if self.R==1:self.canvas_all.delete(self.aRS)\n", - " if self.R==0:self.canvas_all.delete(self.aR)\n", - " self.filename2=filedialog.askopenfilename() # getting file from search\n", - " S='.RRNG' in self.filename2 # checking if .RRNG is in the filename\n", - " s='.rrng' in self.filename2 # checking if .rrng is in the filename\n", - " SR='.RNG' in self.filename2\n", - " sr='.rng' in self.filename2\n", - " if S or s is True: # if .RRNG or .rrng was selected\n", - " self.label_rrng_selected=Label(text='.rrng file selected') # create label for rrng\n", - " self.aR=self.canvas_all.create_window(280,190,window=self.label_rrng_selected) #placing label on canvas\n", - " self.R=0 # setting variable for later recognition\n", - " if self.succ==1: self.canvas_all.delete(self.a_succ) \n", - " self.succ=0\n", - " elif SR or sr is True:\n", - " self.label_rrng_selected=Label(text='.rng file selected') # create label for rrng\n", - " self.aRS=self.canvas_all.create_window(280,190,window=self.label_rrng_selected) #placing label on canvas\n", - " self.R=1 \n", - " if self.succ==1: self.canvas_all.delete(self.a_succ) \n", - " self.succ=0\n", - " else: # if no correct file was selected\n", - " messagebox.showinfo(title='APT_analyzer',message='select .rrng file. Warning: Notation has to be either .rrng or .RRNG')\n", - " self.filename2=[] # leaving filename empty\n", - " self.R=2 # set variable back to 2\n", - " \n", - " def calculate_tip(self): # function for reading and converting the pos and rrng file and creating the tip\n", - " if self.E==2:messagebox.showinfo(title='APT_analyzer',message='no file .pos/.epos file selected') # checking if no pos/epos file is selected (E=2)\n", - " elif self.R==2:messagebox.showinfo(title='APT_analyzer',message='no file .rrng file selected') # checking if no rrng file is selected (R=2)\n", - " else: # if pos/epos and rrng file is selected\n", - " with open(self.filename, 'rb') as f: # opening pos/epos data from file1\n", - " data = f.read() # reading pos/epos data from file1\n", - " data_rrng=self.filename2 # reading rrng data from file2\n", - " if self.E==0:pos=apt.read_pos(data) # converting pos data if pos file was selected (E==0)\n", - " elif self.E==1:pos=apt.read_epos(data) # converting epos data if epos file was selected (E==1)\n", - " if self.R==0:rrngs=apt.read_rrng(data_rrng) # converting rrng data\n", - " elif self.R==1:rrngs=apt.read_rng(data_rrng)\n", - " self.tip=apt.label_ions(pos, rrngs) # label ions using apt_importers functions\n", - " self.ele=apt.unique(list(self.tip['comp'])) # determining how many unique elements in tip using apt_importers functions\n", - " string=['all'] # setting up strings for atom selection combobox\n", - " for i in range (0,len(self.ele)): # loop over all unique elements\n", - " string.append(self.ele[i]) # creating strings with all unique elements\n", - " self.data_atom=(string) # data for combobox containing all unique elements + 'all' at start\n", - " self.cb_atom = Combobox(tk, values=self.data_atom) # creating combobox with unique elements data \n", - " self.atom=1\n", - " \n", - " self.cb_atom.current(0) # setting initial value of combobox to 'all' \n", - " self.fig.clear() # if plot single was selected clear all previous plots\n", - " self.canvas_all.delete('message') \n", - " self.spec=gridspec.GridSpec(ncols=2,nrows=2,width_ratios=[2,1],wspace=0.5,hspace=0.5,height_ratios=[2,1]) #adjust the size of the figure compared to (111)\n", - " self.ax=self.fig.add_subplot(self.spec[0],projection='3d')\n", - " N=5000\n", - " u_ele=self.ele\n", - " x=np.array(self.tip['x'])[::N] # select x cooridant (only take every Nth atom to plot later)\n", - " y=np.array(self.tip['y'])[::N] # select y cooridant from tip data\n", - " z=np.array(self.tip['z'])[::N] # select z cooridant from tip data\n", - " c=np.array(self.tip['colour'])[::N] # select the colour column in the tip data\n", - " label=np.array(self.tip['comp'])[::N] # select the composition column in the tip data\n", - " for i in range(0,len(u_ele)): # loop that goes over every different elements\n", - " e=label==u_ele[i] # seperation of the elements\n", - " x_new=x[e] # select the x values of each element indiviually\n", - " y_new=y[e]\n", - " z_new=z[e]\n", - " c_new=c[e]\n", - " self.ax.scatter3D(x_new,y_new,z_new,c=c_new,label=u_ele[i],s=1) # scatter plot each element seperatly and assign label\n", - " x_N=len(x) \n", - " self.ax.set_title('preview (number of atoms={:.0f}, 1/{:.0f} of all atoms)'.format(x_N,N)) # assigne x_N value to show the number of plotted atoms in title\n", - " self.canvas.draw_idle()\n", - " apt.set_axes_equal(self.ax) # function for setting the axes equal for 3d plot\n", - " self.ax.legend(loc='center left', bbox_to_anchor=(1.07, 0.5))#, fontsize=7) # show legends of each element \n", - " self.ax.set_xlabel('X-axis',fontweight='bold') # label x axis\n", - " self.ax.set_ylabel('Y-axis',fontweight='bold') # label y axis\n", - " self.ax.set_zlabel('Z-axis',fontweight='bold') # label z axis\n", - " self.label_succ=Label(text='Sucessfully loaded files!') # create label for rrng\n", - " self.a_succ=self.canvas_all.create_window(200,260,window=self.label_succ) #placing label on canvas\n", - " self.succ=1\n", - " \n", - " def clear(self): # function for clearing canvas\n", - " self.fig.clear() # clearing canvas\n", - " self.canvas.draw_idle() # drawing \n", - " self.canvas_all.delete('message') # deleting message about concentration data\n", - " \n", - " def plot_tip(self): # function about plotting tip\n", - " if self.plot_exist==1: # if plot already exists get the parameters of old plot\n", - " azim=self.ax.azim # get azimutal angle of old plot\n", - " elev=self.ax.elev\n", - " xlim=self.ax.get_xlim() # get x,y and z limit of old plot\n", - " ylim=self.ax.get_ylim()\n", - " zlim=self.ax.get_zlim()\n", - " if self.i_plot.get()==0:self.invert=0 # if inversion checkbox is unmarked set value=0\n", - " else: self.invert=1 # if inversion checkbox is marked set value=1\n", - " \n", - " color_zyl=str(self.cb_color.get()) # get color of cylinder from color entry box\n", - " color_cp=str(self.cb_color_cp.get())\n", - " self.canvas_all.delete('message') # clearing message about concentration data\n", - " Z= self.cb_atom.get() # getting variable from combobox of atom selection\n", - " M = self.slider_res.get() # getting variable from combobox of resolution selection\n", - " SS=self.slider_size.get() # getting variable from combobox of atom size selection\n", - " S=float(SS)/10 # transforming entry into float and deviding by 10 so values 0.1 to 1 are also included\n", - " image_size=self.slider_image_size.get()\n", - " image_size=float(image_size)**2\n", - " u_ele=self.ele\n", - " self.fig.clear() # if plot single was selected clear all previous plots\n", - " self.spec=gridspec.GridSpec(ncols=2,nrows=2,width_ratios=[image_size,1],wspace=0.1,hspace=0.1,height_ratios=[image_size,1]) #adjust the size of the figure compared to (111)\n", - " self.ax=self.fig.add_subplot(self.spec[0],projection='3d') # make new figure with size depending on the image size slider\n", - " if self.plot_exist==1: # if plot already exist set old parameters of plot as new ones\n", - " self.ax.view_init(elev=elev,azim=azim) # set azimuthal angle and elevation of old plot as new one\n", - " self.ax.set_xlim3d(xlim[0],xlim[1]) # set x,y and z limit of old plot as new one\n", - " self.ax.set_ylim3d(ylim[0],ylim[1])\n", - " if self.i_plot.get()==0 and self.invert==0:self.ax.set_zlim3d(zlim[0],zlim[1]) # depending on if the plot should be inverted or not\n", - " if self.i_plot.get()==1 and self.invert==1:self.ax.set_zlim3d(zlim[0],zlim[1]) # reset the z limit of new plot to center it again\n", - " self.plot_exist=1 # set plot exist as one in order to mark that plot exists\n", - " n=len(np.array(self.tip['x'])) # get the number of atoms\n", - " N=int(50000/(10**M)) # calc N (the number how many atoms should be plotted)\n", - " if N==0:N=1\n", - " \n", - " x=np.array(self.tip['x'])[::N] # select x cooridant (only take every Nth atom to plot later)\n", - " y=np.array(self.tip['y'])[::N] # select y cooridant from tip data\n", - " z=np.array(self.tip['z'])[::N] # select z cooridant from tip data\n", - " c=np.array(self.tip['colour'])[::N] # select the colour column in the tip data\n", - " label=np.array(self.tip['comp'])[::N] # select the composition column in the tip data\n", - "\n", - " max_x=max(x)\n", - " min_x=min(x)\n", - " max_z=max(z)\n", - " min_z=min(z)\n", - " max_y=max(y)\n", - " min_y=min(y)\n", - " for i in range(0,len(u_ele)): # loop that goes over every different elements\n", - " e=label==u_ele[i] # seperation of the elements\n", - " x_new=x[e] # select the x values of each element indiviually\n", - " y_new=y[e]\n", - " z_new=z[e]\n", - " c_new=c[e] \n", - " if self.i_plot.get()==1: z_new=-z_new # if inverse tip is selected change the sign of the z coordinates\n", - " if Z=='all': # if all was selected plot all of them together\n", - " X=np.array([x,y,z]).T \n", - " c_new[c_new=='#FFFFFF']='#000000'\n", - " self.ax.scatter(x_new,y_new,z_new,c=c_new,label=u_ele[i],s=S,zorder = 1,depthshade = False,picker=True) # scatter plot each element seperatly and assign label\n", - " x_N=len(x) \n", - " self.ax.set_title('number of atoms={:.0f}, 1/{:.0f} of all atoms'.format(x_N,N)) # assigne x_N value to show the number of plotted atoms in title\n", - " elif u_ele[i]==Z: # if a certain element was selected in combobox for element selection\n", - " X=np.array([x_new,y_new,z_new]).T # putting all \n", - " c_new[c_new=='#FFFFFF']='#000000'\n", - " self.ax.scatter(X[:,0],X[:,1],X[:,2],c=c_new,label=u_ele[i],s=S,zorder = 1, depthshade = False,picker=True) #scatter plot each element seperatly and assign label \n", - " x_N=len(x_new) # assigne x_N only to show the number of plotted atoms of selected element \n", - " self.ax.set_title('number of atoms={:.0f}, 1/{:.0f} of all atoms of this type'.format(x_N,N)) \n", - " apt.set_axes_equal(self.ax) # function for setting the axes equal for 3d plot\n", - " self.ax.legend(loc='center left', bbox_to_anchor=(1.07, 0.5))#, fontsize=7) # show legends of each element\n", - " if self.var_axis.get()==0: # if show axis was selected\n", - " self.ax.set_xlabel('X-axis',fontweight='bold') # label x axis\n", - " self.ax.set_ylabel('Y-axis',fontweight='bold') # label y axis\n", - " self.ax.set_zlabel('Z-axis',fontweight='bold') # label z axis\n", - " elif self.var_axis.get()==1: # if show axis was not selected\n", - " self.ax.set_axis_off() # dont show axis\n", - " \n", - " if self.var_cyl.get()==1: # if show clyinder was selected\n", - " x_pos=self.cly_x.get() # get x value from x entry box\n", - " if x_pos=='': x_pos=0\n", - " else:x_pos = float(x_pos)\n", - " y_pos =self.cly_y.get() # get y value from y entry box\n", - " if y_pos=='': y_pos=0\n", - " else:y_pos = float(y_pos)\n", - " z_pos = self.cly_z.get() # get z value from z entry box\n", - " if z_pos=='': z_pos=0\n", - " else:z_pos = float(z_pos) \n", - " if self.i_plot.get()==1: z_pos = -z_pos # if inverse tip is selected inverse the z coordinates of the cylinder \n", - " height = float(self.height.get()) # get height value from height entry box\n", - " alpha = float(self.alpha.get()) # get tilt along y value from alpha entry box\n", - " beta = float(self.beta.get()) # get tilt along x value from beta entry box\n", - " r = float(self.radius.get()) # get radius value from radius entry box \n", - " theta=np.linspace(0,2*np.pi,201) # create linspace variable for plotting the circle\n", - " alpha=math.radians(alpha) # transform alpha from deg in rad\n", - " beta=math.radians(beta) # transfrom beta from deg in rad\n", - " Xc,Yc,Zc=data_for_cylinder_along_z(0,0,0,r,height) # calculating cylinder\n", - " Z=np.array([Xc,Yc,Zc]).T\n", - " matrix_l=np.array([(np.cos(beta), np.sin(alpha)*np.sin(beta), np.cos(alpha)*np.sin(beta)),\n", - " (0, np.cos(alpha), -np.sin(alpha)),\n", - " (-np.sin(beta), np.sin(alpha)*np.cos(beta), np.cos(alpha)*np.cos(beta))])\n", - " pro=[]\n", - " for j in range(0,len(Z)):\n", - " for i in range (0,len(Z[j])):\n", - " pro.append(matrix_l.dot(Z[i][j])) \n", - " pro=np.array(pro) \n", - " Z_new=pro.reshape(50,50,3).T\n", - " self.ax.plot_surface(Z_new[0]+x_pos,Z_new[1]+y_pos,Z_new[2]+z_pos,alpha=0.4,color=color_zyl) # plotting cylinder\n", - " apt.set_axes_equal(self.ax)\n", - " \n", - " if self.var_points.get()==1:\n", - " XX=[]\n", - " YY=[]\n", - " ZZ=[]\n", - " points=[]\n", - " points=self.text_box.get(1.0,END)\n", - " points=points.split('\\n')\n", - " points=[x for x in points if x!='']\n", - " for i in range(0,len(points)):\n", - " points_inner=points[i].split(',')\n", - " xyz2=[]\n", - " for j in points_inner:\n", - " m=j.split('=')\n", - " xyz2.append(m[1])\n", - " x2=float(xyz2[0])\n", - " y2=float(xyz2[1])\n", - " z2=float(xyz2[2])\n", - " XX.append(x2)\n", - " YY.append(y2)\n", - " ZZ.append(z2)\n", - " if self.i_plot.get()==1:z2=-z2 # if inverse tip is selected\n", - " self.ax.scatter3D(x2,y2,z2,marker='+',color=color_cp,s=S*100)\n", - " if len(XX)>=3 and self.var_plane.get()==1:\n", - " center_x=sum(XX)/len(XX)\n", - " center_y=sum(YY)/len(YY)\n", - " center_z=sum(ZZ)/len(ZZ)\n", - " self.cly_x=StringVar(value=center_x)\n", - " self.cly_y=StringVar(value=center_y)\n", - " self.cly_z=StringVar(value=center_z)\n", - " tmp_C=[]\n", - " for i in range(len(XX)):\n", - " tmp_C.append([XX[i],YY[i],ZZ[i]])\n", - " C=np.array(tmp_C).T\n", - " svd=np.linalg.svd(C - np.mean(C, axis=1, keepdims=True))\n", - " left=svd[0]\n", - " fit=left[:,-1]\n", - " #print('solution: %f x +%f y +%f z = 0' %(fit[0],fit[1],fit[2]))\n", - " normal=np.array([fit[0],fit[1],fit[2]]) \n", - " r=5\n", - " self.ax.plot([center_x,center_x+r*normal[0]],[center_y,center_y+r*normal[1]],[center_z,center_z+r*normal[2]],color='r',linewidth=5)\n", - " xlim=self.ax.get_xlim()\n", - " ylim=self.ax.get_ylim()\n", - " X,Y=np.meshgrid(np.arange(xlim[0],xlim[1]),np.arange(ylim[0],ylim[1]))\n", - " Z=np.zeros(X.shape)\n", - " for r in range(X.shape[0]):\n", - " for c in range(X.shape[1]):\n", - " #Z[r,c]=fit[0]*X[r,c]+fit[1]*Y[r,c]+fit[2]\n", - " Z[r,c]=-(fit[0]*X[r,c]+fit[1]*Y[r,c])/fit[2]\n", - " #self.ax.plot([cpx,fit[0]],[cpy,fit[1]],[cpz,-1])\n", - " self.ax.plot_wireframe(X+center_x,Y+center_y,Z+center_z,color='k')\n", - " self.canvas.draw_idle() \n", - " \n", - " def distance(point, event):\n", - "# Return distance between mouse position and given data point\n", - "# Args:point (np.array): np.array of shape (3,), with x,y,z in data coords event (MouseEvent): mouse event (which contains mouse position in .x and .xdata)\n", - "# Returns:distance (np.float64): distance (in screen coords) between mouse pos and data point \n", - " assert point.shape == (3,), \"distance: point.shape is wrong: %s, must be (3,)\" % point.shape \n", - " x2, y2, _ = proj3d.proj_transform(point[0], point[1], point[2], self.ax.get_proj()) # Project 3d data space to 2d data space\n", - " x3, y3 = self.ax.transData.transform((x2, y2)) # Convert 2d data space to 2d screen space\n", - " return np.sqrt ((x3 - event.x)**2 + (y3 - event.y)**2) # calculate distance\n", - " \n", - " def onclick(event):\n", - " distances = [distance (X[i,0:3], event) for i in range(X.shape[0])] # use function distance to evaluate the closests index to mouse click\n", - " index=np.argmin(distances) # select the closest index\n", - " self.ax.scatter(X[index, 0], X[index, 1], X[index, 2],marker='+',color=color_cp,s=S*100) # plot controlpoint at closest index point\n", - " text='x='+str(np.round(X[index, 0],4))+', y='+str(np.round(X[index, 1],4))+', z='+str(np.round(X[index, 2],4)) # write x,y,z coordinates into text file\n", - " if self.i_plot.get()==1:text='x='+str(np.round(X[index, 0],4))+', y='+str(np.round(X[index, 1],4))+', z='+str(-np.round(X[index, 2],4))\n", - " self.text_box.insert(END,text) # insert text file in textbox\n", - " self.text_box.insert(END,'\\n') # make sure next control point is next row\n", - " self.canvas.draw_idle()\n", - " \n", - " if self.control.get()==1: \n", - " if self.diss==1:self.fig.canvas.mpl_disconnect(self.cid)\n", - " self.cid=self.fig.canvas.mpl_connect('button_press_event',onclick)\n", - " self.diss=1\n", - " elif self.control.get()==0 and self.diss==1: \n", - " self.fig.canvas.mpl_disconnect(self.cid)\n", - " self.diss=0\n", - " \n", - " def calc_con(self): # function for calculating the concentration\n", - " if self.diss2==1:self.fig.canvas.mpl_disconnect(self.cid) \n", - " self.plot_exist=0\n", - " self.print_con=[]\n", - " self.print_con2=[]\n", - " self.canvas_all.delete('message') \n", - " start_end=self.cb_bulk.get()\n", - " atom= self.cb_atom.get()\n", - " u_ele_real=self.ele\n", - " image_size=self.slider_image_size.get()\n", - " image_size=float(image_size)/2\n", - " self.fig.clear()\n", - " self.spec=gridspec.GridSpec(ncols=2,nrows=2,width_ratios=[image_size,1],wspace=0.5,hspace=0.5,height_ratios=[image_size,1]) #adjust the size of the figure compared to (111)\n", - " self.ax1=self.fig.add_subplot(self.spec[0])\n", - " self.ax1.set_xlabel('z position of cylinder')\n", - " self.ax1.set_ylabel('concentration [%]')\n", - " x_real=np.array(self.tip['x'])\n", - " y_real=np.array(self.tip['y'])\n", - " z_real=np.array(self.tip['z'])\n", - " if self.i_plot.get()==1: # atoms z coordinatines inversion\n", - " z_real = -z_real\n", - " c_real=np.array(self.tip['colour'])\n", - " label_real=np.array(self.tip['comp'])\n", - " inter=float(self.slider_inter.get()/20)\n", - " height = float(self.height.get())\n", - " alpha = float(self.alpha.get())\n", - " beta = float(self.beta.get())\n", - " r = float(self.radius.get())\n", - " x_pos = float(self.cly_x.get())\n", - " y_pos = float(self.cly_y.get())\n", - " z_pos = float(self.cly_z.get())\n", - " if self.i_plot.get()==1: # cylinder z coordinatines inversion\n", - " z_pos = -z_pos\n", - " z_start=-height/2\n", - " z_end=height/2\n", - " alpha=math.radians(-alpha)\n", - " beta=math.radians(-beta)\n", - " ################### tilt and move zylinder ######################################\n", - " pro=[]\n", - " Z=np.array([x_real-x_pos,y_real-y_pos,z_real-z_pos]).T\n", - " M_ROT1=[(1, 0, 0 ), # rotational matrix along the x-direction\n", - " (0, np.cos(alpha), -np.sin(alpha)),\n", - " (0, np.sin(alpha), np.cos(alpha))]\n", - "\n", - " M_ROT2=[(np.cos(beta), 0, np.sin(beta)), # rotational matrix along the y-direction\n", - " (0, 1, 0 ),\n", - " (-np.sin(beta), 0, np.cos(beta))]\n", - " matrix_l=np.array(M_ROT1).dot(M_ROT2)\n", - " for j in range(0,len(Z)):\n", - " pro.append(matrix_l.dot(Z[j])) \n", - " Z_new=np.array(pro)\n", - " x_real=Z_new[:,0]\n", - " y_real=Z_new[:,1]\n", - " z_real=Z_new[:,2]\n", - " ######################################################################### \n", - " xy=((x_real)**2+(y_real)**2)**(1/2) # cirlce constraint for atoms \n", - " circle=xy<=r # only select atoms inside of circle\n", - " message='concentration: \\n' # initialize the message displayed on the right side of the plot\n", - " for m in range(0,len(u_ele_real)): # loop over each individual elements\n", - " plot_z=[] # x component of concentration plots \n", - " Num=[] # Number of atoms inside the circle and intervals (for concentration calculation)\n", - " Num_all=[] # total number of atoms (of each element) to calculate the % contribution of each\n", - " e=label_real[circle]==u_ele_real[m] # seperation of the elements\n", - " z_circle=z_real[circle] # only selec the atoms with circle constraint\n", - " z_p=z_circle[e] # only select the atoms wth circle constraint of specific element\n", - " c_circle=c_real[circle]\n", - " c_p=c_circle[e]\n", - " for j in range (0,int((z_end-z_start)/inter)+1): # loop of intervals\n", - " init=z_p>=z_start+j*inter # starting condition of interval for each element\n", - " init_all=z_circle>=z_start+j*inter # starting condition of interval for all elements together\n", - " z_all2=z_circle[init_all] # selecting atoms which fulfill starting constraint for all elements\n", - " z_p2=z_p[init] # selecting atoms which fulfill the starting constraint for each individual element\n", - " init2=z_p2<=z_start+(j*inter+inter) # ending constraint of interval\n", - " init2_all=z_all2<=z_start+(j*inter+inter) # ending constraint of interval for all elements\n", - " z_all3=z_all2[init2_all] # selecting atoms which also fulfill ending constraint for all elements\n", - " z_p3=z_p2[init2] # selecting atom which also fulfill ending constraint for each individual elements\n", - " Num_all.append(len(z_all3)) # append the number of atoms which fulfill both contraints\n", - " Num.append(len(z_p3)) # append the number of atoms which fulfill both constraints for each individual element\n", - " plot_z.append(z_start+j*inter)#+inter/2) # append the x-coordinate for the concentration plot\n", - " a=np.array(Num,dtype=float) # transform total number of atoms for each individual element into array of floats\n", - " b=np.array(Num_all,dtype=float) # transform total number of atoms into array of floats\n", - " if self.var_switch.get()==1: \n", - " if start_end=='start':b=np.array(Num_all[0])\n", - " last=len(np.array(Num_all))-1 \n", - " if start_end=='end':b=np.array(Num_all[last])\n", - " if b==0:messagebox.showinfo(title='APT_analyzer',message='Reference bulk concentration is 0')\n", - " self.conc=100*np.divide(a,b, out=np.zeros_like(a), where=b!=0) # calculate concentration, if total amount of atoms == 0, result is 0\n", - " message=message+u_ele_real[m] # append element type into message\n", - " message=message+': maximal value: ' # add text to message\n", - " message=message+str(round(max(self.conc),2)) # add maximum of concentration to message\n", - " message=message+' minimal value: ' # add text to message\n", - " message=message+str(round(min(self.conc),2)) # add minimum of concentration to message\n", - " message=message+'\\n' # add line split to message \n", - " if atom=='all': # seperate if atom select checkbox 'all' is selected\n", - " if c_p[0]=='#FFFFFF':c_p[0]='#000000' # if color of particles is white change it to black so it can be seen with white background\n", - " self.ax1.plot(plot_z,self.conc,c_p[0],label=u_ele_real[m]) # plot the concentration of each element individually\n", - " self.print_con2.append(u_ele_real[m])\n", - " self.print_con.append(self.conc)\n", - " elif u_ele_real[m]==atom: # if certain element is selected in checkbox\n", - " if c_p[0]=='#FFFFFF':c_p[0]='#000000'\n", - " self.ax1.plot(plot_z,self.conc,c_p[0],label=u_ele_real[m]) # only plot selected element from the select atom checkbox\n", - " self.print_con2.append(u_ele_real[m])\n", - " self.print_con.append(self.conc)\n", - " self.print_con2.append('z_cyl')\n", - " plot_z2=plot_z.copy()\n", - " plot_z2=np.array(plot_z2).reshape(np.shape(np.array(self.conc)))\n", - " self.print_con.append(plot_z2)\n", - " self.df_con=pd.DataFrame(np.array(self.print_con).T,columns=self.print_con2)\n", - " self.ax1.legend(loc='center left', bbox_to_anchor=(1.07, 0.5))\n", - " self.ax1.set_title('concentration of elements along cylinder / intervalsize=%1.2f' %inter)\n", - " self.canvas.draw_idle()\n", - " self.message=Text(height = 20,width = 50,relief=FLAT)\n", - " self.message.insert(END, message)\n", - " self.message.configure(state='disabled')\n", - " image_ratio=image_size/2.5\n", - " place_factor=0.2\n", - " place_value=1450-place_factor*(1450-image_ratio*1450)\n", - " self.canvas_all.create_window(place_value,300,window=self.message,tags='message')\n", - " self.conc_check=1\n", - " \n", - " \n", - " \n", - " def calc_excess(self):\n", - " self.plot_exist=0\n", - " self.print_ex=[]\n", - " self.print_ex2=[]\n", - " self.canvas_all.delete('message') \n", - " atom= self.cb_atom.get() \n", - " color_zyl=str(self.cb_color.get())\n", - " u_ele_real=self.ele \n", - " image_size=self.slider_image_size.get()\n", - " image_size=float(image_size)/2\n", - " self.fig.clear()\n", - " self.spec=gridspec.GridSpec(ncols=2,nrows=2,width_ratios=[image_size,1],wspace=0.5,hspace=0.5,height_ratios=[image_size,1]) #adjust the size of the figure compared to (111)\n", - " self.ax1=self.fig.add_subplot(self.spec[0])\n", - " self.ax1.set_xlabel('z position of cylinder')\n", - " self.ax1.set_ylabel('Total amount of atoms')\n", - " x_real=np.array(self.tip['x'])\n", - " y_real=np.array(self.tip['y'])\n", - " z_real=np.array(self.tip['z']) # atoms z coordinatines inversion\n", - " if self.i_plot.get()==1:\n", - " z_real=-z_real\n", - " c_real=np.array(self.tip['colour'])\n", - " label_real=np.array(self.tip['comp'])\n", - " inter=float(self.slider_inter.get()/20)\n", - " height = float(self.height.get())\n", - " alpha = float(self.alpha.get())\n", - " beta = float(self.beta.get())\n", - " r = float(self.radius.get())\n", - " x_pos = float(self.cly_x.get())\n", - " y_pos = float(self.cly_y.get())\n", - " z_pos = float(self.cly_z.get())\n", - " if self.i_plot.get()==1: # cylinder z coordinatines inversion\n", - " z_pos=-z_pos\n", - " z_start=-height/2\n", - " z_end=height/2\n", - " alpha=math.radians(-alpha) # transfroms radius into radians\n", - " beta=math.radians(-beta) # - because otherwise it tilts the cylinder in the wrong direction (dont know why)\n", - " ################### tilt and move zylinder ######################################\n", - " pro=[]\n", - " Z=np.array([x_real-x_pos,y_real-y_pos,z_real-z_pos]).T\n", - " M_ROT1=[(1, 0, 0 ), # rotational matrix along the x-direction\n", - " (0, np.cos(alpha), -np.sin(alpha)),\n", - " (0, np.sin(alpha), np.cos(alpha))]\n", - "\n", - " M_ROT2=[(np.cos(beta), 0, np.sin(beta)), # rotational matrix along the y-direction\n", - " (0, 1, 0 ),\n", - " (-np.sin(beta), 0, np.cos(beta))]\n", - " matrix_l=np.array(M_ROT1).dot(M_ROT2)\n", - " for j in range(0,len(Z)):\n", - " pro.append(matrix_l.dot(Z[j])) \n", - " Z_new=np.array(pro)\n", - " x_real=Z_new[:,0]\n", - " y_real=Z_new[:,1]\n", - " z_real=Z_new[:,2]\n", - " ######################################################################### \n", - " xy=((x_real)**2+(y_real)**2)**(1/2)\n", - " circle=xy<=r\n", - " for m in range(0,len(u_ele_real)):\n", - " plot_z=[]\n", - " excess=[]\n", - " ex=0\n", - " e=label_real[circle]==u_ele_real[m] #seperation of the atoms \n", - " z_circle=z_real[circle]\n", - " z_p=z_circle[e]\n", - " c_circle=c_real[circle]\n", - " c_p=c_circle[e]\n", - " for j in range (0,int((z_end-z_start)/inter)+1):\n", - " init=z_p>=z_start+j*inter \n", - " z_p2=z_p[init]\n", - " init2=z_p2<=z_start+(j*inter+inter)\n", - " z_p3=z_p2[init2]\n", - " ex+=len(z_p3)\n", - " excess.append(ex)\n", - " plot_z.append(z_start+j*inter)#+inter/2) \n", - " if self.var_norm.get()==1:excess=np.array(excess)/(max(excess))\n", - " if atom=='all':\n", - " if c_p[0]=='#FFFFFF':c_p[0]='#000000'\n", - " self.ax1.plot(plot_z,excess,c_p[0],label=u_ele_real[m])\n", - " excess_save=excess.copy()\n", - " self.print_ex2.append(u_ele_real[m])\n", - " self.print_ex.append(np.array(excess))\n", - " elif u_ele_real[m]==atom:\n", - " if c_p[0]=='#FFFFFF':c_p[0]='#000000'\n", - " self.ax1.plot(plot_z,excess,c_p[0],label=u_ele_real[m]) \n", - " excess_save=excess.copy()\n", - " self.print_ex2.append(u_ele_real[m])\n", - " self.print_ex.append(np.array(excess)) \n", - " self.print_ex2.append('z_cyl')\n", - " plot_z2=plot_z.copy()\n", - " plot_z2=np.array(plot_z2).reshape(np.shape(np.array(excess)))\n", - " self.print_ex.append(plot_z2)\n", - " self.df_ex=pd.DataFrame(np.array(self.print_ex).T,columns=self.print_ex2)\n", - " self.ax1.set_title('Excess (total atom number of elements along cylinder) / intervalsize=%1.2f ' %inter)\n", - " self.ax1.legend(loc='center left', bbox_to_anchor=(1.07, 0.5))\n", - " \n", - " points_ex=self.text_box_ex.get(1.0,END) # get text from text box\n", - " if points_ex!='\\n':\n", - " points_ex=points_ex.split('\\n') # split the text into each row\n", - " points_ex=[x for x in points_ex if x!=''] # remove any rows that dont contain information\n", - " XX=[]\n", - " YY=[]\n", - " for i in range(0,len(points_ex)): # loop over all rows\n", - " points_inner=points_ex[i].split(',') # split each row into parts of each coordinates\n", - " xy2=[]\n", - " for j in points_inner: # loop over all values inside row\n", - " m=j.split('=') # isolate actual number\n", - " xy2.append(m[1]) # save number\n", - " XX.append(float(xy2[0])) # save x,y and z coordinate\n", - " YY.append(float(xy2[1]))\n", - " if len(XX)==4:\n", - " k1=(YY[0]-YY[1])/(XX[0]-XX[1])\n", - " k3=(YY[2]-YY[3])/(XX[2]-XX[3])\n", - " d1=YY[1]-k1*XX[1]\n", - " d3=YY[3]-k3*XX[3]\n", - " x=np.array([-height/2,height/2])\n", - " y1=gerade(np.array(k1),x,np.array(d1))\n", - " y3=gerade(np.array(k3),x,np.array(d3))\n", - " diff=[]\n", - " for i in range(0,len(excess_save)):\n", - " upper_line=gerade(np.array(k3),plot_z[i],np.array(d3))\n", - " lower_line=gerade(np.array(k1),plot_z[i],np.array(d1)) \n", - " diff.append(np.abs((upper_line-excess_save[i])-(excess_save[i]-lower_line)))\n", - " plot_z=np.array(plot_z)\n", - " x_found=plot_z[diff==min(diff)]\n", - " y_excess_u=gerade(np.array(k3),x_found,np.array(d3))\n", - " y_excess_l=gerade(np.array(k1),x_found,np.array(d1))\n", - " excess_atom_value=y_excess_u[0]-y_excess_l[0]\n", - " excess_value=excess_atom_value/(np.pi*r**2)\n", - " self.ax1.plot([x_found,x_found],[y_excess_l,y_excess_u])\n", - " self.ax1.plot(x,y1,color=color_zyl)\n", - " self.ax1.plot(x,y3,color=color_zyl)\n", - " self.ax1.scatter(XX,YY,color='black',marker='x')\n", - " self.ax1.annotate(\" 1\", (XX[0], YY[0]))\n", - " self.ax1.annotate(\" 2\", (XX[1], YY[1]))\n", - " self.ax1.annotate(\" 3\", (XX[2], YY[2]))\n", - " self.ax1.annotate(\" 4\", (XX[3], YY[3]))\n", - " self.output = Text(height = 10,width = 50,relief=FLAT)\n", - " image_ratio=image_size/2.5\n", - " place_factor=0.25\n", - " place_value=650-place_factor*(650-image_ratio*650)\n", - " self.canvas_all.create_window(650,place_value,window=self.output,tags='message')\n", - " message2='Calculated with the following 4 points: \\n'\n", - " message2=message2+'point 1: x = '+str(XX[0])+' y = '+str(YY[0])+'\\n'\n", - " message2=message2+'point 2: x = '+str(XX[1])+' y = '+str(YY[1])+'\\n'\n", - " message2=message2+'point 3: x = '+str(XX[2])+' y = '+str(YY[2])+'\\n'\n", - " message2=message2+'point 4: x = '+str(XX[3])+' y = '+str(YY[3])+'\\n'+'\\n'\n", - " message2=message2+'the interfacial excess atoms = '+str(np.round(excess_atom_value,3))+' atoms'+'\\n'\n", - " message2=message2+'the interfacial excess = '+str(np.round(excess_value,3))+' atoms/nm²'\n", - " self.output.insert(END, message2)\n", - " self.output.configure(state='disabled')\n", - " self.canvas.draw_idle() \n", - " elif len(XX)>=4:messagebox.showinfo(title='APT_analyzer',message='Too many fit points selected. Reduce to 4 points to fit excess.')\n", - " def onclick(event):\n", - " x=np.round(event.xdata,2)\n", - " y=np.round(event.ydata,2)\n", - " self.ax1.scatter(x,y,color='black',marker='x')\n", - " text='x='+str(x)+', y='+str(y)\n", - " self.text_box_ex.insert(END,text)\n", - " self.text_box_ex.insert(END,'\\n')\n", - " self.canvas.draw_idle() \n", - " if self.diss2==1:self.fig.canvas.mpl_disconnect(self.cid) \n", - " self.cid=self.fig.canvas.mpl_connect('button_press_event',onclick)\n", - " self.diss2=1\n", - " self.canvas.draw_idle() \n", - " self.excess_check=1\n", - " \n", - " def calc_zoom(self): \n", - " self.plot_exist=0\n", - " self.canvas_all.delete('message') \n", - " image_size=self.slider_image_size.get()\n", - " image_size=float(image_size)**2\n", - " self.fig.clear()\n", - " self.spec=gridspec.GridSpec(ncols=2,nrows=2,width_ratios=[image_size,1],wspace=0.5,hspace=0.5,height_ratios=[image_size,1]) #adjust the size of the figure compared to (111)\n", - " self.ax2=self.fig.add_subplot(self.spec[0],projection='3d')\n", - " atom= self.cb_atom.get()\n", - " M = self.slider_res.get()\n", - " SS=self.slider_size.get()\n", - " S=float(SS)/10\n", - " u_ele_real=self.ele\n", - " n=len(np.array(self.tip['x']))\n", - " N_plot=int(50000/(10**M))\n", - " if N_plot==0:N_plot=1\n", - " x_real=np.array(self.tip['x'])\n", - " y_real=np.array(self.tip['y']) \n", - " z_real=np.array(self.tip['z'])\n", - " if self.i_plot.get()==1: # atoms z coordinatines inversion\n", - " z_real = -z_real\n", - " c_real=np.array(self.tip['colour'])\n", - " label_real=np.array(self.tip['comp'])\n", - " height = float(self.height.get())\n", - " alpha = -float(self.alpha.get())\n", - " beta = -float(self.beta.get())\n", - " r = float(self.radius.get())\n", - " x_pos = float(self.cly_x.get())\n", - " y_pos = float(self.cly_y.get())\n", - " z_pos = float(self.cly_z.get())\n", - " if self.i_plot.get()==1: # cylinder z coordinatines inversion\n", - " z_pos = -z_pos\n", - " color_zyl=str(self.cb_color.get())\n", - " z_start=-height/2\n", - " z_end=height/2\n", - " alpha=math.radians(alpha)\n", - " beta=math.radians(beta)\n", - " ################### tilt and move zylinder ######################################\n", - " pro=[]\n", - " Z=np.array([x_real-x_pos,y_real-y_pos,z_real-z_pos]).T\n", - " M_ROT1=[(1, 0, 0 ), # rotational matrix along the x-direction\n", - " (0, np.cos(alpha), -np.sin(alpha)),\n", - " (0, np.sin(alpha), np.cos(alpha))]\n", - "\n", - " M_ROT2=[(np.cos(beta), 0, np.sin(beta)), # rotational matrix along the y-direction\n", - " (0, 1, 0 ),\n", - " (-np.sin(beta), 0, np.cos(beta))]\n", - " matrix_l=np.array(M_ROT1).dot(M_ROT2)\n", - " for j in range(0,len(Z)):\n", - " pro.append(matrix_l.dot(Z[j])) \n", - " Z_new=np.array(pro)\n", - " x_real=Z_new[:,0]\n", - " y_real=Z_new[:,1]\n", - " z_real=Z_new[:,2]\n", - " ######################################################################### \n", - " xy=((x_real)**2+(y_real)**2)**(1/2)\n", - " circle=xy<=r\n", - " for m in range(0,len(u_ele_real)):\n", - " e=label_real[circle]==u_ele_real[m] #seperation of the atoms \n", - " z_circle=z_real[circle]\n", - " x_circle=x_real[circle]\n", - " y_circle=y_real[circle]\n", - " c_circle=c_real[circle]\n", - " z_p=z_circle[e] \n", - " x_p=x_circle[e] \n", - " y_p=y_circle[e]\n", - " c_p=c_circle[e]\n", - " init_plot=z_p>=z_start\n", - " x_pl2=x_p[init_plot]\n", - " y_pl2=y_p[init_plot]\n", - " z_pl2=z_p[init_plot]\n", - " c_pl2=c_p[init_plot]\n", - " init_plot2=z_pl2<=z_end \n", - " x_pl3=x_pl2[init_plot2]\n", - " y_pl3=y_pl2[init_plot2]\n", - " z_pl3=z_pl2[init_plot2]\n", - " c_pl3=c_pl2[init_plot2]\n", - " if atom=='all':\n", - " c_pl3[c_pl3=='#FFFFFF']='#000000'\n", - " x_N=len(x_real[::N_plot])\n", - " self.ax2.scatter3D(x_pl3[::N_plot],y_pl3[::N_plot],z_pl3[::N_plot],c=c_pl3[::N_plot],label=u_ele_real[m],s=S)\n", - " self.ax2.set_title('number of atoms={:.0f}, 1/{:.0f} of all atoms'.format(x_N,N_plot)) \n", - " elif u_ele_real[m]==atom:\n", - " c_pl3[c_pl3=='#FFFFFF']='#000000'\n", - " x_N=len(x_pl3[::N_plot])\n", - " self.ax2.scatter3D(x_pl3[::N_plot],y_pl3[::N_plot],z_pl3[::N_plot],c=c_pl3[::N_plot],label=u_ele_real[m],s=S)\n", - " self.ax2.set_title('number of atoms={:.0f}, 1/{:.0f} of all atoms of this type'.format(x_N,N_plot)) \n", - " if self.var_axis.get()==0: \n", - " #self.ax2.set_title('number of atoms=%i' %x_N)\n", - " self.ax2.set_xlabel('X-axis',fontweight='bold')\n", - " self.ax2.set_ylabel('Y-axis',fontweight='bold')\n", - " self.ax2.set_zlabel('Z-axis',fontweight='bold')\n", - " elif self.var_axis.get()==1:\n", - " self.ax2.set_axis_off() \n", - " apt.set_axes_equal(self.ax2) \n", - " self.ax2.legend() \n", - " if self.var_cyl.get()==1:\n", - " x_pos = float(self.cly_x.get())\n", - " y_pos = float(self.cly_y.get())\n", - " z_pos = float(self.cly_z.get())\n", - " height = float(self.height.get())\n", - " alpha = float(self.alpha.get())\n", - " beta = float(self.beta.get())\n", - " r = float(self.radius.get())\n", - " color_zyl=str(self.cb_color.get())\n", - " theta=np.linspace(0,2*np.pi,201)\n", - " z_start=-height/2\n", - " z_end=height/2\n", - " alpha=math.radians(alpha)\n", - " beta=math.radians(beta)\n", - " \n", - " y_circle=r*np.cos(theta)\n", - " x_circle=r*np.sin(theta)\n", - " z_circle_s=np.ones(201)*z_start\n", - " z_circle_e=np.ones(201)*z_end\n", - "\n", - " x_line1=np.ones(201)*r\n", - " x_line2=np.ones(201)*-r\n", - " y_line=np.ones(201)*0\n", - " z_line=np.linspace(z_start,z_end,201)\n", - " lw=5\n", - " self.ax2.plot(x_line1,y_line,z_line,color_zyl,linewidth=lw)\n", - " self.ax2.plot(x_line2,y_line,z_line,color_zyl,linewidth=lw)\n", - " self.ax2.plot(x_circle,y_circle,z_circle_s,color_zyl,linewidth=lw) \n", - " self.ax2.plot(x_circle,y_circle,z_circle_e,color_zyl,linewidth=lw) \n", - " self.canvas.draw_idle() \n", - "\n", - "tk = Tk() # tkinter\n", - "tk.title(\"APT analyzer\") # select title of tool\n", - "tk.geometry('1100x700') # select size of tool\n", - "tt=APT_analyzer(tk) # select tool \n", - "tk.mainloop() # start tool" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2b4bf9ed-bc19-456f-99b6-c0cfe5691b57", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docker/apmtools/Cheatsheet.ipynb b/docker/apmtools/Cheatsheet.ipynb index cd86404..0ce2bf2 100644 --- a/docker/apmtools/Cheatsheet.ipynb +++ b/docker/apmtools/Cheatsheet.ipynb @@ -5,7 +5,7 @@ "id": "88054eb2", "metadata": {}, "source": [ - "" + "<img src=\"NOMADOasisLogo.png\" alt=\"Drawing\" style=\"height: 149px;\"/>  <img src=\"FAIRmatNewLogo.png\" alt=\"Drawing\" style=\"height: 149px;\"/>" ] }, { @@ -21,17 +21,44 @@ "id": "4091c1e2", "metadata": {}, "source": [ - "The apmtools container offers functionalities to work, i.e. perform data analyses<br>\n", + "The apmtools container offers functionalities to work and perform data analyses<br>\n", "with reconstructed and ranged datasets from atom probe microscopy experiments.<br>\n", "\n", - "This container includes two tools for processing of atom probe data:\n", - "* The **Leoben APT_analyzer** by Alexander Reichmann et al.\n", - "* The **paraprobe-toolbox** by Markus Kühbach et al.\n", + "This container includes three tools:\n", + "* **APTyzer** by Alexander Reichmann et al. https://github.com/areichm/APTyzer\n", + "* **paraprobe-toolbox** by Markus Kühbach et al. https://gitlab.com/paraprobe/paraprobe-toolbox\n", + "* **APAV** by Jesse Smith et al. https://gitlab.com/jesseds/apav\n", "\n", - "Each tool comes with a tutorial-style example.\n", + "Each tool comes shipped with tutorial-style examples.\n", "***" ] }, + { + "cell_type": "markdown", + "id": "e7afeb96-16a6-4d5a-9986-7aacfc9980f4", + "metadata": { + "tags": [] + }, + "source": [ + "<div class=\"alert alert-block alert-info\">\n", + "Having APTyzer, APAV, and paraprobe-toolbox configured in one container comes with the benefit<br>\n", + "that one can switch between using these tools or couple them together via an e.g. jupyter notebook.\n", + "</div>" + ] + }, + { + "cell_type": "markdown", + "id": "fd044275-1ce9-4fea-bbe8-9903c3620a18", + "metadata": { + "tags": [] + }, + "source": [ + "<div class=\"alert alert-block alert-info\">\n", + "Containers of the Nomad Remote Tools Hub (NORTH) are configured such that they have access to the data in the uploads section of the Oasis instance.<br>\n", + "Individual examples which exemplify how to use the tools in the apmtools container may have to be unpacked/decompressed before their first use.\n", + "</div>" + ] + }, { "cell_type": "markdown", "id": "69238a64", @@ -39,29 +66,31 @@ "tags": [] }, "source": [ - "# Getting started with the Leoben APT_analyzer" + "# Getting started with APTyzer" ] }, { "cell_type": "markdown", - "id": "6e324fa1", + "id": "4aa12615-4734-4802-b6b9-407d6f1797bd", "metadata": {}, "source": [ - "The Leoben APT_analyzer is a Python-based tool with a GUI which can be used to display tomographic<br>\n", - "reconstructions for performing composition and interfacial excesses analyses. The tool is maintained<br>\n", - "by Alexander Reichmann at the Montanuniversität Leoben.<br>\n", - "\n", - "First you should navigate into the respective directory of the tool.<br>" + "<div class=\"alert alert-block alert-info\">\n", + "Computational requirements: Examples with dataset with a few million ions like most used below should be processable even on a computer with a single core and say at least 4GB main memory.\n", + "</div>" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "5fd98af7", + "cell_type": "markdown", + "id": "6e324fa1", "metadata": {}, - "outputs": [], "source": [ - "! cd /home/atom_probe_tools/leoben_apt_analyzer" + "APTyzer is a Python-based tool with a graphical user interface (GUI) which can be used for displaying of a tomographically<br>\n", + "reconstructed atom probe dataset thus enabling composition and interfacial excesses analyses. APTyzer can also be used for<br>\n", + "preparing the meshing of single interface patches (grain or phase boundaries) via setting control points manually.<br>\n", + "These points can be exported and loaded with the paraprobe-toolbox. APTyzer is maintained by Alexander Reichmann,<br>\n", + "who is a PhD student with Lorenz Romaner at the Montanuniversität Leoben, Austria.<br>\n", + "\n", + "To use the tool you should navigate into the respective sub-directory.<br>" ] }, { @@ -69,7 +98,17 @@ "id": "1aa22278", "metadata": {}, "source": [ - "### Step 1: Enter the leoben_apt_analyzer sub-directory via the explorer panel on the left side" + "### Step 1: Navigate into the respective sub-directory via the explorer panel on the left side" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fd98af7", + "metadata": {}, + "outputs": [], + "source": [ + "! cd /home/atom_probe_tools/aptyzer" ] }, { @@ -77,7 +116,7 @@ "id": "cc1408cc-45ec-40c6-90cb-dcec5f900619", "metadata": {}, "source": [ - "### Step 2: Download an example dataset to get started with the leoben_apt_analyzer" + "### Step 2: Download and unpack an example dataset to get started" ] }, { @@ -87,7 +126,23 @@ "metadata": {}, "outputs": [], "source": [ - "! cd /home/atom_probe_tools/leoben_apt_analyzer && wget https://www.zenodo.org/record/6794809/files/aut_leoben_leitner.tar.gz && tar -xvf aut_leoben_leitner.tar.gz" + "! cd /home/atom_probe_tools/aptyzer && wget https://www.zenodo.org/record/6794809/files/aut_leoben_leitner.tar.gz && tar -xvf aut_leoben_leitner.tar.gz" + ] + }, + { + "cell_type": "markdown", + "id": "1d7b6c36-5967-46be-835d-3678c4ef8865", + "metadata": {}, + "source": [ + "### Step 3: Start APTyzer by executing the APT_analyzer.ipynb in a new browser tab" + ] + }, + { + "cell_type": "markdown", + "id": "65cc8b4d-3f83-49fd-94f8-4309852499de", + "metadata": {}, + "source": [ + "Open the aptyzer.ipynb via the JupyterLab side bar (on the left side). Clicking through this notebook will start the graphical user interface." ] }, { @@ -95,7 +150,6 @@ "id": "8cea07f1-ccc5-40f4-881a-f9fec8a3f206", "metadata": {}, "source": [ - "### Step 3: Start the apt_analyzer by executing the leoben_apt_analyzer.ipynb in a new browser tab\n", "### Step 4: Run the tool using the POS and RRNG with the aut_leoben_leitner example dataset." ] }, @@ -104,13 +158,13 @@ "id": "051121d5-f318-42a7-81d8-a11349401e4c", "metadata": {}, "source": [ - "There is a detailed tutorial (cheatsheet.pdf) for the APT_analyzer in the sub-directory of the tool.<br>\n", - "\n", - "The APT_analyzer enables to export manually selected control points to support building computational<br>\n", - "geometry models of interfaces. These control points can be exported to an HDF5 file and this file be<br>\n", - "used as input for the paraprobe-toolbox. Specifically, the control points can be used to instruct<br>\n", - "paraprobe-nanochem to attempt an automatic modelling of an interface.\n", - "\n", + "There is a detailed tutorial (tutorial.pdf) how to use APTyzer for arbitary datasets. The tool offers export functionalities<br>\n", + "for manually selected control points to support building computational geometry models of interfaces.<br>\n", + "These control points can be exported to an HDF5 file and this file can be used for example as input for the<br>\n", + "paraprobe-nanochem tool from the paraprobe-toolbox.<br>\n", + "A respective tutorial how to achieve this is available in the paraprobe-toolbox teaching material<br>\n", + "and here specifically the aut_leoben_leitner example. This tutorial will show how to use the<br>\n", + "control points and create an automatically meshed model for an interface in the dataset.<br>\n", "***" ] }, @@ -124,20 +178,42 @@ "# Getting started with paraprobe-toolbox" ] }, + { + "cell_type": "markdown", + "id": "144832aa-b357-406c-84ad-d410d27bd8c0", + "metadata": { + "tags": [] + }, + "source": [ + "<div class=\"alert alert-block alert-info\">\n", + "Computational requirements: Examples with dataset with a few million ions like most used below should be processable even on a computer with a single core<br>\n", + "and four GB main memory. Having multiple CPU cores can be useful as the tools of the paraprobe-toolbox use multi-threading for most of the<br>\n", + "numerical and geometrical analyses.<br>\n", + "Making guarantees about the maximum data set sizes (in terms of number of ions) is difficult as it strongly depends on which type of analyses are performed<br>\n", + "and how these are parameterized. Noteworthy to mention is that even the largest examples at the time of writing this cheatsheet which are available in the<br>\n", + "paraprobe-toolbox were processable with a laptop with 32GB main memory in total. Examples with a few million ions consumed not more than one to eight GB.<br>\n", + "</div>" + ] + }, { "cell_type": "markdown", "id": "ec70f493", "metadata": {}, "source": [ - "The paraprobe-toolbox is a collection of software tools for applying computational geometry tasks on tomographic<br>\n", - "reconstructions of atom probe data to extract and characterize microstructural features. The tool is developed<br>\n", - "by <a href=\"https://arxiv.org/abs/2205.13510\">Markus Kühbach et al.</a>. The tool is instructed via a jupyter notebook which documents a script of analysis steps.<br>\n", + "The paraprobe-toolbox is a collection of software tools for applying computational geometry tasks on point cloud data<br>\n", + "such as tomographic reconstructions of atom probe data to extract and characterize microstructural features.<br>\n", + "The tool is developed by <a href=\"https://arxiv.org/abs/2205.13510\">Markus Kühbach et al.</a>. The tool is instructed via a jupyter notebook which documents<br>\n", + "how to chain via scripting different analysis steps. Apart from this, it is possible to use the tools via<br>\n", + "Python scripting to perform e.g. batch processing on computer clusters.<br>\n", "\n", - "During the workflow atom probe data are processable with multiple computational steps.<br>\n", - "Each step uses a different scientific speciality tool. These use CPU parallelization and performance libraries.<br>\n", - "The jupyter notebooks enable users to script the steps of the workflow to automate many steps of the<br>\n", - "data analysis in a reproducible and documented way including iso-surface computations, parameter<br>\n", - "studies, mesh processing, and writing reports or creating plots automatically.<br>" + "Tools of the paraprobe-toolbox are chained into computational workflows. Each step uses a different scientific<br>\n", + "speciality tool. All these tools have *paraprobe* as a prefix in their executable name.<br>\n", + "The tools use CPU parallelization and specific libraries of the computational geometry or other specialists'<br>\n", + "communities. The jupyter notebooks enable users to achieve a complete automation of their<br>\n", + "data analyses (if this is desired). Internally, each tools keeps track of input and output files via<br>\n", + "hashes and time stamps to enable provenance tracking and support repeatable and reproducible research.<br>\n", + "All results are openly accessible and documented via so-called <a href=\"https://fairmat-experimental.github.io/nexus-fairmat-proposal\">NeXus application definitions (see the NORTH/apmtools pages).</a><br>\n", + "Such workflows can include parameter studies, mesh processing, writing of reports, and creation of plots.<br>" ] }, { @@ -147,14 +223,16 @@ "tags": [] }, "source": [ - "### Step 1: Enter the paraprobe-toolbox/teaching/example_analyses sub-directory using the jupyter explorer panel to the left." + "### Step 1: Enter the paraprobe-toolbox/teaching/example_analyses sub-directory via the explorer panel to the left." ] }, { "cell_type": "code", "execution_count": null, "id": "f6461c64-cbcd-4d0a-8be3-850205382a71", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "# analysis results are stored here:\n", @@ -170,13 +248,11 @@ "id": "bce1299f-24fb-4ffc-99d3-99aac8921136", "metadata": {}, "source": [ - "Reconstruction and ranging data (i.e. POS, ePOS, APT, RNG, RRNG, NXS) to be consumed by paraprobe should be stored in *teaching/example_data*<br>\n", - "Analyses with relevant IPYNB jupyter notebooks should be stored in *teaching/example_analyses*\n", + "Reconstruction and ranging data (i.e. POS, ePOS, APT, RNG, RRNG, NXS) to be consumed by paraprobe-toolbox should be stored in *teaching/example_data*.<br>\n", + "Analyses with relevant jupyter notebooks should be stored in *teaching/example_analyses*.\n", "\n", - "**Using NeXus:** the tools have been modified to enable now reading also data from NeXus/HDF5 files\n", - "which have been created<br>\n", - "with the NXapm application definition (see <a href=\"https://fairmat-experimental.github.io/nexus-fairmat-proposal\">nexus-fairmat-proposal</a> and here specifically<br>\n", - "the section about atom probe for more details)." + "The tools use NeXus data schemas and HDF5 files. The specification of these files is documented in the so-called<br>\n", + "<a href=\"https://fairmat-experimental.github.io/nexus-fairmat-proposal\">nexus-fairmat-proposal</a> (see specifically the NORTH/apmtools pages)." ] }, { @@ -184,15 +260,7 @@ "id": "670cc82a-2ea2-4a48-bded-793b15e2f58f", "metadata": {}, "source": [ - "### Step 2: Unpack one of the example datasets or use a NeXus/HDF5 file from your upload section." - ] - }, - { - "cell_type": "markdown", - "id": "31c88293-1cc5-48dd-9f3a-ada16de9ac65", - "metadata": {}, - "source": [ - "Individual examples have to be unpacked/decompressed before you can use them.<br>" + "### Step 2: Unpack the example datasets or use a NeXus/HDF5 file from your uploads section." ] }, { @@ -202,7 +270,8 @@ "metadata": {}, "outputs": [], "source": [ - "! cd /home/atom_probe_tools/paraprobe-toolbox/teaching/example_data/usa_portland_wang && tar -xvf usa_portland_wang.tar.gz" + "! cd /home/atom_probe_tools/paraprobe-toolbox/teaching/example_data/usa_portland_wang && tar -xvf usa_portland_wang.tar.gz\n", + "! cd /home/atom_probe_tools/paraprobe-toolbox/teaching/example_data/aut_leoben_leitner && tar -xvf aut_leoben_leitner.tar.gz" ] }, { @@ -210,15 +279,15 @@ "id": "c409e217-e14a-4bba-a07e-e16753288bd8", "metadata": {}, "source": [ - "Alternatively, **If you have already a NeXus/HDF5 file in your upload section you can also use this file.**<br>\n", - "In order to do so you can move that file into one of the example_data analysis section inside<br>\n", - "the container. This works because the container is configured such that the directory which<br>\n", - "represents the upload section is mounted into the container and accessible via the config section.<br>" + "Alternatively, NeXus/HDF5 files in your uploads section can also be used. In order to do so, you can move<br>\n", + "the respective file(s) into one of the example_data analysis sections inside the container.<br>\n", + "This works because the container is configured such that the directory which represents<br>\n", + "the uploads section is mounted into the apmtools container and is accessible via config directory.<br>" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "22c54c6b-fe0d-4e47-9247-4d5f42761134", "metadata": {}, "outputs": [], @@ -231,7 +300,7 @@ "id": "4fa357bd-7e4a-4c78-afeb-cc4debe34f48", "metadata": {}, "source": [ - "### Step 3: Explore the usage of the tool via jupyter notebooks. Start with the unpacked example usa_portland_wang." + "### Step 3: Start with the unpacked example usa_portland_wang." ] }, { @@ -239,8 +308,15 @@ "id": "8c8865e7-70e9-4309-93c3-5eaf73e2d2e0", "metadata": {}, "source": [ - "Each example comes with a detailed jupyter notebook which guides through the analysis. This notebook documents the workflow<br>\n", - "and instructs it by orchestrating the interaction between python tools, C/C++ tools, HDF5, and H5Web for visualization." + "Each example of the paraprobe-toolbox comes with a detailed jupyter notebook which guides through the analysis.<br>\n", + "Especially the usa_portland_wang and the aut_leoben_leitner (see connection to APTyzer tool) examples<br>\n", + "show the multi-faceted analysis features of the tools in the paraprobe-toolbox.<br>\n", + "These examples show how Python and C/C++ applications work together with NeXus, HDF5, and H5Web<br>\n", + "to provide tools which can be flexibly be coupled to other open-source atom probe software and<br>\n", + "used to complement analyses which have been achieved with commercial software like APSuite/IVAS.<br>\n", + "More complex examples with customizable jupyter notebooks are available in the documentation<br>\n", + "of the tool https://paraprobe-toolbox.readthedocs.io/en/latest/.<br>\n", + "***" ] }, { @@ -248,11 +324,133 @@ "id": "80d09ac1-e56d-4923-9ba3-9e869d368a51", "metadata": {}, "source": [ - "The tool is described in further details in various places https://arxiv.org/abs/2205.13510 and<br>\n", - "https://gitlab.com/paraprobe/paraprobe-toolbox <br>\n", - "The notebooks exemplify how individual tools of the toolbox interact.\n", + "# Getting started with APAV" + ] + }, + { + "cell_type": "markdown", + "id": "bfec8daa-59c3-47b9-a0af-8f03afd5c9a6", + "metadata": {}, + "source": [ + "<div class=\"alert alert-block alert-info\">\n", + "Computational requirements: Examples with dataset with a few million ions like most used below should be processable even on a computer with a single core and say at least 4GB main memory.<br>\n", + "Having multiple CPU cores can be useful as APAV uses multi-threading for some costly numerical analyses.\n", + "</div>" + ] + }, + { + "cell_type": "markdown", + "id": "c10fcef1-e82f-4400-af22-f6df70687faf", + "metadata": {}, + "source": [ + "APAV (Atom Probe Analysis and Visualization) is a Python package for analysis and visualization of atom probe tomography datasets.<br>\n", + "The tool is developed by <a href=\"https://joss.theoj.org/papers/10.21105/joss.04862\">Jesse Smith et al.</a>. Complementary to the design of the paraprobe-toolbox functionalities,<br>\n", + "APAV can be chained into workflows via e.g. a jupyter notebook. A particular functional strength and focus of APAV<br>\n", + "has been ranging and handling of so-called multi-hit events via a graphical user interface via Python.<br>\n", + "\n", + "APAV has a detailed documentation https://apav.readthedocs.io/en/latest/index.html." + ] + }, + { + "cell_type": "markdown", + "id": "b75f60d5-3151-4986-9222-ddaeb1fa7e5f", + "metadata": { + "tags": [] + }, + "source": [ + "<div class=\"alert alert-block alert-info\">\n", + "Jesse Smith has also made available example data which can be used for learning the multi-hit event analyses capabilities of APAV. If these files are not available <a href=\"https://doi.org/10.5281/zenodo.6794809\">via this Zenodo repository (version >9)</a>,<br>\n", + "users are requested to inspect the respective file share location from the revision of the APAV JOSS paper <a href=\"https://github.com/openjournals/joss-reviews/issues/4862\">see the comment from jdasm from November 30, 2022 here</a><br>\n", + "The respective GBCO dataset relevant here is especially this one R5038_00333-v02.epos. <a href=\"https://doi.org/10.1017/S1431927621012794\">Scientific details can be found here</a>.\n", + "</div>" + ] + }, + { + "cell_type": "markdown", + "id": "f88501b0-a4bd-427d-b303-a45a3556ab35", + "metadata": { + "tags": [] + }, + "source": [ + "### Step 1: Enter the apav sub-directory via the explorer panel to the left." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "474644bb-75fd-4514-bab5-8d9d30e46610", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "! ls /home/atom_probe_tools/apav" + ] + }, + { + "cell_type": "markdown", + "id": "ba59fb6e-2b63-4e95-99f7-75248c2a4f79", + "metadata": {}, + "source": [ + "### Step 2: Use available files in community-specific formats from your uploads section or download the above-mentioned examples from APAV." + ] + }, + { + "cell_type": "markdown", + "id": "f65c621e-05f5-45ca-9a01-4cbd3206bc55", + "metadata": { + "tags": [] + }, + "source": [ + "The APAV documentation details which formats are supported." + ] + }, + { + "cell_type": "markdown", + "id": "9e3fb02c-1706-4864-a27c-06aaadc5c11b", + "metadata": { + "tags": [] + }, + "source": [ + "### Step 3: Start a new jupyter-notebook and use it to compose your own analysis workflow." + ] + }, + { + "cell_type": "markdown", + "id": "57c641c2-748c-4c8a-a2f1-4d9428f141a5", + "metadata": {}, + "source": [ + "The APAV documentation can support you with starting your own analyses.\n", "***" ] + }, + { + "cell_type": "markdown", + "id": "a27bbf75-8a2a-4df3-8096-f942afc5e656", + "metadata": {}, + "source": [ + "# Version, funding, feedback\n", + "* **APTyzer** https://github.com/areichm/APTyzer 887b82f\n", + "* **paraprobe-toolbox** https://gitlab.com/paraprobe/paraprobe-toolbox 78a394dc\n", + "* **APAV** https://pypi.org/project/APAV v1.4.0\n", + "* **NeXus** https://fairmat-experimental.github.io/nexus-fairmat-proposal latest\n", + "\n", + "Last revision: Markus Kühbach, 2023/04/17\n", + "\n", + "<a href=\"https://www.fairmat-nfdi.eu/fairmat/\">FAIRmat</a> is a consortium on research data management which is part of the German NFDI.<br>\n", + "The project is funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) – project 460197019.\n", + "\n", + "If you spot issues with this container or you would like to suggest how we can improve the apmtools container:<br>\n", + "Please contact the respective maintainer of this container." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff8b1235-d4c8-4ce0-9013-d1d87b7191df", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -271,7 +469,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.9.16" } }, "nbformat": 4, diff --git a/docker/apmtools/Dockerfile b/docker/apmtools/Dockerfile index 559ea7f..b073c41 100644 --- a/docker/apmtools/Dockerfile +++ b/docker/apmtools/Dockerfile @@ -1,7 +1,7 @@ # recipe to create the nomad-remote-tools-hub apmtools container via a dockerfile FROM gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/webtop -# # for testing the container locally without running in oauth errors +# for testing the container locally without running in oauth errors # FROM ghcr.io/linuxserver/webtop:amd64-ubuntu-openbox-version-e1079163 # # found that newest system python is 3.8.something # # rest should come only from and be managed through (mini)conda not pip! @@ -16,67 +16,62 @@ FROM gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/webtop USER root -# query version afterwards like so dpkg -s <packagename> | grep '^Version:' -# OS dependencies, low-level system libraries -RUN apt-get update -RUN apt-get install -y git=1:2.25.1-1ubuntu3.5 \ - && apt-get install -y m4=1.4.18-4 \ - && apt-get install -y file=1:5.38-4 \ - && apt-get install -y wget=1.20.3-1ubuntu2 \ - && apt-get install -y mesa-common-dev=21.2.6-0ubuntu0.1~20.04.2 \ - && apt-get install -y libglu1-mesa-dev=9.0.1-1build1 \ - && apt-get install -y build-essential=12.8ubuntu1.1 \ - && apt-get install -y mpich=3.3.2-2build1 \ - && apt-get install -y libgmp-dev=2:6.2.0+dfsg-4 \ - && apt-get install -y libmpfr-dev=4.0.2-1 \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /home \ +RUN mkdir -p /home \ && mkdir -p /home/atom_probe_tools \ - && wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh \ - && mv Miniconda3-py39_4.12.0-Linux-x86_64.sh miniconda3_py3.9_4.12.0_linux_x86_64.sh \ - && chmod +x miniconda3_py3.9_4.12.0_linux_x86_64.sh \ - && bash ./miniconda3_py3.9_4.12.0_linux_x86_64.sh -b -p /usr/local/miniconda3 \ - && rm -f miniconda3_py3.9_4.12.0_linux_x86_64.sh + && mkdir -p /home/atom_probe_tools/apav \ + && mkdir -p /home/atom_probe_tools/aptyzer -# future improvements should use --no-install-recommends to safe space further, but for mpich this created problems when building paraprobe +COPY Cheatsheet.ipynb FAIRmatNewLogo.png NOMADOasisLogo.png /home/atom_probe_tools/ +ENV PATH=/usr/local/miniconda3/bin:/home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/cmake/cmake-3.26.3/localinstallation/bin:$PATH -COPY Cheatsheet.ipynb FAIRmat_S.png APT_analyzer.ipynb /home/atom_probe_tools/ - -# add all relevant environment variables that required for building the -# container make sure to reflect eventual changes of the cmake version -ENV PATH=/usr/local/miniconda3/bin:/home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/cmake/cmake-3.22.2/localinstallation/bin:$PATH +# query version afterwards like so dpkg -s <packagename> | grep '^Version:' +# OS dependencies, low-level system libraries, --fix-missing, rm -rf /var/lib/apt/lists/* +# future improvements should use --no-install-recommends to safe space further, but for mpich this created problems when building paraprobe-toolbox -RUN mkdir -p /home \ +RUN cd ~ \ + && apt update \ + && apt install -y curl=7.68.0-1ubuntu2.18 \ + && curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh \ + && chmod +x nodesource_setup.sh \ + && ./nodesource_setup.sh \ + && apt install -y nodejs=18.16.0-deb-1nodesource1 \ + && apt install -y m4=1.4.18-4 file=1:5.38-4 git=1:2.25.1-1ubuntu3.10 wget=1.20.3-1ubuntu2 mesa-common-dev=21.2.6-0ubuntu0.1~20.04.2 libglu1-mesa-dev=9.0.1-1build1 build-essential=12.8ubuntu1.1 mpich=3.3.2-2build1 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libmpfr-dev=4.0.2-1 libssl-dev=1.1.1f-1ubuntu2.17 hwloc=2.1.0+dfsg-4 \ + && wget https://repo.anaconda.com/miniconda/Miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ + && mv Miniconda3-py38_23.1.0-1-Linux-x86_64.sh miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ + && chmod +x miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ + && bash ./miniconda3-py38_23.1.0-1-Linux-x86_64.sh -b -p /usr/local/miniconda3 \ + && rm -f miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ && cd /home \ && conda config --add channels conda-forge \ && conda config --set channel_priority strict \ - && conda install python ipykernel=6.15.3 gitpython=3.1.27 nomkl=1.0 pandas=1.4.4 ase=3.22.1 radioactivedecay=0.4.15 scikit-learn=1.1.2 silx=1.0.0 jupyterlab=3.4.7 nodejs=18.9.0 \ - && pip install jupyterlab_h5web[full]==6.0.1 \ + && conda install -c conda-forge nomkl=1.0 jupyterlab-h5web=7.0.0 jupyterlab=3.6.3 hdbscan=0.8.29 gitpython=3.1.31 nodejs=18.15.0 ase=3.19.0 radioactivedecay=0.4.17 pandas=2.0.0 sphinx=6.1.3 \ && conda clean -afy \ - && mkdir -p /home/atom_probe_tools \ - && mkdir -p /home/atom_probe_tools/leoben_apt_analyzer \ - && git clone https://github.com/areichm/APT_analyzer.git \ - && cd APT_analyzer \ - && git checkout 6ee93ca6052c221ae335cbaa2a218911024301fa \ - && cp APT_analyzer_tutorial.pdf /home/atom_probe_tools/leoben_apt_analyzer/cheatsheet.pdf \ - && cp /home/atom_probe_tools/APT_analyzer.ipynb /home/atom_probe_tools/leoben_apt_analyzer/leoben_apt_analyzer.ipynb \ - && cp LICENSE /home/atom_probe_tools/leoben_apt_analyzer/LICENSE \ - && cp README.md /home/atom_probe_tools/leoben_apt_analyzer/README.md \ - && cp apt_importers.py /home/atom_probe_tools/leoben_apt_analyzer/apt_importers.py \ - && cd .. \ - && rm -rf APT_analyzer \ - && rm /home/atom_probe_tools/APT_analyzer.ipynb \ + && cd /home \ + && python3 -m pip install --upgrade pip \ + && python3 -m pip install ecdf==0.7.0 pytictoc==1.5.2 ifes-apt-tc-data-modeling==0.0.6 python-docs-theme==2023.3.1 \ + && cd /home \ + && cd /home/atom_probe_tools/apav \ + && python3 -m pip install apav==1.4.0 \ + && cd /home/atom_probe_tools \ + && git clone https://github.com/areichm/APTyzer.git \ + && cd APTyzer \ + && git checkout 887b82f \ + && cp APTyzer_V_1_2o.ipynb /home/atom_probe_tools/aptyzer/aptyzer.ipynb \ + && cp LICENSE /home/atom_probe_tools/aptyzer/LICENSE \ + && cp README.md /home/atom_probe_tools/aptyzer/README.md \ + && cp Tutorial_APTyzer_V1_2.pdf /home/atom_probe_tools/aptyzer/tutorial.pdf \ + && rm -rf /home/atom_probe_tools/APTyzer \ && cd /home/atom_probe_tools \ && git clone https://gitlab.com/paraprobe/paraprobe-toolbox.git \ && cd paraprobe-toolbox \ - && git checkout b95be8c9e79f044fd8716ffe5d2b7adf4e14208d \ + && git checkout 78a394dc \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p cmake \ && cd cmake \ - && wget https://cmake.org/files/v3.22/cmake-3.22.2.tar.gz \ - && tar -xvf cmake-3.22.2.tar.gz \ - && rm -rf cmake-3.22.2.tar.gz \ - && cd cmake-3.22.2 \ + && wget https://cmake.org/files/v3.26/cmake-3.26.3.tar.gz \ + && tar -xvf cmake-3.26.3.tar.gz \ + && rm -rf cmake-3.26.3.tar.gz \ + && cd cmake-3.26.3 \ && mkdir -p localinstallation \ && chmod +x bootstrap \ && ./bootstrap --prefix="$PWD/localinstallation/" -- -DCMAKE_USE_OPENSSL=OFF 2>&1 | tee PARAPROBE.CMake.Bootstrap.STDOUTERR.txt \ @@ -85,37 +80,18 @@ RUN mkdir -p /home \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p hdf5 \ && cd hdf5 \ - && wget https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.13/hdf5-1.13.2/src/CMake-hdf5-1.13.2.tar.gz \ - && gunzip CMake-hdf5-1.13.2.tar.gz \ - && tar -xvf CMake-hdf5-1.13.2.tar \ - && rm -rf CMake-hdf5-1.13.2.tar \ - && cd CMake-hdf5-1.13.2 \ + && wget https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.14/hdf5-1.14.0/src/CMake-hdf5-1.14.0.tar.gz \ + && tar -xvf CMake-hdf5-1.14.0.tar.gz \ + && rm -rf CMake-hdf5-1.14.0.tar.gz \ + && cd CMake-hdf5-1.14.0 \ && ./build-unix.sh 2>&1 | tee PARAPROBE.Hdf5.Build.STDOUTERR.txt \ - && ./HDF5-1.13.2-Linux.sh --include-subdir --skip-license 2>&1 | tee PARAPROBE.Hdf5.Install.STDOUTERR.txt \ + && ./HDF5-1.14.0-Linux.sh --include-subdir --skip-license 2>&1 | tee PARAPROBE.Hdf5.Install.STDOUTERR.txt \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p boost \ && cd boost \ - && wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz \ - && tar -xvf boost_1_80_0.tar.gz \ - && rm -f boost_1_80_0.tar.gz \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ - && mkdir -p fftw \ - && cd fftw \ - && wget https://www.fftw.org/fftw-3.3.10.tar.gz \ - && tar -xvf fftw-3.3.10.tar.gz \ - && rm -f fftw-3.3.10.tar.gz \ - && cd fftw-3.3.10 \ - && mkdir -p localinstallation \ - && ./configure --prefix="$PWD/localinstallation/" --enable-float --enable-sse 2>&1 | tee PARAPROBE.FFTW.Configure.STDOUTERR.txt \ - && make -j4 2>&1 | tee PARAPROBE.FFTW.Make4.STDOUTERR.txt \ - && make check 2>&1 | tee PARAPROBE.FFTW.MakeCheck.STDOUTERR.txt \ - && make install 2>&1 | tee PARAPROBE.FFTW.MakeInstall.STDOUTERR.txt \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ - && mkdir -p tetgen \ - && cd tetgen \ - && tar -xvf tetgen1.6.0.tar.gz \ - && cd tetgen1.6.0 \ - && make -j4 tetlib 2>&1 | tee PARAPROBE.TetGen.Make4.STDOUTERR.txt \ + && wget https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.gz \ + && tar -xvf boost_1_81_0.tar.gz \ + && rm -f boost_1_81_0.tar.gz \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p eigen \ && cd eigen \ @@ -125,80 +101,54 @@ RUN mkdir -p /home \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p cgal \ && cd cgal \ - && wget https://github.com/CGAL/cgal/releases/download/v5.5/CGAL-5.5.tar.xz \ - && tar -xvf CGAL-5.5.tar.xz \ - && rm -f CGAL-5.5.tar.xz \ + && wget https://github.com/CGAL/cgal/releases/download/v5.5.2/CGAL-5.5.2.tar.xz \ + && tar -xvf CGAL-5.5.2.tar.xz \ + && rm -f CGAL-5.5.2.tar.xz \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p voroxx \ && cd voroxx \ && git clone https://github.com/chr1shr/voro.git \ && cd voro \ && git checkout 56d619faf3479313399516ad71c32773c29be859 \ - && cd .. \ - && mv voro voro++-0.4.6 \ && cd /home/atom_probe_tools/paraprobe-toolbox \ - && sed -i 's|set(MYPROJECTPATH "<<YOURPATH>>")|set(MYPROJECTPATH \"'"$PWD"'\/\")|g' /home/atom_probe_tools/paraprobe-toolbox/code/PARAPROBE.Dependencies.cmake \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-utils/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-utils/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-ranger/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-ranger/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && cp paraprobe_ranger /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_ranger \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-surfacer/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-surfacer/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && cp paraprobe_surfacer /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_surfacer \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-distancer/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-distancer/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && cp paraprobe_distancer /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_distancer \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-tessellator/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-tessellator/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && cp paraprobe_tessellator /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_tessellator \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-nanochem/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-nanochem/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && cp paraprobe_nanochem /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_nanochem \ - && mkdir -p /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-intersector/build \ - && cd /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-intersector/build \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx .. \ - && make -j4 \ - && cp paraprobe_intersector /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_intersector \ - && chmod +x /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe_* \ + && sed -i 's|set(MYPROJECTPATH "<<YOURPATH>>/paraprobe-toolbox")|set(MYPROJECTPATH \"'"$PWD"'\/\")|g' code/PARAPROBE.Dependencies.cmake \ && cd /home/atom_probe_tools/paraprobe-toolbox \ - && rm -f PARAPROBE.Step*.sh \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-clusterer \ + && chmod +x PARAPROBE.Step05.Build.Tools.sh \ + && ./PARAPROBE.Step05.Build.Tools.sh \ + && chmod +x PARAPROBE.Step06.Install.Tools.sh \ + && ./PARAPROBE.Step06.Install.Tools.sh \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/boost \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/cgal \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/chrono \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/eigen \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/hdf5 \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/hornus2017 \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/lewiner2002 \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory/voroxx \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-utils/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-ranger/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-selector/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-surfacer/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-distancer/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-tessellator/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-spatstat/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-nanochem/build \ + && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-intersector/build \ && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-crystalstructure \ && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-miscellaneous \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/paraprobe-spatstat \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/ \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/teaching/example_analyses/aus_sydney_rielli_primig \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/teaching/example_analyses/aut_leoben_mendez_martin \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/teaching/example_analyses/ger_berlin_kuehbach_fairmat \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/teaching/example_data/aus_sydney_rielli_primig \ - && rm -rf /home/atom_probe_tools/paraprobe-toolbox/teaching/example_data/aut_leoben_mendez_martin \ && chown -R ${PUID}:${PGID} /home \ && chown -R ${PUID}:${PGID} /usr/local/miniconda3 +# all rm -rf statements should only be used during container build when we do not like +# to have a version which can be recompiled inside the container # as the entire C/C++ executables for all thirdparty dependencies are linked -# statically the entire thirdparty/mandatory can be deleted +# statically the entire thirdparty/mandatory is deleted during the container build +# which freezes the state of each paraprobe tool but has the disadvantage that the +# tools can then not be recompiled from inside the container COPY 02-exec-cmd /config/custom-cont-init.d/02-exec-cmd ENV HOME=/home/atom_probe_tools WORKDIR $HOME -# further optimization for the future could be to copy the content from conda bin and lib over like done in -# https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/-/blob/develop/docker/nionswift/Dockerfile -# but there might be more low level dependencies from h5web so this is currently not used - -# for running this container standalone e.g. -# docker run -p 3000:8888 <<imagename>> \ No newline at end of file +# for running this container standalone e.g. docker run -p 3000:8888 <<imagename>> \ No newline at end of file diff --git a/docker/apmtools/FAIRmatNewLogo.png b/docker/apmtools/FAIRmatNewLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..aba8697486c07a914800f7519a978306e12d5637 GIT binary patch literal 24588 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 Hu0mjf!SIX5 literal 0 HcmV?d00001 diff --git a/docker/apmtools/FAIRmat_S.png b/docker/apmtools/FAIRmat_S.png deleted file mode 100644 index 43c597e6f09d0850fd1b7c646a481d7c8f0ef3f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6890 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 diff --git a/docker/apmtools/NOMADOasisLogo.png b/docker/apmtools/NOMADOasisLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..b5f82fd462e34ef25e8e93ba6aa834a212d001dc GIT binary patch literal 12991 zcmV;wGC<9VP)<h;3K|Lk000e1NJLTq00Gkg005N;1^@s61Bei400009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsHGE_-KK~#7F?VaCp zRL8yVf8A?@k>FG&PmmcLCqQz_;t4$5?HGASJb{O*7)Wjq7luRSg5hGv2Yi8ilNh{D zA)WwH$Uggy;RzU5Il!sdndK+2QpX4kTC4Z(*K0<M{?F>wy}El%f2xF#1e%$iwfgtB zzdr^RI6B{NB(GlAYl1fzgZl9MDHpN)ORW|^TW-b>*qcwzH{|CusRI|dj<r~RedY5Q z0)apvP#O#j(`fbUGc(D&05;1Qe-9tAf#0r=^`8aw1cO+l$^8V<-ao$jRSzbS&O#lg z*IFQ03z+x?FgA<dZS4J@#45hMJxJke5Gj4`!JogF%=Pmeaz6R1q(%1-aQ+J>U=}hb zbo4%2#rtKI?iWbYukq*cx=6Ycd(Q*{C1b>xk^wz<7%t#1Z_x*6LI~U_M0#JOeDT?T zRXlRd4Cn<koGs9`hO~YRx{xGy;mUt@!7+}V`fdRj|2^a+#cSgKefjnx6yWIGKk!<u zU4@*Wqj&e&SHD~W&B*7I@_9z<!S-{Uc8Bg6ya&4}Tfh5k<()2Q=IHz1wi%B;1WgD? zA3a-ny8{LJ)$hKYkD}-T=psozd-{KWxeS^)`fvRPtHB-6gs8z86Ingo?=!*3@gqd~ zy+}k)L}Jy%HCh%m(8zE@(rG|PVYI0*et~hgaBQyMHMNmh<&!_OAc}BPf6>zJ>tvg9 z8-ITruAid_<{CiMbtWy05x2^<6EWVxO=-D^gT+(U0G|J`8E20`Bb)9a4uxTZ_nbqm zee6^}79zQ(^Pb_HH9+J2&VW<ar2_@2aoz@<`w)Y>U;tiA7x2#(=;C!ckAD_&EO$cp z`z#R~za3bV?RLlK`t<ECL#OI>nk@U2S<!I2W*Yz+w}*JcJ~U-Ww?dD@d2|Oih(38u z;5%eQZ=8FGhVCJhv4*tCM*I%eS|7sovsYy#>@hmMdmEH-50PQ@;GU_!^L~F8io)4@ z;00Xo^Hj*}xJGvU^Q%*Q>akDzdm&>=DT=a<=vIas#PL)8$Mk%_H*S11TdEP=ij4MI z2c};<hEG0fgQw8-M+ONzgGwUDKIq>p)e(Jj&)jkGo~g041qJx+T)&0kjq}doVC{=L z4kOStMAsBl1Wz15iLx=p4Pb%S;IS<ux(x=i_<Z_D=lZ$pUe>sN?k2tkkHLjC97d{S z*oR%ho}p6nw8?Rdz@`u3JtSZOY`~z6>9vPiB0-%LjIL5BW{gXNCoM1lHGD$F*kn{4 z4VrQrQ}TK+uYC_U05k;dd4KqVIutEC9i@`7x_<64Zf15i(Y<NO)USt8Dmiw?e)p{x z8_Cgs|NcX~SRb+i%|$w($DiWx_}A3@<p}sb&K8NTnC{f6AtKEUJwt?{-|GU>s5%-n z<uayZpvaTPzdF5P$u45_xScepvcV`NL+VyEFv>7WrCO?Q@aU%NX)ZFPJWZFtA!jsV z{H8-2RQ=%Lb*1}bcD`R1u4K#hfc7R+&$FmA;sY)QO=BBVN=eDIu4PYpTm~M@W2aJB z*Uv6wwNd8Bg-*598|S=g?4kGl;NjH8h+i^dNS%W=C|3+B9SZ^lep)w^I@Jz0`aG4L z5ub1^Xd26yk|Cv(6gZgH-G@4+u-vE#eR}>X?({doKia+-R1W-ti=H{%^k_)6A6w9g z^!Ob}l{3}4so>V1`#5lB_Iav0Bfde-vnvL5Ut{XAAsu4hpi)FCL<juEH-lQoa)eVU zYI!(6^5Z^YQDnc%7dJ^#euCVXIT9n7=4=vUMvt&C&MR9t-HOaYMsUxz1HGp)m3e&z z_rPC6I;391*I{LY_{-UOPb1Q;w9zT>=5^o=w%}4dg+-#{ONj)jyKruoWda0aBOMk) zYdoqUUX){Vr9}R9ACHPD!(evi5Ln%xb~UDC<;j!wXam@jVLG5x?}fT{Jr@($1Y-;6 z95`E#+=?qg4pNGZG2K#ZtC;Qh9%H6Q48+&+Yn{Iz>rvJ_eDkJy&DJ6HQBo>;Qc}Ge zFlgJnW_LBF(}%=jRSfA6dOfA4|LBN`FU>~dQo|_wKR)uJxxp172gV{z*Bly71{c#B zH2?mf`ST}V9%(#TInw(3%8^<6n!b-O`V0&;PYV(;e${5`EN@q;={lr7%28)x4yYk* zIDI>P7=W%>TN$YxwRuf=(yl=1zhl4q_K^wE>P?U!L&cnt4Ql)zbQVrPXHjc3WP*W^ zLl-QO-Ae_p12m3Yz?pAWj&%3>X9I!rCHgr2LBEZkuYxG_*!4kYbO-D(>`Lv9^{+`n zP=M2rI#UYy-xD%QBCo2`pzdsQoF%>;%joo-+3n}Y=lipez^o9W#XyVA8I~wcLdk54 zOtZQ}R9^IE0cx-?>Ty$g*G)kNJ&<vjUi0D_v7N5ea4+@NA~^BQO0%2iKRtQSTuLLk zq4b~twmYN8O;!3LA(%p{5<Rb?0lo$*tfy^om?z849$l+XSB|tPvm!tg9w=ZdBel(# zde$hRO5NyLM48^LI8;CNM0U_tx{@~dg7;Eytp+FK<)%^wfFhD7E6o$ESY%$GK4fo0 zW_&y~BVQy0g~*?J#i;F(h(IZm=8GB>LDwr8Rg7j&7cEuTkQUu!OtYx8n(5uGpvP?{ zw@xyA)ptfRBH>GORo#Y=Ln1^QRKd*zJ@wP7hIEvvTEWf9ZtZQXx~UIFgN_-hI9H6b zWtpl~s5AN~eE)Q%xh%6P1+G~euX~d*rKl7p2p$<|M&i8EDe^6}q&%Vr*hp96CRoG> zUDH+T>Y+iVnky(`dqdt&xZM{CL5wb2pa2?{uKWuVO3|s!%z`R(?GFs9vNKt>1HEBP zCn+l3fI(a9CNW7->5#~q`g*<0b7)0scvv@jM0F(42-RGRq<gJSB8j3sqt4tCf`&Rt z_X8c8{3%|%7KTn0I)rLXlb+5vrPnP~YsEn8U9ND&hB2MAsC0-{?<L|FVDe~rGgus* zr?MIoiq-JgA{c-b*qHV+XgQxqKBJKC9nmphd;WYBGEQItWnp{qCK|2Ppr;b4`7C_@ z^g(k`*$Gru(Nbe1&Fjl#dc9Ek4|H(jPt?Ni#raF^ytU?!kV8q03CcA*tP@eq2n<jt zI!(2vQOO{T-(|e-rkG=`g)p+V`=Tf)u`YOb_;*UzGojR-?X~cI5hpOOidGS%G1bs@ z=rej`x5RpNkHYLn1l~mCWwp%<^$~krFbGCRYLOZ44|=1UG}rUkZBraH5stmqf~AFy z3aO$bD1naEKfY>idcG-x`=9`4HE&CJMVvs!`MXdO(wG+NI;;SCWVa?Wvd>_3UmB4J zCTd2zkf|X?xn3&NHp_A01&sC7j6R1ucl~d-x?jGCZ}r*AJ6$Llc|E8J+jqBq<E==F za^y$2J%f5bl$}6PUbb+M=AC3iph0$vQoNHJ*$?0?@C3@j9G&~F1BO^%SHFSRQ$13E zyCLgticCSGZDYz?|6{vEpLp-KU`*(|CRjjq#_CRvl}z=jIYeFy-xqKK8&DG5<fH{} zD4N0&>n)gcB6|)QZj!lGQ%oL}Ngz@Fy<5|Ix}nWjwVPGv7&3+W-!8^r{OgX+Yl00F zh3#MbxU&IKjnaG;zAxYe*2`+X!c@9GZ_{U{4;N^#VB>caRu^III$~37QK8Gxn2-ZY zm1XU6cWNrZ*bkt9&M{;}{`wzpjeeC^1mSg3Oj=Q${EjAd9mm~W1UphiH6%}u$Pm&A zR6KKfunr{|QwWBDV2~Cu2ETxbhw7$w<ONeSLMrFcjHzI$BFZtQtSqCw4^hs})U6%T zxkM0lXyz#c)#gsd*wSu4dqS^6CA(1aQ4#H=;$Pq}9CJ4f2v82Fh(Qd|J;76J>p<tx zKta^q#LQC>MWuMcz9Yr^^VLl^DKX^~LFhsOgkrWqQP}>C>d|f}sv(tmRAkcN=mLeP zicszP@<i;tBpP554sa22V&bR_OA;p{KQBo0H3bconDR;p7VVN|5&sz|pdxIK^!AgI zoHR8gAHc{|{2T(HJie(m>0P`dSk7{&o=3AWVPZ#EIkg=$Le50ag0pU}e-BJLy?HyP zMXmgLscLhlNcTU3TR;uTJK_5cI8$<H$|6LjLzp`ojJI&GezB{lrO*bWQ!Y<%<(gzU zorj9u`@%p?H}7b%VG~Wx^l8t=fNVCUcD7USbl){8poU~uXIYUVXSd6G?>Pj9sBK~* zX+XdkvLV{(XOI^GZO{ZuqZkx1E&E0tu*z8&#$(V-?$Kf!O*G?62#yo!nLtI@9&!jv zsH!0e9(kvRq<nT;m_4^WN(};migBREJFgP>P}yh{ef$Fuzgh<+nBcT&biojUCk+M> zs9;J^LNK88{MZ{jdzCH#=i^HVjuGkUMp@V%Q@6**6%-L^T_qkG3g7Q~^u|FF2D2E6 z=SHydCIkgz8mJtU0y{p}r|8s5EmS`KsegX2R6@zpn2TTv5)fdZgsGz?EKR$RBU)lS z-)Jox5`vk`p@P#QC<SkPCh}1Dz6+j!hEy$@PDN?8a&Bw3pL3|D1DJYBFJO)XH6S5} z80?{R6Ps@kQC6xlwkaJr5ao>=$6g;>BiEFK?MuWbH6&S@P(6Og-%Q;xO{kd;QJv^6 zcsK?z#39E_Dy9^f-&v5nU8<v)Qf4~({<m#q=}>0Qm=4}6Gr!;3$The1&htk1Ho;V_ z^8HX|7dIKx(jLrZ(1TS|*O)pGLtuzgj>VG)%_U|jl@$r~`^uDVFk_K&`q1<=o4h00 z#>K|SwPSPN&fBWj=fDf$`^*7kN`{}Z5@mLAb4*6Z5KzypUtB~3B0_XZq^Wa`8M73f z=2iFELVfy>-g@t`5Bdv523qVUx~>b{_`0AUD<jubh3(6Sx4>btbGqsd;9io9Cg5cj zXkCUyTzwrF3u{RHblACdC^A^MFaq))(HVe-Z{gVQzSXiR%S}lXFwP8?Om}|1Rg1cy z8CxUQz>|@dT)3O+mDg+p4~sZ}`${ruhNlPgU&KCAXrhFGuOSdaXVJ+BJ*mMNumCS; z#90*Gc_(YWP-eI(K>^hx^C{j!b2@j$#>h2QVS6h$Dts@e0Tnc)<N%f})WPKgGPi17 z7FaFR_AiX2sQe;LopmU(n<`a~5>?R<71yUYJpA>k@01!aG;K=p)0Ji^MoD!}=Wdi8 znaIZkzl7vn627NCfunQ%tLTM3F6xlrdtZ8aM|SgKI>fl==`w}WK?i2KFzN89^e7+? zB;;VjY1B}&tw)UCJbtQwXWVgdIyWUKvF6NmMyBWl*pLv+qs-(4mWA&rGNy9IvG>1Q zoH-1AyaqZ@5Oip+*?8}zXhYsZ(^aEzI*6K(K0Vh(*SZChO5*kba9ctS6rKKNuHRKs z!x@D^o7bR?Ms-P~$^CVf#{c;0R~y+B+LYK&$t(@3XIvg|FWyax28lYNp(IGW{Z)bv zkDgB0E|ko`*(sEkFPq`HW@9yR0rPmxwqWN6ZvZKUMQVy%yZp}h*@L$`U;$}N>CiM@ z#5X-?=QhIH|A*pHRf^F)o6te)6xm<76@%lDki$S`umL3^YH$%ZjmMw^G^$ujZBIc2 z=p4NHzV1yaqtsLxIXvii^a?v3P)?n9D^l2ALGsQv(}&VFm{Z-VmpMd)3p@Q0^0Da; zd1zXscVQCI)v0)3W@%!N$S}SH9zfT*jhpSsGS%bDuh|v??~5{{(jlti?OQQ@lxHUt zh3$!JxDlT$`hoA6+O=Jg8K;!q%?4fAVMCjjhRvn`7hu9j#kw9Rf>OQL&th~6la?-z z;nL?z;0fB&t~q(K^7aBol2lJ~KMjbfG8+vX@}a3*P*9hR4(rF=Bc(dF%EI=@+K;fP zE6sZRgMRJ|qmRP(+hDM4E4@2p<Fd7S9?u><t%tg~9}${X65(A&!<LXkT7%QBcg{0> zerQNf+G*ZFr-Qoci>{G@v-3iT=AQpR3fn9EUTOF>8ZosDW8L)VKg0CaB75oGA)9<a zW>L%33Jmn7-ah`ERByU4ImCDgErDl{<A|GB=}v$&P?1c>Icrc8%(2)qq(h3HF0e6j zO;y-lw@Qzyk`!k(Uo&dGwHlm^m+eQ@n~dosMW^HunyCq~$#F<1D!uY~3?3qC%*s(2 zs-I#x<svwUJW^nxPP&bPiehC;2+q#;>-e0N>W;e4!YV6C3GnKCEP+nfT~`b#v8_5G z)nX<<!~r+cHkg2-Qvko3P1K0t%Iyw#ij2O5o%cx^tQwqf={`#toO!llx8^iNMC#r3 zHb$=fw62!oHE|ZUfRdCK!uLajfQwJRJkoZ>kP_RBsm)EMEQ}xOoto|dSe>O=0{%zf zgPEsRZbliQM$(|hU=w<LH0(}9qyw@&NO7E>jgf2W!uDM-fNDpyCo@iabiF?PvT5ol zk1{*<%{^IZcA%=G*~l9-?SWHgFJsmV;>JkP+487^cQ?q37Z&>j$Y>lOgKF#841=XL zeEjU_j$<%2qiAE~nyRq9wvz-~V2T1N%;6(4)EGz6DB31BuUFV(IJgg;hj|%xDnc1l zH{Bb<OBs1rf~OCfi#DS}GU#+{W{~!cp&WX2F7e5O<{6jLWTPlH(v&`Ot%$I_spqQP z5sj~-=wJoy2<YMZUVI)w)5U0XlwFM}dCfxDZ#UM7D7#bf(yfI`b@wu{3JE*u>U0=Y zxC7TYdE02*nso4<fiN^Q0PBhP`^mevdANNXR2pe)j9g2EXoCXAYrBdfJP~(5_#RXI z0<$&J;rp_A;_vA2y6W_eQrqi?G%e!zT%XclrRtfQAnR0odkM;>HhmgizaS~O26zuY zZL)ipaTa6lEB7Yza+MXj(a5kIoBQWort%&!9)m^(9p+2N=lio_T`Vw$DZ~QuhcX+3 z8*2o7`J0s^UC<4^{+vdP0o&JEpol&9dounp6HF6|7;>@8*;OWgreGME(I_hu;Ssr` zOuk!urSLxQhZyvgFnTXi5py2JKh~fZFCX#JS!d(?BQ&TT@YtYALu#{A@sslnhy=}) zY=EyMSOdP|f1T7v$gn~JvqGe^Xo&EG^iJ6?#E8<3tSP19U$__cFhYwtZhj(yuB-K> zK%i=jWlRZQ4XSlRI>cv#8VqS55D4ta*v6DNcIux?n0E3<DTBc>rp-=+EBIzmsYXhv z-K?1^5C{YUyO2v#`mHIpVEtU&xZv}2L9cNWRBxP`K7q9dAM|WvSm!5#Aq@lqf&Ix9 znG&*oqiZ&!LDCvDZEK9~$@M>?W0Kwk7a?Q+VrHRgUAuumAYh8z#*{cd_uV1}weF5< z%VsgUf2@bnzBO?Vgd=0QD6N}DXW;VR%2$yI1Ofq%QJNfhpvJ9;gpZ4@S&X@%wC~5a zPM{Sd!ykh!$cPGd4W7?5gCPwB0)hM}MW%$a-Mzj<r8{xsd>eDDw5AOx0~v|(&me^* zU#GtW0)c=FC>v86JM?NDegMaCzE(O8(x|3wRn5=+5syIdF1$Fn80w;dKp;>YWn)VG zX0HEG0JOjnNTTcM%8?dS1$mT));F04sf5Gopn^=}!q?J(a_EK*-2rlalNPRPgG3NO z2oghR0KiV`9LqmbTt}9~__+r^9qt8ZBoGJ?g^cO(_rGlekM4jgknX5MqolNR<D*&N ztU+oxK&jvz1-51VlQ#Gmjc5!cowjx;4x2vGS*XMGk8J?{9<E&rFBmoFWE4T`j1Gzr z_u<7%w{q)DYxdU5;_EaHRVg*lnoT`#z!cu6<2h<XyzyC%pG#Mz!v?k3$hzo$Uw=Bd zYW>>ZaE@uMqjv{uPOq`C%m0YwpF-}L-%BSMcns^fZ~pm<+{$5vjHzp>-b$o+19}-6 zP!`d6sTP<fo2PjfF5j+3BiE<r9-_n0f-(@|BCea$lR<VwUSn&N;b>vzhe|V=yYopr zh4r$mi0DGfdxZ_`r{~G&JOpi|XW-HoOHd}C`~laS9AjK}sYA^4IR_a%T%#CY+ap+K z-Te$~A!AA$KlRU8dS13+5X-3>fdG+ha2N2pSTJyt?GHt)X?hK=l)S;{<R-4+>lbA@ z%U!s1tDF&uI-t!wimn>k$oFQL5lL{PSGmS~HKw%YESd){J}m#f9ym23xsQ?C@>YkY z1_crLhN4BbE-@%lQ)8<FUa?_B*X$V4A(VoT_#L=@?y9#&^%X{2tgyeGhn9T*Tuznm z=%kgpM#G0};Gb>0#@hIK398QZv(z_2+6Jxc8y40)d%ww;qZ5_THM%tV)56A7j?c03 z!ZyR02-7hpo$F^8(1>2OYDBj(YE)qz)0qFqr}XbUl+E;78~=1O)qDNyRqIaLq#;we z(M`OM+z__egTls?=Fq80Kj=jWcSR%uhRFs9P$OImTt!9_wR>>m+!7@0nitJB41Nz~ z!x%lLTgEio(C_Tr@E0%wWdLT@@SbMnJ9LUTU<UQTs3R*dd0hYFhY+Qt72N_qXk)$S zp>zOThH7e7A~38!(njhEl<kP3hry7R2stm)FCP13NQufA)4&9B{T!x$(k1WGZZh>N z`tTRrs9hx`(GVNpd->LnZlv^XoWp38xjBrYs(9?Y1Y;WT4~krFoV}Mdfp|;}H)^WG zIf*Ittucrq`!kctkWv;QP#&M2zv{E|QW;|^SZE5E2xK?qi-*3YfOdtkj&la}#-C>G zMh3#}drKLQU`We_B2pnb;4#IGDJk-}fCx2>$}=>i9(4BQ!Zm{`)-5|K=onMo%my+# z@;u|=8>H-3fpmItV=9;<vp87;0@a4P`>JM0hqz==vyNKJ&mk(`ppyajJUa3YYQz)# z9#nwh#?%G%BcPrR_AUgf3q=f7N$u{2G^l^HTQAJIjO{xt+`_ROmvqJ_1)g@Q(kWfY zm>&JNegj;%@d$iFj?T;H6Q~-WeAJ$ph=C=Gc8|>@EpUSd{KrS-?JabTh2t;fLf1KC z_M6Q#Q7kg$&fIts_ByS0<gx?gK#YDs)u0=Wb4NQ~O@Q@6I)`fOtYwY6v$^pSGZ7PY zjX45Er4^!(0#d}I4+S^`<L;@aXrR&R*AE_EpJjs2GS1(H6o%cFby(m0Z;#LQv%d)F z2?jBP^lOob-isGeH|{q56Sj!boA|~yV3MHscA8`n;UbhtCT!mVSBOeFo)e<d@{v)g zs7zE!C>v8s@wzk^oM8BT=y^T+FaO7|kpZ1Pl(xx1U^10nt4j%F@?OS~LS`_V{Xag> zB7m90G~uV;6Oi5&319YNjcAjrdYyDP7+b(J=UtV#ipsovK93$UI7B(Z_Yt4WYs@N? z)vT7xfwD1W5!JBl3*2v`CY4({;3-Gv{?X#Bb|IS~$@QlCq=Cigwgxwk&Gow?P3}JX z>X%F4BD9I}7v-hi7!3#(k3g{AV5mA><T0?cE{|O$4AaeW)K+<722-4l3zRpKjMH35 z$0$em-h8tYljFO-2a(2jjlFRW-bBRs*^Oq-QYg)Vp+W03aD8L=I=vTlSxzBs0s-0@ zSCxz@MSqOhAG@{_9QXx{>K1)VX_66TP`gUeHI$G$r^jJDY8^Y(zbZs>%{ilb=;eiy z&JHi(O?4MOzSSN3f803V1|X-+xM6L{;~H1~vkPTlL)d;lObg#<b3MTjWH|2vOP1l| zF*6Kw=913tuhXFMlAX6w5$_oZ#>i<&aLpsO49^et#>#3?%DIMnO`5jw|I?Y*_waqo z9mpsdQ)i>njQY$WAdmhGcz|?FYH){HN|~cmgpN^k$0?&q7k4V!W-l?Z3<<xeG`;20 z=Sx6Fn3RGbp)+y~FohgG4P7uzW>j=#LSkIsx*pBsf$+VlRBxKjYM<Fk8+r=Y;PUPK zPQWE;bm?PReoe3a@LCHJn3tP8l;U8TC9BQrZ?H?@Gk7`E$$hP4djNfOH@-*r0~uFp zf*Uh>&23Dx2+HwxW(IA1o_C+Eywe4TIrc$+feE0^(oW~*2t%Wab=CLM1g=@Fsq%E* zm<Wg5=%O=l`EScmluLi5O|J>p&v)?mno3ER6P=Q<y*{JTmG7U>;UF>^#@&LUanX&5 z>ujB$&^BT(GYrcxeEr!b-}c(DuD{fO<S7x?;L7a|=;GtgyO0^zP8PZ1o+RXO^2Toe zC07MlMp2^X#ldEJp47wx4npgQHOq%+NGXDL>p7w$!XkM1&inoH&iHMA5*NS>vE1l% z7}8NLf8N0x?HZUOi%t!rF`X#HeatM}2VIy{WYQOn4_PugtClwT%eUKb=~fdL2D5Ki z=l2bfG(a!0RztcKdOttLECa1MV<+*Nxx3Rp%VkVuI?|ICa(bTAhrv0SD%oIEgU8IV zPUul+?WQ<9K04Pg|LmQ)V+t|WQcfD%=*$|_mQk%*-NN=SX1b7ajSh=J;d`#R=_g6< zLRnC`;nM9E1iDZb8_vuZOIv?5wlT$&<V}|iz)sNf#k8d}k@V4j|NcV`wTIwZG`b6T zadb*IsK!JC($hvG27(MKVBN+DyalGvDQr(2(Uh8$Yb`q|HDFQrJ};bmac>9oGnO$u zKKK1qOi3<)k6_yJ*r~qti3GHUJf%U_enAGcwDpss*9IIms;68k>AdFDbO&Ih4PO@# zm`Z)?O`|ftH1dVM=+uVry$WeX2Qj!!0~}yqV@gH<uaFLS45lp?tfVXl_`Zg;e&rZc zBl47H>nU5gh*24120F<NMQ6H&?URv|8`Ee^O$px%_!sDeoF1#%$`eV;g^5dh8dK^v z_1%yTp$O~Or@kvSx~2vwN{?4z4NGxIjHGxCHU{km)m4~*I*yU(Os4}QdYe+|vLjOH zI?~cGVx)w&=Qm)Wtt{2M0y{D)TA>REMi}mDOta2i@P1(HC=uf~sfIe3c4L&|@Aqe? z55Yrk)8jiJYp7WF1ycy2J4F@`&V*XPfDz~V)LJ&s>A<9HduOQteO<j$C__8?)Bn`W z74bW8<J_H6C*Sx2tHodfX;9DgZ8<VK8dHj>=y(KFk-^cAqwjy)1_NwFqZ}Ty3J$Wy zy6>V9XN1nrX*uLSiiN0jh?(fLrFEJN9iG=&qZY~bv@R1}(1jV{`xwm8hAI9SjcOLD z+L{F98dC>iFvZZ3A)`u>YKbZL9gXSq|A4E$9Dz}RUK{69^{21PsFD$-H0aD>=vOHk z-GMqCkpkV25{+)w4w^aWKQ1EGe@x=7AN8%%D=CJci+D$2iK#<6--DKRgLGh~3+8Fb z4Swdu54geKn|Q-2`w9#nXCeDVk!mFR(wVvbhXvDyb(=9Aq<5=q3^o`V)#G#hA1M8M z?04VJ=Mj3K`Ju<K4od<IXheVDnZSPKZxT9kq`m&kG>fdK>3!qVM)pImA<5uer_-Uc z2HV~7ySg{dO!!_OWo+!DFgCx<uudX7l?2Su0v6pI7}m#f%?%jVZE}K@P4DhaJxl*a z?Tt}o3|4VN{+e;vydg9Gi;a1r>-7JsHAZM$u>O1%9WQ;O+!*{!tH3{x>_(*oZPcHh z>uOD<GN!L?s#Dlr?4fu>YDl`AB@@E;w6QbMg0hi@6+dpAy(y0ok>EpFLk(=hf$j?% z4MVfinW4?*2Hu+qW{<9H7hq6flO>>(XO+{tW0TDSZ86R-;1XTk^dW#0dMG-W^!i7g zM;dPi-OWd@lXA|L?uUS{K_@~qK?hoE)M6Db-`?GM(W{0ut3TCya8YB|u>$gaXhRe| z+-RVw`tcf59q;*QZPZt(S*+liZ!)HgMK(NzfUnR%0Ugaq)Ci-y`KZjH+Iy1feI87c z{9UTCGdA5vA6B;-lFqs@K^X+W4JlXUWY|CxrH^%N@NQ$VL2#5TfNT%az{+dBVN8EJ z*KdWWG>{j@)8e($moGI(`N9!HN=)f~E)CFv#?IKWQtFPYAz6=3w=#%U>CCDFf&@m$ z&LP_`$81K~sBRe3M2I#70y)7{_=kn3^VBpuak^D)ijbuGH|U~iPTj)x$r$N6t!hX@ z|5<0*h{R8WYYe+mW%}0>ed0V!&5&AH4DQjE*@3^D9XnW%#&mYRUuVqm8KnVNsZSp= za+qEn#`@q2CdBpMDj3O&^%m%Ygzd+9jkSY4eUYCbX~k@Yr5owqt;qCmRJ|zGnEa*= z@H(rpo7p+x{i9TAOkciA^ruY%lLO<$x<b=*(wn);g_{z#--B$^)R^+-Eil2QTMN6* zt8@a2QswcE3e{4{i4puFxJyf>+xJnklE#!X9*k)~8R~qdGq<1$6Tz64h3?5bNsf+8 zodd(os3B$0yh@i1Djn~T`o9%2sF(k?XmB2IaVRq|#V|AQ$6YhHS<RFiyf6b30ehVW zhs+BM3fu38)*~_o)R0Dz2G#M+oeXM~l$2;5un?!+Y6Kev)4nWS+ATumB*>1QYKRO1 z1<|Zv6fi@iF$<Mf#YXE98PKnWv=uq*ow?c|lNr>R7x$n_(0S#Ap8!|aoS+DGhpYe> zJZV56P!I+MlzCVbL!cytzAV&E!uI>3^N0*HGvjm=nmUxQli+G&QZ~!8Qq#Sp({<@q z(_KdfBh*N2b7D9P5k)`;=p`#3kr7;PT1S^kkPb}LwNyKVub-XQD2@>;N#E%_B4a`g zX(zJHeF2SX)5%&YV+)l$HZyc&+)TI6!Y5}tqu=28L>mwYXkcna@$91RvzWS6f^?w! z3~e#Y3)}C9)`8*Z7|c8`uU1b+m1?Oj8r7(x>UDC>G2Q-V&0}zd2ri5|bDS~uGYAAU zFa^mhsBrM0?uLT~xPtc4az)ggbT%3mnEIi0U^p+od2+i^jln*xN=BzchHJhyeL|*x znP$yvB}m$GZ9~+dCMYGw7z6@^;j95F&>6%VXIHiA^HRs13;Mi_H9#k->P{qVFGK@$ zk*vq+DQu}%tVtwBP-JT7(0`V0Z_B^mIFFy{ygoNy$K8!AI_*N0$;K)!$&VX&j|)t3 zGwCEYzy?`#x=7Efh8v*X(N}?qMWjW#!dVOe`l7g+>;<rIOab&pPC`-mKnT6*P*USj z-Z*qR?*la?+oQ{<zDFB8IT#Vm&lC%|Jo?7vjm4mbY<rGxSzMCNa$El0nlrO8c%jR~ zv6z4^Sj5#!214jf+mzAixgS~NrZ>&YL=0+F_bj;y;d@nNvl1HCk8jP&G%ynv%S%Zb zn5g3mwC0z-n3roVyMTpy86q!(>yHD2$?#$VuA@1MEUD9TPjm_-3`iA5x|uNQF2TXR z<FpfGSU<kKxM5h!Xj*c~v9Q6=nYnbk^~SIou4xahKMtvIXTnBca$&VDXa&7=(}()^ zBckE*no&w;8Zhc^(b!Kn)jFG+mP{V?NE;By*f6Zdӊpw>S=t89G>Nc1Ds&T5w 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 B-E;r| literal 0 HcmV?d00001 diff --git a/tools.json b/tools.json index f416bd9..047db2d 100644 --- a/tools.json +++ b/tools.json @@ -134,7 +134,7 @@ }, "apmtools": { "short_description": "An example for analyzing atom probe data.", - "description": "Miscellaneous tools from the atom probe community:\nCurrently the Leoben APT_analyzer and the paraprobe-toolbox.", + "description": "Miscellaneous tools from the atom probe community:\nCurrently APTyzer, paraprobe-toolbox, and APAV.\nA JupyterLab instance will start which has a detailed Cheatsheet notebook for getting started.", "image": "gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/apmtools-webtop", "privileged": true, "icon": "jupyter_logo.svg", @@ -148,7 +148,7 @@ }, "fiji": { "short_description": "ImageJ and Fiji for image processing", - "description": "ImageJ and Fiji with amongst others several electron-microscopy specific plug-ins", + "description": "ImageJ and Fiji with amongst others several electron-microscopy specific plug-ins.\nA JupyterLab instance will start which has a detailed Cheatsheet notebook for getting started.", "image": "gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/fiji-webtop", "privileged": true, "icon": "jupyter_logo.svg", @@ -162,7 +162,7 @@ }, "frwr": { "short_description": "Inline electron holography by C. Koch", - "description": "FRWR3 in-line holography/focus series reconstruction code", + "description": "FRWR3 in-line holography/focus series reconstruction code.", "image": "gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/frwr-webtop", "privileged": true, "icon": "jupyter_logo.svg", @@ -176,7 +176,7 @@ }, "abtem": { "short_description": "Electronic structure supported image simulation for transmission electron microscopy.", - "description": "VESTA, GPAW, and abTEM configured in one container for simulating images and diffraction patterns in transmission electron microscopy", + "description": "VESTA, GPAW, and abTEM configured in one container for simulating images and diffraction patterns in transmission electron microscopy.\nA JupyterLab instance will start which has a detailed Cheatsheet notebook for getting started.", "image": "gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-remote-tools-hub/abtem-webtop", "privileged": true, "icon": "jupyter_logo.svg", -- GitLab From 5abf70b9fd82c9867d2cfb27ad5945ff1aeb637f Mon Sep 17 00:00:00 2001 From: "markus.kuehbach" <markus.kuehbach@hu-berlin.de> Date: Fri, 28 Apr 2023 16:41:24 +0200 Subject: [PATCH 2/4] Updated apmtools with consistent ifes library for pre1 --- docker/apmtools/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/apmtools/Dockerfile b/docker/apmtools/Dockerfile index b073c41..4c2cbcd 100644 --- a/docker/apmtools/Dockerfile +++ b/docker/apmtools/Dockerfile @@ -48,7 +48,7 @@ RUN cd ~ \ && conda clean -afy \ && cd /home \ && python3 -m pip install --upgrade pip \ - && python3 -m pip install ecdf==0.7.0 pytictoc==1.5.2 ifes-apt-tc-data-modeling==0.0.6 python-docs-theme==2023.3.1 \ + && python3 -m pip install ecdf==0.7.0 pytictoc==1.5.2 ifes-apt-tc-data-modeling==0.0.7 python-docs-theme==2023.3.1 \ && cd /home \ && cd /home/atom_probe_tools/apav \ && python3 -m pip install apav==1.4.0 \ @@ -64,7 +64,7 @@ RUN cd ~ \ && cd /home/atom_probe_tools \ && git clone https://gitlab.com/paraprobe/paraprobe-toolbox.git \ && cd paraprobe-toolbox \ - && git checkout 78a394dc \ + && git checkout 7ed2695896b6ace73696ab67b8d922ea6db21fe8 \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p cmake \ && cd cmake \ -- GitLab From ffd6158ce42ff914009b601d4c9b77e29e6e8ad7 Mon Sep 17 00:00:00 2001 From: "markus.kuehbach" <markus.kuehbach@hu-berlin.de> Date: Wed, 3 May 2023 11:21:00 +0200 Subject: [PATCH 3/4] bumped ifes_apt_tc_data_modeling to 0.0.8 --- docker/apmtools/Cheatsheet.ipynb | 24 ++++++++++++++++++++++-- docker/apmtools/Dockerfile | 6 +++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/docker/apmtools/Cheatsheet.ipynb b/docker/apmtools/Cheatsheet.ipynb index 0ce2bf2..07614ea 100644 --- a/docker/apmtools/Cheatsheet.ipynb +++ b/docker/apmtools/Cheatsheet.ipynb @@ -395,6 +395,26 @@ "### Step 2: Use available files in community-specific formats from your uploads section or download the above-mentioned examples from APAV." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9bf1d28-abff-4628-aaa8-cb61f796e412", + "metadata": {}, + "outputs": [], + "source": [ + "import shutil" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d092aaf-663d-4ef3-9ac7-5fc6762d785f", + "metadata": {}, + "outputs": [], + "source": [ + "! cd /home/atom_probe_tools/apav && wget https://www.zenodo.org/record/77885531/files/apm_sprint14_apav_usa_denton_smith.zip && shutil.unpack_archive(\"apm_sprint14_apav_usa_denton_smith.zip\")" + ] + }, { "cell_type": "markdown", "id": "f65c621e-05f5-45ca-9a01-4cbd3206bc55", @@ -431,7 +451,7 @@ "source": [ "# Version, funding, feedback\n", "* **APTyzer** https://github.com/areichm/APTyzer 887b82f\n", - "* **paraprobe-toolbox** https://gitlab.com/paraprobe/paraprobe-toolbox 78a394dc\n", + "* **paraprobe-toolbox** https://gitlab.com/paraprobe/paraprobe-toolbox e349fd3 (v0.4)\n", "* **APAV** https://pypi.org/project/APAV v1.4.0\n", "* **NeXus** https://fairmat-experimental.github.io/nexus-fairmat-proposal latest\n", "\n", @@ -469,7 +489,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.8.16" } }, "nbformat": 4, diff --git a/docker/apmtools/Dockerfile b/docker/apmtools/Dockerfile index 4c2cbcd..09b9330 100644 --- a/docker/apmtools/Dockerfile +++ b/docker/apmtools/Dockerfile @@ -48,7 +48,7 @@ RUN cd ~ \ && conda clean -afy \ && cd /home \ && python3 -m pip install --upgrade pip \ - && python3 -m pip install ecdf==0.7.0 pytictoc==1.5.2 ifes-apt-tc-data-modeling==0.0.7 python-docs-theme==2023.3.1 \ + && python3 -m pip install ecdf==0.7.0 pytictoc==1.5.2 ifes-apt-tc-data-modeling==0.0.8 python-docs-theme==2023.3.1 \ && cd /home \ && cd /home/atom_probe_tools/apav \ && python3 -m pip install apav==1.4.0 \ @@ -64,7 +64,7 @@ RUN cd ~ \ && cd /home/atom_probe_tools \ && git clone https://gitlab.com/paraprobe/paraprobe-toolbox.git \ && cd paraprobe-toolbox \ - && git checkout 7ed2695896b6ace73696ab67b8d922ea6db21fe8 \ + && git checkout e349fd34d7cfcb4282c2f3f61c69f9e5659f68e6 \ && cd /home/atom_probe_tools/paraprobe-toolbox/code/thirdparty/mandatory \ && mkdir -p cmake \ && cd cmake \ @@ -151,4 +151,4 @@ COPY 02-exec-cmd /config/custom-cont-init.d/02-exec-cmd ENV HOME=/home/atom_probe_tools WORKDIR $HOME -# for running this container standalone e.g. docker run -p 3000:8888 <<imagename>> \ No newline at end of file +# for running this container standalone e.g. docker run -p 3000:8888 <<imagename>> -- GitLab From 050392186abd739661f7e510456c9d53b4d86dda Mon Sep 17 00:00:00 2001 From: "markus.kuehbach" <markus.kuehbach@hu-berlin.de> Date: Wed, 3 May 2023 14:37:53 +0200 Subject: [PATCH 4/4] fixed git and libssl-dev after checking successful compilation of image on nomad-util.esc mpcdf machine --- docker/apmtools/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/apmtools/Dockerfile b/docker/apmtools/Dockerfile index 09b9330..bd71db8 100644 --- a/docker/apmtools/Dockerfile +++ b/docker/apmtools/Dockerfile @@ -35,7 +35,7 @@ RUN cd ~ \ && chmod +x nodesource_setup.sh \ && ./nodesource_setup.sh \ && apt install -y nodejs=18.16.0-deb-1nodesource1 \ - && apt install -y m4=1.4.18-4 file=1:5.38-4 git=1:2.25.1-1ubuntu3.10 wget=1.20.3-1ubuntu2 mesa-common-dev=21.2.6-0ubuntu0.1~20.04.2 libglu1-mesa-dev=9.0.1-1build1 build-essential=12.8ubuntu1.1 mpich=3.3.2-2build1 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libmpfr-dev=4.0.2-1 libssl-dev=1.1.1f-1ubuntu2.17 hwloc=2.1.0+dfsg-4 \ + && apt install -y m4=1.4.18-4 file=1:5.38-4 git=1:2.25.1-1ubuntu3.11 wget=1.20.3-1ubuntu2 mesa-common-dev=21.2.6-0ubuntu0.1~20.04.2 libglu1-mesa-dev=9.0.1-1build1 build-essential=12.8ubuntu1.1 mpich=3.3.2-2build1 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libmpfr-dev=4.0.2-1 libssl-dev=1.1.1f-1ubuntu2.18 hwloc=2.1.0+dfsg-4 \ && wget https://repo.anaconda.com/miniconda/Miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ && mv Miniconda3-py38_23.1.0-1-Linux-x86_64.sh miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ && chmod +x miniconda3-py38_23.1.0-1-Linux-x86_64.sh \ -- GitLab