101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469 | def create_GUI(system='', shells=1, resolution=256, reference='standard'):
''' Launches a tkinter graphical user interface with three visual panels and various sliders to change the system parameters in real time.
The left panel is the colliding wind nebula with the current parameters given by the sliders. The centre panel is a reference image. The
right panel is the difference of the reference image and model image: model - reference.
Parameters
----------
system : str or dict
If str: one of the pre-defined systems in the `systems.py` file (which are dictionaries that contain system parameters).
If dict: a user-defined system dictionary. Take care to make sure all of the necessary keywords are defined.
shells : int
The number of dust shells (orbits) to begin the GUI model with.
resolution : int
The number of pixels along one axis of the (square) model/reference image. If a named reference string is supplied, or a list of arrays is supplied, this
input will be overwritten.
reference : str or list of j/np.arrays
The reference image for the middle panel in the GUI.
If a string, should be one of {'standard', 'VISIR', 'JWST'}.
- 'standard' will just generate a model nebula with the given system+shells parameters at the specified resolution.
- 'VISIR' and 'JWST' will use the Apep data of choice, provided that data is in the correct location on the user's file system.
If a list of arrays, it should be arranged as `[X_ref, Y_ref, H_ref]` where `X_ref` is a meshgrid of x angular coordinates (analogously for Y_ref),
and `H_ref` is a resolution x resolution array of pixel values.
'''
# set the system parameters to a predefined system or use the user-defined params
if type(system) == dict:
starcopy = system
elif type(system) == str:
if system != '':
starcopy = eval(f'wrb.{system}.copy()')
else:
starcopy = wrb.WR104.copy()
# manually set the number of shells to generate the first model panel with
starcopy['n_orbits'] = shells
# determine the necessary resolution for the gui image panels
if type(reference) == str:
ref_type = 'predefined'
if reference == 'standard':
pass # we'll get these parameters later after doing the first model eval
elif reference == 'VISIR':
print('Manually setting resolution to 600 pixels.')
resolution = 600 # VISIR
elif reference == 'JWST':
print('Manually setting resolution to 898 pixels.')
resolution = 898 # JWST
elif jnp.size(reference[2]) > 1:
ref_type = 'user-defined'
resolution = len(reference[2])
if jnp.size(reference[2]) != resolution**2:
raise(ValueError, 'The input reference image is not a square array, but must be. Please try again with a square reference image.')
# we need to redefine the smooth histogram functions to have the necessary resolution
@jit
def smooth_histogram2d(particles, weights, stardata):
im_size = resolution
x = particles[0, :]
y = particles[1, :]
xbound, ybound = jnp.max(jnp.abs(x)), jnp.max(jnp.abs(y))
bound = jnp.max(jnp.array([xbound, ybound])) * (1. + 2. / im_size)
xedges, yedges = jnp.linspace(-bound, bound, im_size+1), jnp.linspace(-bound, bound, im_size+1)
return gm.smooth_histogram2d_base(particles, weights, stardata, xedges, yedges, im_size)
@jit
def smooth_histogram2d_w_bins(particles, weights, stardata, xbins, ybins):
im_size = resolution
return gm.smooth_histogram2d_base(particles, weights, stardata, xbins, ybins, im_size)
# generate the first model evaluation
particles, weights = gm.gui_funcs[int(starcopy['n_orbits']) - 1](starcopy)
X, Y, H_original = smooth_histogram2d(particles, weights, starcopy)
H_original = gm.add_stars(X[0, :], Y[:, 0], H_original, starcopy)
# now obtain the reference image data
if ref_type == 'predefined':
if reference == 'standard':
particles, weights = gm.dust_plume(starcopy)
X_ref, Y_ref, H_ref = X.copy(), Y.copy(), H_original.copy()
elif reference == 'VISIR':
X_ref, Y_ref, H_ref = Apep_VISIR_reference()
elif reference == 'JWST':
X_ref, Y_ref, H_ref = Apep_JWST_reference()
elif ref_type == 'user-defined':
X_ref, Y_ref, H_ref = reference
# ---------- now set up the tkinter window ------------ #
root = tkinter.Tk()
# root.protocol("WM_DELETE_WINDOW", root.destroy())
root.wm_title("xenomorph -- Graphical User Interface")
titles = ['Model', 'Reference', 'Difference']
w = 1/3.08
fig, axes = plt.subplots(figsize=(12, 4), ncols=3, gridspec_kw={'wspace':0, 'width_ratios':[w, w, 1-2*w]})
mesh = axes[0].pcolormesh(X, Y, H_original, cmap='hot')
axes[0].set(aspect='equal', xlabel='Relative RA (")', ylabel='Relative Dec (")', title=titles[0])
reference_mesh = axes[1].pcolormesh(X_ref, Y_ref, H_ref, cmap='hot')
maxside2 = np.max(np.abs(np.array([X_ref, Y_ref])))
axes[1].set(xlim=(-maxside2, maxside2), ylim=(-maxside2, maxside2))
H_ref_ravel = H_ref.ravel()
norm = colors.Normalize(vmin=-1., vmax=1.)
diff_mesh = axes[2].pcolormesh(X_ref, Y_ref, H_original - H_ref, cmap='seismic', norm=norm)
for i in range(1, 3):
axes[i].set(aspect='equal', xlabel='Relative RA (")', title=titles[i])
axes[i].tick_params(axis='y',
which='both',
left=False,
labelleft=False)
the_divider = make_axes_locatable(axes[2])
color_axis = the_divider.append_axes("right", size="5%", pad=0.1)
fig.colorbar(diff_mesh, cax=color_axis)
for i in range(2):
axes[i].set_facecolor('k')
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.destroy)
# ---------- tkinter admin practically done. now define sliders and update routine ------------ #
def update_frequency(param, new_val):
''' This function updates the panels of the GUI every time a slider is moved.
Parameters
----------
param : str
The system dictionary keyword that is being changed.
new_val : float/int
The new value to set the param argument to.
'''
starcopy[param] = float(new_val)
particles, weights = gm.gui_funcs[int(starcopy['n_orbits']) - 1](starcopy)
X_new, Y_new, H = smooth_histogram2d(particles, weights, starcopy)
H = gm.add_stars(X_new[0, :], Y_new[:, 0], H, starcopy)
new_H = H.ravel()
mesh.update({'array':new_H})
X_diff, Y_diff, H_diff = smooth_histogram2d_w_bins(particles, weights, starcopy, X_ref[0, :], Y_ref[:, 0])
H_diff = gm.add_stars(X_diff[0, :], Y_diff[:, 0], H_diff, starcopy)
H_diff = H_diff.ravel()
diff_mesh.update({'array':H_diff - H_ref_ravel})
new_coords = mesh._coordinates
new_coords[:, :, 0] = X_new
new_coords[:, :, 1] = Y_new
mesh._coordinates = new_coords
maxside1 = np.max(np.abs(np.array([X_new, Y_new])))
axes[0].set(xlim=(-maxside1, maxside1), ylim=(-maxside1, maxside1))
diff_maxside = np.max([maxside1, np.max(np.abs(np.array([X_ref, Y_ref])))])
axes[2].set(xlim=(-diff_maxside, diff_maxside), ylim=(-diff_maxside, diff_maxside))
# required to update canvas and attached toolbar!
canvas.draw()
# define sliders below:
ecc = tkinter.Scale(root, from_=0, to=0.99, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('eccentricity', v), label="Eccentricity", resolution=0.01)
ecc.set(starcopy['eccentricity'])
inc = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('inclination', v), label="Inclination (deg)", resolution=1.)
inc.set(starcopy['inclination'])
asc_node = tkinter.Scale(root, from_=0, to=360, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('asc_node', v), label="Asc. Node (deg)", resolution=1.)
asc_node.set(starcopy['asc_node'])
arg_peri = tkinter.Scale(root, from_=0, to=360, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('arg_peri', v), label="Arg. of Peri. (deg)", resolution=1.)
arg_peri.set(starcopy['arg_peri'])
opang = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('open_angle', v), label="Open Angle (deg)", resolution=1.)
opang.set(starcopy['open_angle'])
m1 = tkinter.Scale(root, from_=5, to=50, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('m1', v), label="WR Mass (M_o)", resolution=0.1)
m1.set(starcopy['m1'])
m2 = tkinter.Scale(root, from_=5, to=50, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('m2', v), label="Secondary Mass (M_o)", resolution=0.1)
m2.set(starcopy['m2'])
phase = tkinter.Scale(root, from_=0.01, to=1.5, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('phase', v), label="Phase", resolution=0.01)
phase.set(starcopy['phase'])
n_orb = tkinter.Scale(root, from_=1, to=20, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('n_orbits', v), label="Num. Shells")
n_orb.set(starcopy['n_orbits'])
turnon = tkinter.Scale(root, from_=-180, to=0, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('turn_on', v), label="Turn On (deg)", resolution=1)
turnon.set(starcopy['turn_on'])
turnoff = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('turn_off', v), label="Turn Off (deg)", resolution=1)
turnoff.set(starcopy['turn_off'])
distance = tkinter.Scale(root, from_=1e3, to=1e4, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('distance', v), label="Distance (pc)", resolution=10)
distance.set(starcopy['distance'])
ws1 = tkinter.Scale(root, from_=0, to=5e3, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('windspeed1', v), label="WR v_wind (km/s)", resolution=5)
ws1.set(starcopy['windspeed1'])
ws2 = tkinter.Scale(root, from_=0, to=5e3, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('windspeed2', v), label="Secondary v_wind (km/s)", resolution=5)
ws2.set(starcopy['windspeed2'])
period = tkinter.Scale(root, from_=0, to=300, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('period', v), label="Orb. Period (yr)", resolution=2)
period.set(starcopy['period'])
osd = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('orb_sd', v), label="Orb. Var. SD (deg)", resolution=1)
osd.set(starcopy['orb_sd'])
oamp = tkinter.Scale(root, from_=-1, to=1, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('orb_amp', v), label="Orb. Var. Amp", resolution=0.01)
oamp.set(starcopy['orb_amp'])
orbmin = tkinter.Scale(root, from_=0, to=360, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('orb_min', v), label="Orb. Min. (deg)", resolution=0.1)
orbmin.set(starcopy['orb_min'])
azsd = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('az_sd', v), label="Az. Var. SD (deg)", resolution=0.1)
azsd.set(starcopy['az_sd'])
azamp = tkinter.Scale(root, from_=-1, to=1, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('az_amp', v), label="Az. Var. Amp", resolution=0.01)
azamp.set(starcopy['az_amp'])
azmin = tkinter.Scale(root, from_=0, to=360, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('az_min', v), label="Az. Min. (deg)", resolution=0.1)
azmin.set(starcopy['az_min'])
compincl = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_incl', v), label="Tert. Incl. (deg)", resolution=1.)
compincl.set(starcopy['comp_incl'])
compaz = tkinter.Scale(root, from_=0, to=360, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_az', v), label="Tert. Azimuth. (deg)", resolution=1.)
compaz.set(starcopy['comp_az'])
compopen = tkinter.Scale(root, from_=0, to=180, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_open', v), label="Tert. Open Angle (deg)", resolution=0.1)
compopen.set(starcopy['comp_open'])
compreduc = tkinter.Scale(root, from_=0, to=2, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_reduction', v), label="Tert. Photodissociation", resolution=0.01)
compreduc.set(starcopy['comp_reduction'])
compplume = tkinter.Scale(root, from_=0, to=2, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_plume', v), label="Companion Plume", resolution=0.01)
compplume.set(starcopy['comp_plume'])
histmax = tkinter.Scale(root, from_=1, to=0, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('histmax', v), label="Max Brightness", resolution=0.01)
histmax.set(starcopy['histmax'])
sigma = tkinter.Scale(root, from_=0.01, to=10, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('sigma', v), label="Gaussian Blur (pix)", resolution=0.01)
sigma.set(starcopy['sigma'])
oblate = tkinter.Scale(root, from_=0., to=1, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('oblate', v), label="Plume Oblateness", resolution=0.01)
oblate.set(starcopy['oblate'])
nuc_dist = tkinter.Scale(root, from_=0.1, to=1e3, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('nuc_dist', v), label="Nuc. Dist. (AU)", resolution=0.01)
nuc_dist.set(starcopy['nuc_dist'])
opt_thin_dist = tkinter.Scale(root, from_=0.2, to=1e3, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('opt_thin_dist', v), label="Opt. Thin Dist.", resolution=0.01)
opt_thin_dist.set(starcopy['opt_thin_dist'])
lum_power = tkinter.Scale(root, from_=0.001, to=2., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('lum_power', v), label="Lum. Power", resolution=0.01)
lum_power.set(starcopy['lum_power'])
spin_inc = tkinter.Scale(root, from_=0.001, to=90., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('spin_inc', v), label="Spin Incl. (deg)", resolution=0.5)
spin_inc.set(starcopy['spin_inc'])
spin_Omega = tkinter.Scale(root, from_=0.001, to=360., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('spin_Omega', v), label="Spin Arg. (deg)", resolution=1)
spin_Omega.set(starcopy['spin_Omega'])
star1amp = tkinter.Scale(root, from_=0.001, to=10., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star1amp', v), label="WR Star Amp.", resolution=0.001)
star1amp.set(starcopy['star1amp'])
star2amp = tkinter.Scale(root, from_=0.001, to=10., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star2amp', v), label="Sec. Star Amp.", resolution=0.001)
star2amp.set(starcopy['star2amp'])
star3amp = tkinter.Scale(root, from_=0.001, to=10., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star3amp', v), label="Tert. Star Amp.", resolution=0.001)
star3amp.set(starcopy['star3amp'])
star1sd = tkinter.Scale(root, from_=-3., to=1., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star1sd', v), label="WR Star SD (log px)", resolution=0.001)
star1sd.set(starcopy['star1sd'])
star2sd = tkinter.Scale(root, from_=-3., to=1., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star2sd', v), label="Sec. Star SD (log px)", resolution=0.001)
star2sd.set(starcopy['star2sd'])
star3sd = tkinter.Scale(root, from_=-3., to=1., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star3sd', v), label="Tert. Star SD (log px)", resolution=0.001)
star3sd.set(starcopy['star3sd'])
star3dist = tkinter.Scale(root, from_=1., to=20000., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('star3dist', v), label="Tert. Star Dist. (AU)", resolution=1.)
star3dist.set(starcopy['star3dist'])
gradual_turn = tkinter.Scale(root, from_=0.01, to=180., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('gradual_turn', v), label="Gradual Turn", resolution=0.1)
gradual_turn.set(starcopy['gradual_turn'])
comp_plume_sd = tkinter.Scale(root, from_=0.01, to=180., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_plume_sd', v), label="comp_plume_sd", resolution=0.1)
comp_plume_sd.set(starcopy['comp_plume_sd'])
comp_plume_max = tkinter.Scale(root, from_=0., to=540., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('comp_plume_max', v), label="comp_plume_max", resolution=0.1)
comp_plume_max.set(starcopy['comp_plume_max'])
accel_rate = tkinter.Scale(root, from_=-5, to=1, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('accel_rate', v), label="Log Accel. Rate", resolution=0.01)
accel_rate.set(starcopy['accel_rate'])
term_windspeed = tkinter.Scale(root, from_=0., to=5e3, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('term_windspeed', v), label="Terminal v_wind", resolution=5.)
term_windspeed.set(starcopy['term_windspeed'])
windspeed_polar = tkinter.Scale(root, from_=0, to=3600, orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('windspeed_polar', v), label="Polar v_wind", resolution=10)
windspeed_polar.set(starcopy['windspeed_polar'])
aniso_vel_mult = tkinter.Scale(root, from_=-10, to=0., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('aniso_vel_mult', v), label="Aniso. Vel. Mult.", resolution=0.05)
aniso_vel_mult.set(starcopy['aniso_vel_mult'])
aniso_vel_power = tkinter.Scale(root, from_=0., to=5., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('aniso_vel_power', v), label="Aniso. Vel. Pow.", resolution=0.01)
aniso_vel_power.set(starcopy['aniso_vel_power'])
open_angle_polar = tkinter.Scale(root, from_=0, to=180., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('open_angle_polar', v), label="Polar OA", resolution=1)
open_angle_polar.set(starcopy['open_angle_polar'])
aniso_OA_mult = tkinter.Scale(root, from_=-10., to=0., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('aniso_OA_mult', v), label="Aniso. OA Mult.", resolution=0.05)
aniso_OA_mult.set(starcopy['aniso_OA_mult'])
aniso_OA_power = tkinter.Scale(root, from_=0, to=5., orient=tkinter.HORIZONTAL,
command=lambda v: update_frequency('aniso_OA_power', v), label="Aniso. OA Pow.", resolution=0.01)
aniso_OA_power.set(starcopy['aniso_OA_power'])
# arrange sliders in this order:
sliders = [ecc, inc, asc_node, arg_peri, phase, period, m1, m2,
distance, ws1, turnon, turnoff, gradual_turn, opang, nuc_dist, n_orb,
osd, orbmin, oamp, azsd, azmin, azamp, accel_rate, term_windspeed,
compopen, compplume, compreduc, compincl, compaz, sigma, histmax, lum_power,
spin_inc, spin_Omega, windspeed_polar, aniso_vel_mult, aniso_vel_power, open_angle_polar, aniso_OA_mult,
aniso_OA_power, star1amp, star1sd, star2amp, star2sd, star3amp, star3sd, star3dist
]
# and finally place all buttons and sliders in correct position
num_in_row = 8
toolbar.grid(row=0, columnspan=num_in_row)
canvas.get_tk_widget().grid(row=1, column=0, columnspan=num_in_row)
for i, slider in enumerate(sliders):
row = int(np.floor(i / num_in_row)) + 2
col = i%(num_in_row)
slider.grid(row=row, column=col)
button_quit.grid(row=row+1, column=num_in_row//2)
root.mainloop() # runs the tkinter instance
|