11# -------------------------------------------------------------------------------
22# Name: Temperature Dependent I-V Measurement GUI
33# Purpose: Provide a professional graphical user interface for controlling
4- # a Lakeshore 350 and a Keithley 2400.
4+ # a Lakeshore 350 and a Keithley 2400 for automated V-T sweeps .
55# Author: Prathamesh Deshmukh
66# Created: 18/09/2025
7- # Version: 5.2 (Original Theme Restored )
7+ # Version: 6.0 (Modernized GUI )
88# -------------------------------------------------------------------------------
99
1010# --- GUI and Plotting Packages ---
1616import traceback
1717from datetime import datetime
1818from matplotlib .figure import Figure
19+ import matplotlib .gridspec as gridspec
1920from matplotlib .backends .backend_tkagg import FigureCanvasTkAgg
2021import matplotlib as mpl
2122
@@ -98,15 +99,16 @@ def shutdown_all(self):
9899# FRONTEND CLASS - The Main GUI Application
99100#===============================================================================
100101class TemperatureIVGUI :
101- PROGRAM_VERSION = "5.2"
102- # --- Original Theme Colors Restored ---
103- CLR_BG , CLR_HEADER , CLR_FG = '#2B3D4F' , '#3A506B' , '#EDF2F4'
104- CLR_FRAME_BG , CLR_INPUT_BG = '#3A506B' , '#4C566A'
105- CLR_ACCENT_GREEN , CLR_ACCENT_RED , CLR_ACCENT_BLUE = '#A7C957' , '#EF233C' , '#8D99AE'
102+ PROGRAM_VERSION = "6.0"
103+ # --- Modern PICA Theme ---
104+ CLR_BG = '#2B3D4F' ; CLR_HEADER = '#3A506B' ; CLR_FG = '#EDF2F4'
105+ CLR_FRAME_BG = '#3A506B' ; CLR_INPUT_BG = '#4C566A'
106+ CLR_ACCENT_GREEN , CLR_ACCENT_RED , CLR_ACCENT_BLUE = '#A7C957' , '#E74C3C' , '#8D99AE'
107+ CLR_ACCENT_GOLD = '#FFC107'
106108 CLR_CONSOLE_BG = '#1E2B38'
107- FONT_BASE = ('Segoe UI' , 10 ); FONT_TITLE = ('Segoe UI' , 12 , 'bold' )
109+ FONT_BASE = ('Segoe UI' , 11 ); FONT_TITLE = ('Segoe UI' , 13 , 'bold' )
108110 try :
109- # Robust path finding for assets
111+ # Robust path finding for assets, assuming standard PICA structure
110112 SCRIPT_DIR = os .path .dirname (os .path .abspath (__file__ ))
111113 LOGO_FILE = os .path .join (SCRIPT_DIR , ".." , "_assets" , "LOGO" , "UGC_DAE_CSR.jpeg" )
112114 except NameError :
@@ -119,7 +121,7 @@ def __init__(self, root):
119121 self .root .geometry ("1600x950" )
120122 self .root .configure (bg = self .CLR_BG )
121123 self .root .minsize (1400 , 800 )
122- self .backend = ExperimentBackend (); self .experiment_state = 'idle'
124+ self .backend = ExperimentBackend (); self .experiment_state = 'idle' ; self . logo_image = None
123125 self .setup_styles ()
124126 self .create_widgets ()
125127 self .root .protocol ("WM_DELETE_WINDOW" , self ._on_closing )
@@ -129,20 +131,24 @@ def setup_styles(self):
129131 style = ttk .Style (self .root )
130132 style .theme_use ('clam' )
131133 style .configure ('.' , background = self .CLR_BG , foreground = self .CLR_FG , font = self .FONT_BASE , bordercolor = self .CLR_ACCENT_BLUE )
132- style .configure ('TFrame' , background = self .CLR_BG )
133- style .configure ('TPanedWindow' , background = self .CLR_BG )
134- style .configure ('TLabel' , background = self .CLR_FRAME_BG , foreground = self .CLR_FG )
135- style .configure ('Header.TLabel' , background = self .CLR_HEADER )
134+ style .configure ('TFrame' , background = self .CLR_BG ); style .configure ('TPanedWindow' , background = self .CLR_BG )
135+ style .configure ('TLabel' , background = self .CLR_FRAME_BG , foreground = self .CLR_FG ); style .configure ('Header.TLabel' , background = self .CLR_HEADER )
136136 style .configure ('TEntry' , fieldbackground = self .CLR_INPUT_BG , foreground = self .CLR_FG , bordercolor = self .CLR_ACCENT_BLUE , insertcolor = self .CLR_FG )
137137 style .map ('TEntry' , background = [('active' , self .CLR_INPUT_BG ), ('!disabled' , self .CLR_INPUT_BG )])
138- style .configure ('TButton' , padding = (10 , 8 ), background = self .CLR_ACCENT_BLUE , foreground = self .CLR_BG )
139- style .map ('TButton' , background = [('active' , self .CLR_FG ), ('!disabled' , self .CLR_ACCENT_BLUE )], foreground = [('active' , self .CLR_BG )])
140- style .configure ('Start.TButton' , background = self .CLR_ACCENT_GREEN )
141- style .configure ('Stop.TButton' , background = self .CLR_ACCENT_RED )
138+
139+ style .configure ('TButton' , font = self .FONT_BASE , padding = (10 , 9 ), foreground = self .CLR_ACCENT_GOLD , background = self .CLR_HEADER , borderwidth = 0 , focusthickness = 0 , focuscolor = 'none' )
140+ style .map ('TButton' , background = [('active' , self .CLR_ACCENT_GOLD ), ('hover' , self .CLR_ACCENT_GOLD )], foreground = [('active' , self .CLR_BG ), ('hover' , self .CLR_BG )])
141+
142+ style .configure ('Start.TButton' , font = self .FONT_BASE , padding = (10 , 9 ), background = self .CLR_ACCENT_GREEN , foreground = self .CLR_BG )
143+ style .map ('Start.TButton' , background = [('active' , '#8AB845' ), ('hover' , '#8AB845' )])
144+
145+ style .configure ('Stop.TButton' , font = self .FONT_BASE , padding = (10 , 9 ), background = self .CLR_ACCENT_RED , foreground = self .CLR_FG )
146+ style .map ('Stop.TButton' , background = [('active' , '#D63C2A' ), ('hover' , '#D63C2A' )])
147+
142148 style .map ('TButton' , foreground = [('disabled' , '#6e7a91' )])
143149 style .configure ('TLabelframe' , background = self .CLR_FRAME_BG , bordercolor = self .CLR_ACCENT_BLUE )
144150 style .configure ('TLabelframe.Label' , background = self .CLR_FRAME_BG , foreground = self .CLR_FG , font = self .FONT_TITLE )
145- mpl .rcParams [ 'font.family' ] = 'Segoe UI'
151+ mpl .rcParams . update ({ 'font.family' : 'Segoe UI' , 'font.size' : 11 , 'axes.titlesize' : 15 , 'axes.labelsize' : 13 })
146152
147153 def create_widgets (self ):
148154 self .create_header ()
@@ -165,13 +171,18 @@ def _create_right_panel(self, parent):
165171 panel = ttk .Frame (parent , padding = 5 )
166172 container = ttk .LabelFrame (panel , text = 'Live I-V Curve' )
167173 container .pack (fill = 'both' , expand = True )
168- self .figure = Figure (dpi = 100 , facecolor = 'white' ); self .ax_main = self .figure .add_subplot (111 )
169- self .ax_main .set_facecolor ('#f0f0f0' )
170- self .ax_main .grid (True , linestyle = '--' , color = 'white' ); self .ax_main .axhline (0 , color = 'k' , lw = 0.5 ); self .ax_main .axvline (0 , color = 'k' , lw = 0.5 )
174+ self .figure = Figure (dpi = 100 , facecolor = 'white' )
175+ gs = gridspec .GridSpec (2 , 1 , figure = self .figure , height_ratios = [3 , 1 ])
176+ self .ax_main = self .figure .add_subplot (gs [0 , 0 ])
177+ self .ax_temp = self .figure .add_subplot (gs [1 , 0 ], sharex = self .ax_main )
178+
171179 self .line_main , = self .ax_main .plot ([], [], color = self .CLR_ACCENT_RED , marker = 'o' , markersize = 3 , linestyle = '-' )
172- self .ax_main .set_title ("Waiting for experiment..." , fontweight = 'bold' , color = self .CLR_BG )
173- self .ax_main .set_xlabel ("Current (A)" , color = self .CLR_BG ); self .ax_main .set_ylabel ("Voltage (V)" , color = self .CLR_BG )
174- self .ax_main .tick_params (colors = self .CLR_BG ); self .figure .tight_layout ()
180+ self .ax_main .set_title ("Waiting for experiment..." , fontweight = 'bold' ); self .ax_main .set_ylabel ("Voltage (V)" )
181+ self .ax_main .tick_params (axis = 'x' , labelbottom = False )
182+ self .line_temp , = self .ax_temp .plot ([], [], color = self .CLR_ACCENT_BLUE , marker = '.' , markersize = 3 , linestyle = '-' )
183+ self .ax_temp .set_xlabel ("Current (A)" ); self .ax_temp .set_ylabel ("Temperature (K)" )
184+ for ax in [self .ax_main , self .ax_temp ]: ax .grid (True , linestyle = '--' , alpha = 0.6 )
185+ self .figure .tight_layout ()
175186 self .canvas = FigureCanvasTkAgg (self .figure , container ); self .canvas .get_tk_widget ().pack (fill = 'both' , expand = True , padx = 5 , pady = 5 )
176187 return panel
177188
@@ -184,13 +195,13 @@ def create_header(self):
184195 def _create_info_panel (self , parent , grid_row ):
185196 frame = ttk .LabelFrame (parent , text = 'Information' )
186197 frame .grid (row = grid_row , column = 0 , sticky = 'new' , pady = 5 )
187- frame .grid_columnconfigure (1 , weight = 1 )
188- logo_canvas = tk .Canvas (frame , width = 80 , height = 80 , bg = self .CLR_FRAME_BG , highlightthickness = 0 )
198+ frame .grid_columnconfigure (1 , weight = 1 ); logo_canvas = tk .Canvas (frame , width = 80 , height = 80 , bg = self .CLR_FRAME_BG , highlightthickness = 0 )
189199 logo_canvas .grid (row = 0 , column = 0 , padx = 10 , pady = 10 )
190200 if PIL_AVAILABLE and os .path .exists (self .LOGO_FILE ):
191201 try :
192202 img = Image .open (self .LOGO_FILE ).resize ((80 , 80 ), Image .Resampling .LANCZOS )
193- self .logo_image = ImageTk .PhotoImage (img ); logo_canvas .create_image (40 , 40 , image = self .logo_image )
203+ self .logo_image = ImageTk .PhotoImage (img )
204+ logo_canvas .create_image (40 , 40 , image = self .logo_image )
194205 except Exception : pass
195206 info_text = ("Institute: UGC DAE CSR, Mumbai\n Measurement: Voltage vs. Temperature\n Instruments: Lakeshore 350 & Keithley 2400" )
196207 ttk .Label (frame , text = info_text , justify = 'left' ).grid (row = 0 , column = 1 , sticky = 'w' , padx = 5 )
@@ -203,15 +214,13 @@ def _create_params_panel(self, parent, grid_row):
203214 temp_frame = ttk .LabelFrame (container , text = 'Temperature' )
204215 temp_frame .grid (row = 0 , column = 0 , sticky = 'nsew' , padx = (0 ,5 ))
205216 temp_frame .grid_columnconfigure (1 , weight = 1 )
206- self ._create_entry (temp_frame , "Start Temp (K)" , "300" , 0 )
207- self ._create_entry (temp_frame , "End Temp (K)" , "280" , 1 )
217+ self ._create_entry (temp_frame , "Start Temp (K)" , "300" , 0 ); self ._create_entry (temp_frame , "End Temp (K)" , "280" , 1 )
208218 self ._create_entry (temp_frame , "Temp Step (K)" , "-5" , 2 )
209219 self .lakeshore_combobox = self ._create_combobox (temp_frame , "Lakeshore VISA" , 3 )
210220 iv_frame = ttk .LabelFrame (container , text = 'I-V Sweep' )
211221 iv_frame .grid (row = 0 , column = 1 , sticky = 'nsew' , padx = (5 ,0 ))
212222 iv_frame .grid_columnconfigure (1 , weight = 1 )
213- self ._create_entry (iv_frame , "Max Current (µA)" , "100" , 0 )
214- self ._create_entry (iv_frame , "Step Current (µA)" , "5" , 1 )
223+ self ._create_entry (iv_frame , "Max Current (µA)" , "100" , 0 ); self ._create_entry (iv_frame , "Step Current (µA)" , "5" , 1 )
215224 self ._create_entry (iv_frame , "Compliance (V)" , "10" , 2 )
216225 self ._create_entry (iv_frame , "Dwell Time (s)" , "0.2" , 3 )
217226 self .keithley_combobox = self ._create_combobox (iv_frame , "Keithley VISA" , 4 )
@@ -277,15 +286,17 @@ def _experiment_loop(self):
277286 self .log (f"Temperature stabilized at { temp_now :.4f} K." ); self .experiment_state = 'run_sweep'
278287 self .root .after (100 , self ._experiment_loop )
279288 else :
280- self .root .after (5000 , self ._experiment_loop )
289+ self .root .after (3000 , self ._experiment_loop )
281290 elif self .experiment_state == 'run_sweep' :
282291 temp_now = self .backend .get_temperature ()
283292 self .log (f"Starting I-V sweep at { temp_now :.4f} K..." )
284293 self .ax_main .set_title (f"Sweeping at { temp_now :.2f} K..." ); self .line_main .set_data ([], []); self .canvas .draw ()
285294 currents , voltages = self .backend .run_iv_sweep (self ._validate_and_get_params ())
286295 self .log ("I-V sweep complete." ); self .line_main .set_data (currents , voltages )
287296 self .ax_main .relim (); self .ax_main .autoscale_view (); self .canvas .draw ()
288- self ._save_data (currents , voltages , temp_now )
297+ temps_for_plot = np .full_like (currents , temp_now )
298+ self .line_temp .set_data (currents , temps_for_plot ); self .ax_temp .relim (); self .ax_temp .autoscale_view ()
299+ self ._save_data (currents , voltages , temp_now ); self .canvas .draw ()
289300 self .current_temp_index += 1
290301 if self .current_temp_index >= len (self .temp_points ):
291302 self .stop_experiment ("All points measured." )
@@ -323,7 +334,7 @@ def set_ui_state(self, running: bool):
323334 self .stop_button .config (state = 'normal' if running else 'disabled' )
324335
325336 def _scan_for_visa_instruments (self ):
326- if self .backend .rm is None : self .log ("ERROR: VISA library missing." ); return
337+ if self .backend .rm is None : self .log ("ERROR: PyVISA library missing or failed to load ." ); return
327338 self .log ("Scanning for VISA instruments..." ); resources = self .backend .rm .list_resources ()
328339 if resources :
329340 self .log (f"Found: { resources } " ); self .lakeshore_combobox ['values' ] = resources ; self .keithley_combobox ['values' ] = resources
@@ -334,7 +345,8 @@ def _scan_for_visa_instruments(self):
334345
335346 def _browse_file_location (self ):
336347 path = filedialog .askdirectory ()
337- if path : self .entries ["Save Location" ].delete (0 , 'end' ); self .entries ["Save Location" ].insert (0 , path )
348+ if path :
349+ self .entries ["Save Location" ].config (state = 'normal' ); self .entries ["Save Location" ].delete (0 , 'end' ); self .entries ["Save Location" ].insert (0 , path ); self .entries ["Save Location" ].config (state = 'disabled' )
338350
339351 def _create_entry (self , parent , label_text , default_value , row , browse = False ):
340352 ttk .Label (parent , text = f"{ label_text } :" ).grid (row = row , column = 0 , sticky = 'w' , padx = 10 , pady = 3 )
@@ -344,6 +356,8 @@ def _create_entry(self, parent, label_text, default_value, row, browse=False):
344356 if browse :
345357 btn = ttk .Button (parent , text = "..." , width = 3 , command = self ._browse_file_location )
346358 btn .grid (row = row , column = 3 , sticky = 'e' , padx = (0 ,10 ))
359+ # Make save location entry read-only to enforce using the button
360+ entry .config (state = 'disabled' )
347361
348362 def _create_combobox (self , parent , label_text , row ):
349363 ttk .Label (parent , text = f"{ label_text } :" ).grid (row = row , column = 0 , sticky = 'w' , padx = 10 , pady = 3 )
0 commit comments