/ 8_Utils / Python / RADAR_eq.py
RADAR_eq.py
  1  import tkinter as tk
  2  from tkinter import ttk, messagebox
  3  import math
  4  import numpy as np
  5  
  6  class RadarCalculatorGUI:
  7      def __init__(self, root):
  8          self.root = root
  9          self.root.title("RADAR Parameters Calculator")
 10          self.root.geometry("850x750")
 11          
 12          # Configure colors
 13          self.bg_color = '#f0f0f0'
 14          self.root.configure(bg=self.bg_color)
 15          
 16          # Create main container
 17          self.main_frame = ttk.Frame(root, padding="10")
 18          self.main_frame.pack(fill=tk.BOTH, expand=True)
 19          
 20          # Title
 21          title_label = tk.Label(self.main_frame, text="RADAR PARAMETERS CALCULATOR", 
 22                                font=('Arial', 16, 'bold'), bg=self.bg_color)
 23          title_label.pack(pady=10)
 24          
 25          # Create notebook for tabs
 26          self.notebook = ttk.Notebook(self.main_frame)
 27          self.notebook.pack(fill=tk.BOTH, expand=True, pady=10)
 28          
 29          # Input tab
 30          self.input_frame = ttk.Frame(self.notebook)
 31          self.notebook.add(self.input_frame, text="Input Parameters")
 32          
 33          # Results tab
 34          self.results_frame = ttk.Frame(self.notebook)
 35          self.notebook.add(self.results_frame, text="Results")
 36          
 37          # Create input fields
 38          self.create_input_fields()
 39          
 40          # Create results display
 41          self.create_results_display()
 42          
 43          # Create button frame (outside notebook)
 44          self.button_frame = ttk.Frame(self.main_frame)
 45          self.button_frame.pack(fill=tk.X, pady=10)
 46          
 47          # Create calculate button
 48          self.create_calculate_button()
 49          
 50          # Constants
 51          self.c = 3e8  # Speed of light in m/s
 52          
 53      def create_input_fields(self):
 54          """Create all input fields with labels and units"""
 55          
 56          # Create a canvas with scrollbar for input fields
 57          canvas = tk.Canvas(self.input_frame, borderwidth=0)
 58          scrollbar = ttk.Scrollbar(self.input_frame, orient="vertical", command=canvas.yview)
 59          scrollable_frame = ttk.Frame(canvas)
 60          
 61          scrollable_frame.bind(
 62              "<Configure>",
 63              lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
 64          )
 65          
 66          canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
 67          canvas.configure(yscrollcommand=scrollbar.set)
 68          
 69          # Input fields with default values
 70          inputs = [
 71              ("Frequency (GHz):", "10.0"),
 72              ("Pulse duration (μs):", "1.0"),
 73              ("PRF (Hz):", "1000"),
 74              ("Emitted power (dBm):", "30"),
 75              ("Antenna gain (dBi):", "20"),
 76              ("Receiver sensitivity (dBm):", "-90"),
 77              ("Radar cross section (m²):", "1.0"),
 78              ("System losses (dB):", "3"),
 79              ("Noise figure (dB):", "3"),
 80              ("Boltzmann constant (k) - optional:", "1.38e-23"),
 81              ("Temperature (K):", "290")
 82          ]
 83          
 84          self.entries = {}
 85          
 86          for i, (label, default) in enumerate(inputs):
 87              # Create a frame for each input row
 88              row_frame = ttk.Frame(scrollable_frame)
 89              row_frame.pack(fill=tk.X, pady=5)
 90              
 91              # Label
 92              ttk.Label(row_frame, text=label, width=30, anchor='w').pack(side=tk.LEFT, padx=5)
 93              
 94              # Entry
 95              entry = ttk.Entry(row_frame, width=20, font=('Arial', 10))
 96              entry.pack(side=tk.LEFT, padx=5)
 97              entry.insert(0, default)
 98              self.entries[label] = entry
 99          
