1+ # Import necessary libraries
12import numpy as np
23import math
34import lxml .etree as et
78from PyQt5 .QtWidgets import QApplication , QWidget , QLabel , QLineEdit , QPushButton , QVBoxLayout , QFileDialog , QMessageBox , QDialog , QDialogButtonBox , QGridLayout
89from cluster_point_process_functions import primprocess , add_types , secprocess , add_vertex_events
910
10- # Class used for setting type_ratios in the GUI for the cluster point process
11+ # source venv/bin/activate
12+ # python3 cluster_point_process.py
13+
14+ # This script provides a GUI to configure and generate synthetic 911 call data using a cluster point process model.
15+ # It integrates primary and secondary event generation with user-configurable parameters.
16+
17+
18+ # ------------------------------
19+ # Class: TypeRatioDialog
20+ # ------------------------------
21+ # Provides a dialog to input ratios for different 911 call types (Law, EMS, Fire).
1122class TypeRatioDialog (QDialog ):
1223 def __init__ (self ):
1324 super ().__init__ ()
@@ -17,7 +28,8 @@ def __init__(self):
1728
1829 def init_ui (self ):
1930 layout = QVBoxLayout ()
20-
31+
32+ # Labels and input fields for call type ratios
2133 self .labels = ["Law" , "EMS" , "Fire" ]
2234 self .entries = {}
2335 for label_text in self .labels :
@@ -28,6 +40,7 @@ def init_ui(self):
2840 layout .addWidget (label )
2941 layout .addWidget (entry )
3042
43+ # OK and Cancel buttons
3144 button_box = QDialogButtonBox (QDialogButtonBox .Cancel | QDialogButtonBox .Ok )
3245 button_box .accepted .connect (self .accept )
3346 button_box .rejected .connect (self .on_cancel_clicked )
@@ -37,6 +50,7 @@ def init_ui(self):
3750 self .backup_initial_values () # Save initial values
3851
3952 def accept (self ):
53+ """Validates and processes user inputs when OK is clicked."""
4054 invalid_fields = []
4155 for label , entry in self .entries .items ():
4256 text = entry .text ().strip ()
@@ -51,18 +65,22 @@ def accept(self):
5165 invalid_fields .append (label )
5266
5367 if invalid_fields :
68+ # Show an error message for invalid inputs
5469 error_message = "Invalid or empty values in the following fields:\n "
5570 for field in invalid_fields :
5671 error_message += f"- { field } \n "
5772 QMessageBox .warning (self , "Input Error" , error_message )
5873 else :
74+ # Store validated results
5975 self .result = {label : float (entry .text ().strip ()) for label , entry in self .entries .items ()}
6076 super ().accept ()
6177
6278 def backup_initial_values (self ):
79+ """Stores initial values to restore them if the user cancels."""
6380 self .initial_values = {label : entry .text () for label , entry in self .entries .items ()}
6481
6582 def on_cancel_clicked (self ):
83+ """Restores initial values if the dialog is canceled."""
6684 for label , entry in self .entries .items ():
6785 current_text = entry .text ().strip ()
6886 if not current_text : # If the field is empty, reset to initial value
@@ -71,19 +89,25 @@ def on_cancel_clicked(self):
7189
7290 self .reject () # Close the dialog
7391
74- # Class used for setting prototype values in the GUI for the cluster point process
92+ # ------------------------------
93+ # Class: PrototypesDialog
94+ # ------------------------------
95+ # Allows the user to define prototype configurations for secondary event generation.
7596class PrototypesDialog (QDialog ):
7697 def __init__ (self ):
7798 super ().__init__ ()
7899 self .setWindowTitle ("Set Prototypes" )
79100 self .init_ui ()
80101
81102 def init_ui (self ):
103+ """Sets up the layout and input fields for prototype configurations."""
82104 layout = QGridLayout ()
83105 self .entries = {}
84106
107+ # Labels for prototype parameters
85108 labels = ["mu_r:" , "sdev_r:" , "mu_intensity:" , "sdev_intensity:" ]
86109
110+ # Add input fields for four prototypes
87111 for i in range (4 ):
88112 prototype_label = QLabel (f"Prototype { i } :" )
89113 layout .addWidget (prototype_label , i , 0 )
@@ -94,6 +118,7 @@ def init_ui(self):
94118 layout .addWidget (QLabel (label ), i , j * 2 + 1 )
95119 layout .addWidget (entry , i , j * 2 + 2 )
96120
121+ # OK and Cancel buttons
97122 button_box = QDialogButtonBox (QDialogButtonBox .Ok | QDialogButtonBox .Cancel )
98123 button_box .accepted .connect (self .accept )
99124 button_box .rejected .connect (self .reject )
@@ -102,6 +127,7 @@ def init_ui(self):
102127 self .setLayout (layout )
103128
104129 def accept (self ):
130+ """Validates and processes user inputs when OK is clicked."""
105131 invalid_fields = []
106132 for label , entry in self .entries .items ():
107133 text = entry .text ().strip ()
@@ -116,6 +142,7 @@ def accept(self):
116142 invalid_fields .append (label )
117143
118144 if invalid_fields :
145+ # Show an error message for invalid inputs
119146 error_message = "Invalid or empty values in the following fields:\n "
120147 for field in invalid_fields :
121148 error_message += f"- { field } \n "
@@ -129,40 +156,43 @@ def __init__(self):
129156 super ().__init__ ()
130157 self .setWindowTitle ("911 Call Data Generator" )
131158 # Input dialog for type_ratio and prototypes
159+ # These will allow the user to input custom type ratios and prototypes via separate dialogs
132160 self .type_ratio_dialog = None
133161 self .prototypes_dialog = None
134162
135163 # Dictionary for holding type_ratio and prototypes
136- self .type_ratios = {}
137- self .prototypes = {}
164+ self .type_ratios = {} # Example: {'Law': 0.64, 'EMS': 0.18, 'Fire': 0.18}
165+ self .prototypes = {} # Example: {0: {'mu_r': 0.0005, 'sdev_r': 0.0001}, ...}
138166
139167 self .init_ui ()
140168
141169 def init_ui (self ):
142170 layout = QVBoxLayout ()
143171
144- # Graph File input field
172+ # Input field for selecting the graph file (.graphml)
145173 graph_file_label = QLabel ("Select Graph File (.graphml):" )
146174 self .graph_file_label = QLineEdit ()
147175 layout .addWidget (graph_file_label )
148176 layout .addWidget (self .graph_file_label )
149177
178+ # Button to browse and select a graph file
150179 graph_file_button = QPushButton ("Browse" )
151180 graph_file_button .clicked .connect (self .browse_file )
152181 layout .addWidget (graph_file_button )
153182
154- # Input labels for GUI
183+ # Input labels and fields for various parameters
184+ # These fields collect user inputs for parameters like event timing, duration, etc.
155185 self .labels = [
156- "Graph ID:" ,
157- "First (seconds):" ,
158- "Last (seconds):" ,
159- "Mean Time Interval (seconds):" ,
160- "Dead Time after Event (seconds):" ,
161- "Mean Call Interval after incident (seconds):" ,
162- "Mean Duration (seconds):" ,
163- "Minimum Duration (seconds):" ,
164- "Mean Patience Time (seconds):" ,
165- "Mean On-Site Time (seconds):" ,
186+ "Graph ID:" , # Insert graph labels
187+ "First (seconds):" , # The time of the first event or call in the dataset, measured in seconds from a reference point (e.g., the start of the logging period).
188+ "Last (seconds):" , # The time of the last event or call in the dataset, measured in seconds from the same reference point.
189+ "Mean Time Interval (seconds):" , # The average time interval between consecutive 911 calls, measured in seconds.
190+ "Dead Time after Event (seconds):" , # The average time period after an event during which no new events or calls are expected to occur, measured in seconds. This could represent a cooldown period or a time when the system is not actively logging new calls.
191+ "Mean Call Interval after incident (seconds):" , # The average time interval between the end of an incident and the next 911 call, measured in seconds. This could be used to model the frequency of follow-up calls or related incidents.
192+ "Mean Duration (seconds):" , # The average duration of a 911 call or incident, measured in seconds. This includes the time from the start of the call to its conclusion.
193+ "Minimum Duration (seconds):" , # The shortest duration of a 911 call or incident in the dataset, measured in seconds. This could be used to filter out very short or incomplete calls.
194+ "Mean Patience Time (seconds):" , # The average time a caller is willing to wait on hold before hanging up, measured in seconds. This metric is important for understanding caller behavior and optimizing call center operations.
195+ "Mean On-Site Time (seconds):" , # The average time emergency responders spend on-site at an incident, measured in seconds. This includes the time from arrival at the scene to departure.
166196 ]
167197
168198 self .entries = {}
@@ -174,24 +204,27 @@ def init_ui(self):
174204 layout .addWidget (label )
175205 layout .addWidget (entry )
176206
177- # Buttons for opening dialogs ( Type Ratio, Prototype)
207+ # Button to open the "Set Type Ratios" dialog
178208 set_type_ratio_button = QPushButton ("Set Type Ratios" )
179209 set_type_ratio_button .clicked .connect (self .show_type_ratio_dialog )
180210 layout .addWidget (set_type_ratio_button )
181211
212+ # Button to open the "Set Prototypes" dialog
182213 set_prototypes_button = QPushButton ("Set Prototypes" )
183214 set_prototypes_button .clicked .connect (self .show_prototypes_dialog )
184215 layout .addWidget (set_prototypes_button )
185216
186- # Submit button
217+ # Button to start the event generation process
187218 generate_button = QPushButton ("Generate Events" )
188219 generate_button .clicked .connect (self .generate_events )
189220 layout .addWidget (generate_button )
190221
222+ # Set the layout of the UI
191223 self .setLayout (layout )
192224 self .show ()
193225
194226 def show_type_ratio_dialog (self ):
227+ # Opens a dialog to allow the user to set type ratios
195228 if not self .type_ratio_dialog :
196229 self .type_ratio_dialog = TypeRatioDialog ()
197230
@@ -200,14 +233,16 @@ def show_type_ratio_dialog(self):
200233 self .type_ratios = {label : float (entry .text ()) for label , entry in self .type_ratio_dialog .entries .items ()}
201234
202235 def show_prototypes_dialog (self ):
236+ # Opens a dialog to allow the user to set prototypes
203237 if not self .prototypes_dialog :
204238 self .prototypes_dialog = PrototypesDialog ()
205239
206240 if self .prototypes_dialog .exec_ () == QDialog .Accepted :
241+ # Extract and reformat prototype values
207242 prototypes_entries = self .prototypes_dialog .entries .items ()
208243 prototypes_values = {label : float (entry .text ()) for label , entry in prototypes_entries }
209244
210- # Reformatting the prototypes dictionary
245+ # Organize prototype data into nested dictionaries
211246 self .prototypes = {}
212247 for label , value in prototypes_values .items ():
213248 split_label = label .split (' - ' )
@@ -220,7 +255,9 @@ def show_prototypes_dialog(self):
220255 self .prototypes [prototype_num ][var_name ] = value
221256
222257 # Function that allows user to browse local files
258+
223259 def browse_file (self ):
260+ # Allows the user to browse and select a .graphml file
224261 options = QFileDialog .Options ()
225262 options |= QFileDialog .DontUseNativeDialog
226263 file_dialog = QFileDialog ()
@@ -242,9 +279,11 @@ def browse_file(self):
242279 # Handles invalid inputs (string instead of int, wrong file
243280 # type but not invalid logic)
244281 def generate_events (self ):
282+ # Validates inputs and triggers the event generation process
245283 error_message = ""
246284 invalid_fields = []
247285
286+ # Validate user input fields
248287 for label_text , entry in self .entries .items ():
249288 if label_text != "Select Region Grid (.graphml):" :
250289 text = entry .text ().strip ()
@@ -256,27 +295,30 @@ def generate_events(self):
256295 except ValueError :
257296 invalid_fields .append (label_text )
258297
298+ # Check if type ratios and prototypes are set
259299 if not self .type_ratios and not self .prototypes :
260300 error_message += "Please set Type Ratio and Prototype values before generating events."
261301 elif not self .type_ratios :
262302 error_message += "Please set Type Ratio values before generating events."
263303 elif not self .prototypes :
264304 error_message += "Please set Prototype values before generating events."
265305
266- # Error handling
306+ # Validate the graph file input
267307 graph_file = self .graph_file_label .text ().strip ()
268308 if not graph_file :
269309 invalid_fields .append ("Select Region Grid (.graphml):" )
270310 elif not graph_file .endswith (".graphml" ):
271311 invalid_fields .append ("Select Region Grid (.graphml) must be a .graphml file." )
272312
313+ # Handle errors and display warnings
273314 if invalid_fields :
274315 error_message = "Invalid or empty values in the following fields: (float values only)\n "
275316 for field in invalid_fields :
276317 error_message += f"- { field } \n "
277318
278- # Event handling
319+ # Proceed with event generation if inputs are valid
279320 try :
321+ # Extract user inputs and use them for event generation
280322 if error_message :
281323 raise ValueError (error_message )
282324
@@ -297,13 +339,16 @@ def generate_events(self):
297339 # PRIMARY EVENTS
298340 ###########################################################################
299341 # Start your event generation process here based on the valid inputs
300- graph = nx .read_graphml ('../../gis2graph/graph_files/spd.graphml' )
342+ graph_file_path = os .path .join ('..' , '..' , 'gis2graph' , 'graph_files' , 'spd.graphml' )
343+ graph = nx .read_graphml (graph_file_path )
301344 graph_id = str (self .entries ["Graph ID:" ].text ())
302345 graph_attribute = graph .nodes [graph_id ]['segments' ]
303346 graph_grid = np .array (eval (graph_attribute ))
304347
348+
349+
305350 # Seed numpy random number to get consistent results
306- np .random .seed (20 )
351+ np .random .seed (20 ) ## change it when i make the test set to something else
307352
308353 # Call primprocess using the inputs from the interface
309354 incidents = primprocess (first , last , mu , pp_dead_t , graph_grid )
0 commit comments