11"""
22Module: PE_Plotter_GUI.py
3- Purpose: A customized P-E Data Plotter extracting specific metadata and overlaying plots .
3+ Purpose: PE Hysteresis Plotter Utility (Part of PICA suite) .
44"""
55
66import tkinter as tk
7- from tkinter import ttk , filedialog , messagebox , scrolledtext
7+ from tkinter import ttk , filedialog , messagebox , scrolledtext , Canvas
88import os
99import io
1010import pandas as pd
1414from matplotlib .backends .backend_tkagg import FigureCanvasTkAgg , NavigationToolbar2Tk
1515import matplotlib as mpl
1616
17+ try :
18+ from PIL import Image , ImageTk
19+ PIL_AVAILABLE = True
20+ except ImportError :
21+ PIL_AVAILABLE = False
22+
23+
1724class PEPlotterAppGUI :
18- PROGRAM_VERSION = "1.0 "
25+ PROGRAM_VERSION = "2.2 "
1926
20- # --- Styling to match your template ---
27+ # --- PICA Styling ---
2128 CLR_BG = '#B8A392'
2229 CLR_HEADER = '#E5DCD3'
2330 CLR_FG = '#2C2825'
@@ -30,21 +37,28 @@ class PEPlotterAppGUI:
3037 FONT_BASE = ('Segoe UI' , 10 )
3138 FONT_TITLE = ('Segoe UI' , 12 , 'bold' )
3239
40+ try :
41+ SCRIPT_DIR = os .path .dirname (os .path .abspath (__file__ ))
42+ LOGO_FILE_PATH = os .path .join (SCRIPT_DIR , ".." , "assets" , "LOGO" , "UGC_DAE_CSR_NBG.jpeg" )
43+ except NameError :
44+ LOGO_FILE_PATH = "../assets/LOGO/UGC_DAE_CSR_NBG.jpeg"
45+
3346 def __init__ (self , root ):
3447 self .root = root
35- self .root .title (f"P-E Data Plotter v{ self .PROGRAM_VERSION } " )
36- self .root .geometry ("1300x800 " )
37- self .root .minsize (1000 , 600 )
48+ self .root .title (f"PICA PE Plotter Utility v{ self .PROGRAM_VERSION } " )
49+ self .root .geometry ("1400x900 " )
50+ self .root .minsize (1100 , 700 )
3851 self .root .configure (bg = self .CLR_BG )
3952
4053 self .active_filepath = None
4154 self .file_data_cache = {} # {filepath: {'df': dataframe, 'metadata': dict}}
4255 self .file_ui_elements = {} # {filepath: {'var': boolVar, 'chk': checkbutton, 'lbl': label, 'frame': frame}}
56+ self .logo_image = None
4357
4458 self .setup_styles ()
4559 self .create_widgets ()
46- self .log ("Welcome to the P-E Plotter Utility." )
47- self .log ("Click 'Add File(s)...' to load your .txt or .csv files." )
60+ self .log ("Welcome to the PICA PE Hysteresis Plotter Utility." )
61+ self .log ("Click 'Add File(s)...' to load your .txt or .csv measurement files." )
4862
4963 def setup_styles (self ):
5064 self .style = ttk .Style (self .root )
@@ -55,33 +69,66 @@ def setup_styles(self):
5569 self .style .configure ('TLabel' , background = self .CLR_FRAME_BG , foreground = self .CLR_FG )
5670 self .style .configure ('Header.TLabel' , background = self .CLR_HEADER )
5771 self .style .configure ('TButton' , font = self .FONT_BASE , padding = (8 , 5 ), foreground = self .CLR_ACCENT_GOLD , background = self .CLR_HEADER )
58- self .style .configure ('Plot.TButton' , background = self .CLR_ACCENT_GREEN , foreground = self .CLR_BG )
72+ self .style .map ('TButton' , background = [('active' , self .CLR_ACCENT_GOLD ), ('hover' , self .CLR_ACCENT_GOLD )],
73+ foreground = [('active' , self .CLR_BG ), ('hover' , self .CLR_BG )])
5974 self .style .map ('TCombobox' , fieldbackground = [('readonly' , self .CLR_INPUT_BG )])
6075 self .style .configure ('TLabelframe' , background = self .CLR_FRAME_BG , bordercolor = self .CLR_ACCENT_BLUE )
6176 self .style .configure ('TLabelframe.Label' , background = self .CLR_FRAME_BG , foreground = self .CLR_FG , font = self .FONT_TITLE )
6277 self .style .configure ('Input.TFrame' , background = self .CLR_INPUT_BG )
6378
6479 mpl .rcParams .update ({
65- 'font.family' : 'Segoe UI' , 'font.size' : 10 ,
80+ 'font.family' : 'Segoe UI' , 'font.size' : 11 ,
81+ 'axes.titlesize' : 14 , 'axes.labelsize' : 12 ,
6682 'figure.facecolor' : self .CLR_BG , 'axes.facecolor' : '#F4EFEA' ,
6783 'axes.edgecolor' : self .CLR_FG , 'axes.labelcolor' : self .CLR_FG ,
68- 'text.color' : self .CLR_FG ,
84+ 'text.color' : self .CLR_FG , 'xtick.color' : self . CLR_FG , 'ytick.color' : self . CLR_FG
6985 })
7086
7187 def create_widgets (self ):
72- # --- Header ---
88+ # --- Header (PICA Style with Logo and Institute Name) ---
7389 header = tk .Frame (self .root , bg = self .CLR_HEADER )
7490 header .pack (side = 'top' , fill = 'x' , padx = 1 , pady = 1 )
75- ttk .Label (header , text = "P-E Hysteresis Plotter" , style = 'Header.TLabel' , font = ('Segoe UI' , 16 , 'bold' )).pack (side = 'left' , padx = 20 , pady = 15 )
91+ header .grid_columnconfigure (1 , weight = 1 )
92+
93+ left_header_frame = tk .Frame (header , bg = self .CLR_HEADER )
94+ left_header_frame .grid (row = 0 , column = 0 , sticky = 'w' )
95+ font_title_main = ('Segoe UI' , 14 , 'bold' )
96+
97+ ttk .Label (left_header_frame , text = "PE Hysteresis Plotter Utility" , style = 'Header.TLabel' ,
98+ font = font_title_main , foreground = self .CLR_ACCENT_GOLD ).pack (side = 'top' , anchor = 'w' , padx = 20 , pady = (10 , 0 ))
99+ ttk .Label (left_header_frame , text = "(Part of the PICA Suite)" , style = 'Header.TLabel' ,
100+ font = ('Segoe UI' , 10 , 'italic' ), foreground = self .CLR_FG ).pack (side = 'top' , anchor = 'w' , padx = 20 , pady = (0 , 10 ))
76101
102+ center_header_frame = tk .Frame (header , bg = self .CLR_HEADER )
103+ center_header_frame .grid (row = 0 , column = 1 , sticky = 'ew' )
104+
105+ logo_canvas = Canvas (center_header_frame , width = 60 , height = 60 , bg = self .CLR_HEADER , highlightthickness = 0 )
106+ logo_canvas .pack (side = 'left' , pady = 10 )
107+
108+ if PIL_AVAILABLE and os .path .exists (self .LOGO_FILE_PATH ):
109+ try :
110+ img = Image .open (self .LOGO_FILE_PATH ).resize ((60 , 60 ), Image .Resampling .LANCZOS )
111+ self .logo_image = ImageTk .PhotoImage (img )
112+ logo_canvas .create_image (30 , 30 , image = self .logo_image )
113+ except Exception as e :
114+ self .log (f"Warning: Could not load logo. { e } " )
115+
116+ institute_frame = tk .Frame (center_header_frame , bg = self .CLR_HEADER )
117+ institute_frame .pack (side = 'left' , padx = 15 )
118+ ttk .Label (institute_frame , text = "UGC-DAE Consortium for Scientific Research" ,
119+ style = 'Header.TLabel' , font = ('Segoe UI' , 16 , 'bold' )).pack (anchor = 'w' )
120+ ttk .Label (institute_frame , text = "Mumbai Centre" ,
121+ style = 'Header.TLabel' , font = ('Segoe UI' , 14 )).pack (anchor = 'w' )
122+
123+ # --- Main Layout ---
77124 main_pane = ttk .PanedWindow (self .root , orient = 'horizontal' )
78125 main_pane .pack (fill = 'both' , expand = True , padx = 10 , pady = 10 )
79126
80127 left_panel = self ._create_left_panel (main_pane )
81128 main_pane .add (left_panel , weight = 1 )
82129
83130 right_panel = self ._create_right_panel (main_pane )
84- main_pane .add (right_panel , weight = 3 )
131+ main_pane .add (right_panel , weight = 4 ) # Higher weight to ensure plot takes most space
85132
86133 def _create_left_panel (self , parent ):
87134 panel = ttk .Frame (parent , width = 350 )
@@ -102,7 +149,7 @@ def _create_left_panel(self, parent):
102149 list_container = ttk .Frame (file_frame , style = 'TFrame' )
103150 list_container .grid (row = 1 , column = 0 , sticky = 'nsew' , padx = 10 , pady = (0 , 10 ))
104151
105- file_canvas = tk .Canvas (list_container , bg = self .CLR_INPUT_BG , highlightthickness = 0 , height = 120 )
152+ file_canvas = tk .Canvas (list_container , bg = self .CLR_INPUT_BG , highlightthickness = 0 , height = 150 )
106153 scrollbar = ttk .Scrollbar (list_container , orient = "vertical" , command = file_canvas .yview )
107154 self .file_list_frame = ttk .Frame (file_canvas , style = 'Input.TFrame' )
108155
@@ -137,6 +184,7 @@ def _create_left_panel(self, parent):
137184
138185 def _create_right_panel (self , parent ):
139186 panel = ttk .Frame (parent )
187+ # Using pack to make sure it fills the complete right side seamlessly
140188 container = ttk .LabelFrame (panel , text = 'Visualization' )
141189 container .pack (fill = 'both' , expand = True )
142190
@@ -186,7 +234,6 @@ def parse_file_custom(self, filepath):
186234 line_clean = line .strip ()
187235 if not line_clean : continue
188236
189- # Extract Metadata
190237 if 'Sample Area' in line_clean :
191238 metadata ['Sample Area' ] = line_clean .split (':' )[- 1 ].strip ()
192239 elif 'Sample Thickness' in line_clean :
@@ -204,21 +251,18 @@ def parse_file_custom(self, filepath):
204251 elif 'Stored:' in line_clean and metadata ['Measurement Date' ] == 'Unknown' :
205252 metadata ['Measurement Date' ] = line_clean .split ('Stored:' )[- 1 ].strip ()
206253
207- # Identify Headers
208254 if 'Point' in line_clean and ('Time' in line_clean or 'Drive' in line_clean ):
209255 sep = ',' if ',' in line_clean else '\t '
210256 headers = [h .strip () for h in line_clean .split (sep )]
211257 is_data = True
212258 continue
213259
214- # Collect Data
215260 if is_data :
216261 sep = ',' if ',' in line_clean else '\t '
217262 parts = line_clean .split (sep )
218263 if len (parts ) >= 3 :
219264 data_lines .append (line_clean )
220265
221- # Build DataFrame
222266 if data_lines :
223267 sep = ',' if filepath .lower ().endswith ('.csv' ) else '\t '
224268 df = pd .read_csv (io .StringIO ('\n ' .join (data_lines )), sep = sep , header = None )
@@ -247,11 +291,10 @@ def browse_files(self):
247291 self .file_data_cache [fp ] = {'df' : df , 'metadata' : metadata }
248292 self ._add_file_to_ui (fp )
249293
250- # Print metadata to console
251294 self .log (f"Loaded: { os .path .basename (fp )} " )
252295 self .log (f" Date: { metadata ['Measurement Date' ]} " )
253- self .log (f" Area: { metadata ['Sample Area' ]} cm2 | Thickness : { metadata ['Sample Thickness' ]} um" )
254- self .log (f" Voltage: { metadata ['Applied Voltage' ]} V | Frequency : { metadata ['Frequency (Hz)' ]} Hz\n " )
296+ self .log (f" Area: { metadata ['Sample Area' ]} cm2 | Thick : { metadata ['Sample Thickness' ]} um" )
297+ self .log (f" Voltage: { metadata ['Applied Voltage' ]} V | Freq : { metadata ['Frequency (Hz)' ]} Hz\n " )
255298
256299 self ._update_dropdowns (df .columns .tolist ())
257300
@@ -290,7 +333,6 @@ def _update_dropdowns(self, columns):
290333 self .x_col_cb ['values' ] = columns
291334 self .y_col_cb ['values' ] = columns
292335
293- # Intelligent Defaults for P-E loop
294336 if not current_x :
295337 default_x = next ((c for c in columns if 'Voltage' in c or 'Field' in c ), columns [0 ])
296338 self .x_col_cb .set (default_x )
@@ -320,14 +362,18 @@ def plot_data(self, event=None):
320362
321363 self .ax_main .set_xlabel (x_col , fontweight = 'bold' )
322364 self .ax_main .set_ylabel (y_col , fontweight = 'bold' )
323- self .ax_main .set_title ("Overlay P-E Hysteresis Plot" , fontweight = 'bold' )
365+ self .ax_main .set_title ("PE Hysteresis Overlay" , fontweight = 'bold' )
366+
367+ # Placing legend *inside* the plot area (loc='best' finds the clearest corner automatically)
368+ self .ax_main .legend (title = "Sample Files" , loc = 'best' )
324369
325- # Legend
326- self .ax_main .legend (title = "Sample Files" , bbox_to_anchor = (1.01 , 1 ), loc = 'upper left' )
327370 self .figure .tight_layout ()
328371 self .canvas .draw_idle ()
329372
373+
330374if __name__ == '__main__' :
375+ import multiprocessing
376+ multiprocessing .freeze_support ()
331377 root = tk .Tk ()
332378 app = PEPlotterAppGUI (root )
333379 root .mainloop ()
0 commit comments