100          # Additional notes
101          notes_label = tk.Label(scrollable_frame, 
102                                text="Note: All values must be numeric. Use point (.) for decimals.", 
103                                font=('Arial', 9, 'italic'), fg='gray')
104          notes_label.pack(pady=20)
105          
106          # Pack canvas and scrollbar
107          canvas.pack(side="left", fill="both", expand=True)
108          scrollbar.pack(side="right", fill="y")
109          
110      def create_calculate_button(self):
111          """Create the calculate button"""
112          calculate_btn = tk.Button(self.button_frame, 
113                                   text="CALCULATE RADAR PARAMETERS", 
114                                   command=self.calculate_parameters,
115                                   bg='#4CAF50', fg='white',
116                                   font=('Arial', 12, 'bold'),
117                                   padx=30, pady=10,
118                                   cursor='hand2')
119          calculate_btn.pack()
120          
121          # Bind hover effect
122          calculate_btn.bind("<Enter>", lambda e: calculate_btn.config(bg='#45a049'))
123          calculate_btn.bind("<Leave>", lambda e: calculate_btn.config(bg='#4CAF50'))
124          
125      def create_results_display(self):
126          """Create the results display area"""
127          
128          # Results title
129          title_label = tk.Label(self.results_frame, text="RADAR PERFORMANCE PARAMETERS", 
130                                font=('Arial', 14, 'bold'))
131          title_label.pack(pady=(20, 20))
132          
133          # Create frame for results with scrollbar
134          canvas = tk.Canvas(self.results_frame, borderwidth=0)
135          scrollbar = ttk.Scrollbar(self.results_frame, orient="vertical", command=canvas.yview)
136          scrollable_frame = ttk.Frame(canvas)
137          
138          scrollable_frame.bind(
139              "<Configure>",
140              lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
141          )
142          
143          canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
144          canvas.configure(yscrollcommand=scrollbar.set)
145          
146          # Results fields
147          results = [
148              ("Maximum detectable range:", "range_result"),
149              ("Range resolution:", "range_res_result"),
150              ("Maximum unambiguous range:", "max_range_result"),
151              ("Maximum detectable speed:", "speed_result"),
152              ("Speed resolution:", "speed_res_result"),
153              ("Doppler frequency resolution:", "doppler_res_result"),
154              ("Pulse width (s):", "pulse_width_result"),
155              ("Bandwidth (Hz):", "bandwidth_result"),
156              ("SNR (dB):", "snr_result")
157          ]
158          
159          self.results_labels = {}
160          
161          for i, (label, key) in enumerate(results):
162              # Create a frame for each result row
163              row_frame = ttk.Frame(scrollable_frame)
164              row_frame.pack(fill=tk.X, pady=10, padx=20)
165              
166              # Label
167              ttk.Label(row_frame, text=label, font=('Arial', 11, 'bold'), 
168                       width=30, anchor='w').pack(side=tk.LEFT)
169              
170              # Value
171              value_label = ttk.Label(row_frame, text="---", font=('Arial', 11), 
172                                     foreground='blue', anchor='w')
173              value_label.pack(side=tk.LEFT, padx=(20, 0))
174              self.results_labels[key] = value_label
175          
176          # Add separator
177          ttk.Separator(scrollable_frame, orient='horizontal').pack(fill=tk.X, pady=20)
178          
179          # Add explanatory note
180          note_text = """
181          NOTES:
182          • Maximum detectable range is calculated using the radar equation
183          • Range resolution = c × τ / 2, where τ is pulse duration
184          • Maximum unambiguous range = c / (2 × PRF)
185          • Maximum detectable speed = λ × PRF / 4
186          • Speed resolution = λ × PRF / (2 × N) where N is number of pulses (assumed 1)
187          • λ (wavelength) = c / f
188          """
189          
190          note_label = tk.Label(scrollable_frame, text=note_text, font=('Arial', 9), 
191                               justify=tk.LEFT, bg='#f8f9fa', relief='solid', 
192                               padx=10, pady=10)
193          note_label.pack(fill=tk.X, padx=20, pady=10)
194          
195          # Pack canvas and scrollbar
196          canvas.pack(side="left", fill="both", expand=True)
197          scrollbar.pack(side="right", fill="y")
198          
199      def get_float_value(self, entry, default=None):
200          """Safely get float value from entry"""
201          try:
202              return float(entry.get())
203          except ValueError:
204              return default
205              
206      def calculate_parameters(self):
207          """Perform all RADAR calculations"""
208          
209          try:
210              # Get all input values
211              f_ghz = self.get_float_value(self.entries["Frequency (GHz):"])
212              pulse_duration_us = self.get_float_value(self.entries["Pulse duration (μs):"])
213              prf = self.get_float_value(self.entries["PRF (Hz):"])
214              p_dbm = self.get_float_value(self.entries["Emitted power (dBm):"])
215              g_dbi = self.get_float_value(self.entries["Antenna gain (dBi):"])
216              sens_dbm = self.get_float_value(self.entries["Receiver sensitivity (dBm):"])
217              rcs = self.get_float_value(self.entries["Radar cross section (m²):"])
218              losses_db = self.get_float_value(self.entries["System losses (dB):"])
219              nf_db = self.get_float_value(self.entries["Noise figure (dB):"])
220              k = self.get_float_value(self.entries["Boltzmann constant (k) - optional:"])
221              temp = self.get_float_value(self.entries["Temperature (K):"])
222              
223              # Validate inputs
224              if None in [f_ghz, pulse_duration_us, prf, p_dbm, g_dbi, sens_dbm, rcs, losses_db, nf_db, temp]:
225                  messagebox.showerror("Error", "Please enter valid numeric values for all fields")
226                  return
227                  
228              # Convert units
229              f_hz = f_ghz * 1e9
230              pulse_duration_s = pulse_duration_us * 1e-6
231              wavelength = self.c / f_hz
232              
233              # Convert dB values to linear
234              p_linear = 10 ** ((p_dbm - 30) / 10)  # Convert dBm to Watts
235              g_linear = 10 ** (g_dbi / 10)
236              sens_linear = 10 ** ((sens_dbm - 30) / 10)
237              losses_linear = 10 ** (losses_db / 10)
238              nf_linear = 10 ** (nf_db / 10)
239              
240              # Calculate receiver noise power
241              if k is None:
242                  k = 1.38e-23  # Default Boltzmann constant
243              
244              # Calculate SNR
245              snr_linear = (p_linear * g_linear**2 * wavelength**2 * rcs) / (
246                  (4 * np.pi)**3 * sens_linear * losses_linear)
247              snr_db = 10 * math.log10(snr_linear) if snr_linear > 0 else float('-inf')
248              
249              # Maximum detectable range using radar equation
250              if snr_linear > 0:
251                  range_max = ((p_linear * g_linear**2 * wavelength**2 * rcs) / 
252                              ((4 * np.pi)**3 * sens_linear * losses_linear)) ** (1/4)
253              else:
254                  range_max = 0
255                  
256              # Range resolution
257              range_res = (self.c * pulse_duration_s) / 2
258              
259              # Maximum unambiguous range
260              max_unambiguous_range = self.c / (2 * prf)
261              
262              # Maximum detectable speed (using half the Nyquist sampling theorem)
263              max_speed = (wavelength * prf) / 4
264              
265              # Speed resolution (for a single pulse, approximate)
266              speed_res = max_speed  # For single pulse, resolution equals max speed
267              
268              # Doppler frequency resolution
269              doppler_res = 1 / pulse_duration_s
270              
271              # Bandwidth
272              bandwidth = 1 / pulse_duration_s
273              
274              # Update results display with formatted values
275              self.results_labels["range_result"].config(
276                  text=f"{range_max:.2f} m  ({range_max/1000:.2f} km)")
277              self.results_labels["range_res_result"].config(
278                  text=f"{range_res:.2f} m")
279              self.results_labels["max_range_result"].config(
280                  text=f"{max_unambiguous_range:.2f} m  ({max_unambiguous_range/1000:.2f} km)")
281              self.results_labels["speed_result"].config(
282                  text=f"{max_speed:.2f} m/s  ({max_speed*3.6:.2f} km/h)")
283              self.results_labels["speed_res_result"].config(
284                  text=f"{speed_res:.2f} m/s  ({speed_res*3.6:.2f} km/h)")
285              self.results_labels["doppler_res_result"].config(
286                  text=f"{doppler_res:.2f} Hz")
287              self.results_labels["pulse_width_result"].config(
288                  text=f"{pulse_duration_s:.2e} s")
289              self.results_labels["bandwidth_result"].config(
290                  text=f"{bandwidth:.2e} Hz")
291              self.results_labels["snr_result"].config(
292                  text=f"{snr_db:.2f} dB")
293              
294              # Switch to results tab
295              self.notebook.select(1)
296              
297              # Show success message
298              messagebox.showinfo("Success", "Calculation completed successfully!")
299              
300          except Exception as e:
301              messagebox.showerror("Calculation Error", f"An error occurred during calculation:\n{str(e)}")
302              
303  def main():
304      root = tk.Tk()
305      app = RadarCalculatorGUI(root)
306      root.mainloop()
307  
308  if __name__ == "__main__":
309      main()