| Conditions | 28 |
| Total Lines | 284 |
| Code Lines | 179 |
| Lines | 47 |
| Ratio | 16.55 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like mutis.astro.KnotsId2dGUI() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # Licensed under a 3-clause BSD style license - see LICENSE |
||
| 215 | def KnotsId2dGUI(mod, use_arrows=False, arrow_pos=1.0): |
||
| 216 | """ |
||
| 217 | Prompt a GUI to select identified knots and alter their label, reprenting their 2D |
||
| 218 | spatial distribution in different times. |
||
| 219 | |||
| 220 | It can be used inside jupyter notebooks, using '%matplotlib widget' first. |
||
| 221 | |||
| 222 | Parameters: |
||
| 223 | ----------- |
||
| 224 | mod : :pd.DataFrame: |
||
| 225 | pandas.DataFrame containing every knot, with at least columns |
||
| 226 | 'label', 'date', 'X', 'Y', 'Flux (Jy)'. |
||
| 227 | |||
| 228 | Returns: |
||
| 229 | -------- |
||
| 230 | mod : :pd.DataFrame: |
||
| 231 | pandas.DataFrame containing every knot with their altered labels, with |
||
| 232 | at least columns 'label', 'date', 'X', 'Y', 'Flux (Jy)'. |
||
| 233 | """ |
||
| 234 | |||
| 235 | mod = mod.copy() |
||
| 236 | |||
| 237 | knots = dict(tuple(mod.groupby('label'))) |
||
| 238 | knots_names = list(knots.keys()) |
||
| 239 | knots_values = list(knots.values()) |
||
| 240 | knots_jyears = {k:Time(knots[k]['date'].to_numpy()).jyear for k in knots} |
||
| 241 | knots_X = {k:knots[k]['X'].to_numpy() for k in knots} |
||
| 242 | knots_Y = {k:knots[k]['Y'].to_numpy() for k in knots} |
||
| 243 | |||
| 244 | |||
| 245 | from matplotlib.widgets import Slider, Button, TextBox, RectangleSelector |
||
| 246 | |||
| 247 | out = get_output() |
||
| 248 | |||
| 249 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,8)) |
||
| 250 | |||
| 251 | lineas = list() |
||
| 252 | flechas = list() |
||
| 253 | textos = list() |
||
| 254 | |||
| 255 | def draw_all(val=2008): |
||
| 256 | nonlocal lineas, flechas, textos |
||
| 257 | |||
| 258 | # instead of clearing the whole axis, remove artists |
||
| 259 | for linea in lineas: |
||
| 260 | if linea is not None: |
||
| 261 | linea.remove() |
||
| 262 | for texto in textos: |
||
| 263 | if texto is not None: |
||
| 264 | texto.remove() |
||
| 265 | for flecha in flechas: |
||
| 266 | if flecha is not None: |
||
| 267 | flecha.remove() |
||
| 268 | ax.set_prop_cycle(None) |
||
| 269 | |||
| 270 | lineas = list() |
||
| 271 | flechas = list() |
||
| 272 | textos = list() |
||
| 273 | |||
| 274 | xlim, ylim = ax.get_xlim(), ax.get_ylim() |
||
| 275 | |||
| 276 | #ax.clear() # either clear the whole axis or remove every artist separetly |
||
| 277 | |||
| 278 | for i, label in enumerate(knots_names): |
||
| 279 | years = knots_jyears[label] |
||
| 280 | idx = (val-1.5 < years) & (years < val) |
||
| 281 | x = knots_X[label][idx] |
||
| 282 | y = knots_Y[label][idx] |
||
| 283 | |||
| 284 | lineas.append(ax.plot(x, y, '.-', linewidth=0.6, alpha=0.4, label=label)[0]) |
||
| 285 | |||
| 286 | if use_arrows: |
||
| 287 | if len(x) > 1: |
||
| 288 | flechas.append(ax.quiver(x[:-1], |
||
| 289 | y[:-1], |
||
| 290 | arrow_pos*(x[1:] - x[:-1]), |
||
| 291 | arrow_pos*(y[1:] - y[:-1]), |
||
| 292 | scale_units='xy', angles='xy', scale=1, |
||
| 293 | width=0.0015, headwidth=10, headlength=10, headaxislength=6, |
||
| 294 | alpha=0.5, color=lineas[i].get_color())) |
||
| 295 | else: |
||
| 296 | flechas.append(None) |
||
| 297 | |||
| 298 | if len(x) > 0: |
||
| 299 | #textos.append(ax.annotate(label, (x[0], y[0]), (-28,-10), textcoords='offset points', color=lineas[i].get_color(), fontsize=14)) |
||
| 300 | textos.append(ax.text(x[-1]+0.015, y[-1]+0.015, label, {'color':lineas[i].get_color(), 'fontsize':14})) |
||
| 301 | else: |
||
| 302 | textos.append(None) |
||
| 303 | |||
| 304 | ax.set_xlim(xlim) |
||
| 305 | ax.set_ylim(ylim) |
||
| 306 | ax.set_aspect('equal') |
||
| 307 | |||
| 308 | fig.canvas.draw_idle() # if removed every artist separately instead of ax.clear() |
||
| 309 | |||
| 310 | draw_all() |
||
| 311 | |||
| 312 | def update(val): |
||
| 313 | nonlocal lineas, flechas, textos |
||
| 314 | |||
| 315 | for i, label in enumerate(knots_names): |
||
| 316 | years = knots_jyears[label] |
||
| 317 | idx = (val-1.5 < years) & (years < val) |
||
| 318 | x = knots_X[label][idx] |
||
| 319 | y = knots_Y[label][idx] |
||
| 320 | |||
| 321 | lineas[i].set_xdata(x) |
||
| 322 | lineas[i].set_ydata(y) |
||
| 323 | |||
| 324 | if textos[i] is not None: |
||
| 325 | if len(x) > 0: |
||
| 326 | textos[i].set_position((x[-1]+0.015, y[-1]+0.015)) |
||
| 327 | textos[i].set_text(label) |
||
| 328 | else: |
||
| 329 | textos[i].remove() |
||
| 330 | textos[i] = None |
||
| 331 | #textos[i].set_position((10, 10)) |
||
| 332 | else: |
||
| 333 | if len(x) > 0: |
||
| 334 | textos[i] = ax.text(x[-1]+0.02, y[-1]+0.02, label, {'color':lineas[i].get_color(), 'fontsize':14}) |
||
| 335 | |||
| 336 | if use_arrows: |
||
| 337 | if flechas[i] is not None: |
||
| 338 | flechas[i].remove() |
||
| 339 | flechas[i] = None |
||
| 340 | |||
| 341 | flechas[i] = ax.quiver(x[:-1], |
||
| 342 | y[:-1], |
||
| 343 | arrow_pos*(x[1:] - x[:-1]), |
||
| 344 | arrow_pos*(y[1:] - y[:-1]), |
||
| 345 | scale_units='xy', angles='xy', scale=1, |
||
| 346 | width=0.0015, headwidth=10, headlength=10, headaxislength=6, |
||
| 347 | alpha=0.5, color=lineas[i].get_color()) |
||
| 348 | |||
| 349 | fig.canvas.draw_idle() |
||
| 350 | |||
| 351 | |||
| 352 | |||
| 353 | selected_knot = None |
||
| 354 | selected_ind = None |
||
| 355 | selected_x = None |
||
| 356 | selected_y = None |
||
| 357 | |||
| 358 | |||
| 359 | View Code Duplication | def submit_textbox(text): |
|
| 360 | nonlocal mod, knots, knots_names, knots_values, knots_jyears, knots_X, knots_Y |
||
| 361 | |||
| 362 | log.debug('Submited with:') |
||
| 363 | log.debug(f' selected_knot {selected_knot}') |
||
| 364 | log.debug(f' selected_ind {selected_ind}') |
||
| 365 | log.debug(f' selected_x {selected_x}') |
||
| 366 | log.debug(f' selected_y {selected_y}') |
||
| 367 | |||
| 368 | if selected_knot is not None: |
||
| 369 | mod.loc[selected_ind, 'label'] = text.upper() |
||
| 370 | |||
| 371 | knots = dict(tuple(mod.groupby('label'))) |
||
| 372 | knots_names = list(knots.keys()) |
||
| 373 | knots_values = list(knots.values()) |
||
| 374 | knots_jyears = {k:Time(knots[k]['date'].to_numpy()).jyear for k in knots} |
||
| 375 | knots_X = {k:knots[k]['X'].to_numpy() for k in knots} |
||
| 376 | knots_Y = {k:knots[k]['Y'].to_numpy() for k in knots} |
||
| 377 | |||
| 378 | log.debug(f"Updated index {selected_ind} to {text.upper()}") |
||
| 379 | else: |
||
| 380 | pass |
||
| 381 | |||
| 382 | draw_all(slider_date.val) |
||
| 383 | |||
| 384 | |||
| 385 | def line_select_callback(eclick, erelease): |
||
| 386 | nonlocal selected_knot,selected_x, selected_y, selected_ind |
||
| 387 | |||
| 388 | # 1 eclick and erelease are the press and release events |
||
| 389 | x1, y1 = eclick.xdata, eclick.ydata |
||
| 390 | x2, y2 = erelease.xdata, erelease.ydata |
||
| 391 | log.debug("GUI: (%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2)) |
||
| 392 | log.debug("GUI: The button you used were: %s %s" % (eclick.button, erelease.button)) |
||
| 393 | |||
| 394 | selected_knot = None |
||
| 395 | selected_x = None |
||
| 396 | selected_y = None |
||
| 397 | selected_ind = None |
||
| 398 | |||
| 399 | for i, label in enumerate(knots_names): |
||
| 400 | years = knots_jyears[label] |
||
| 401 | idx = (slider_date.val-1.5 < years) & (years < slider_date.val) |
||
| 402 | |||
| 403 | if np.sum(idx) == 0: |
||
| 404 | continue # we did not select any component from this component, next one |
||
| 405 | |||
| 406 | x = np.array(knots_X[label]) |
||
| 407 | y = np.array(knots_Y[label]) |
||
| 408 | |||
| 409 | # get points iside current rectangle for current date |
||
| 410 | rect_idx = (x1 < x) & ( x < x2) & (y1 < y) & ( y < y2) & idx |
||
| 411 | |||
| 412 | View Code Duplication | if np.sum(rect_idx) > 0: |
|
| 413 | textbox.set_val(label) |
||
| 414 | |||
| 415 | selected_knot = label |
||
| 416 | selected_x = x[rect_idx].ravel() |
||
| 417 | selected_y = y[rect_idx].ravel() |
||
| 418 | selected_ind = knots[label].index[rect_idx] |
||
| 419 | |||
| 420 | log.debug(f'Selected {label} points rect_idx {rect_idx} x {x[rect_idx]}, y {y[rect_idx]} with indices {selected_ind}') |
||
| 421 | |||
| 422 | textbox.begin_typing(None) |
||
| 423 | break # if we find selected components in this epoch, continue with renaming |
||
| 424 | else: |
||
| 425 | pass |
||
| 426 | |||
| 427 | # print epoch of selected points: |
||
| 428 | if selected_knot is not None: |
||
| 429 | out.clear_output() |
||
| 430 | out.append_stdout('Selected:\n') |
||
| 431 | for idx in selected_ind: |
||
| 432 | #print(f"Selected {mod.loc[idx, 'label']} {mod.loc[idx, 'date']}") |
||
| 433 | out.append_stdout(f" -> {mod.loc[idx, 'label']} {mod.loc[idx, 'date'].strftime('%Y-%m-%d')}\n") |
||
| 434 | |||
| 435 | update(slider_date.val) |
||
| 436 | |||
| 437 | |||
| 438 | View Code Duplication | def toggle_selector(event): |
|
| 439 | log.debug('GUI: Key pressed.') |
||
| 440 | if event.key in ['Q', 'q'] and toggle_selector.RS.active: |
||
| 441 | log.debug('Selector deactivated.') |
||
| 442 | toggle_selector.RS.set_active(False) |
||
| 443 | if event.key in ['S', 's'] and not toggle_selector.RS.active: |
||
| 444 | log.debug('Selector activated.') |
||
| 445 | toggle_selector.RS.set_active(True) |
||
| 446 | if event.key in ['R', 'r']: |
||
| 447 | log.debug('Selector deactivated.') |
||
| 448 | toggle_selector.RS.set_active(False) |
||
| 449 | textbox.begin_typing(None) |
||
| 450 | #textbox.set_val('') |
||
| 451 | |||
| 452 | |||
| 453 | toggle_selector.RS = RectangleSelector(ax, line_select_callback, |
||
| 454 | drawtype='box', useblit=True, |
||
| 455 | button=[1, 3], # don't use middle button |
||
| 456 | minspanx=0, minspany=0, |
||
| 457 | spancoords='data', |
||
| 458 | interactive=False) |
||
| 459 | |||
| 460 | |||
| 461 | #plt.connect('key_press_event', toggle_selector) |
||
| 462 | fig.canvas.mpl_connect('key_press_event', toggle_selector) |
||
| 463 | |||
| 464 | |||
| 465 | |||
| 466 | from mpl_toolkits.axes_grid1 import make_axes_locatable |
||
| 467 | |||
| 468 | divider_slider = make_axes_locatable(ax) |
||
| 469 | slider_ax = divider_slider.append_axes("top", size="3%", pad="4%") |
||
| 470 | slider_date = Slider(ax=slider_ax, label="Date", valmin=2007, valmax=2020, valinit=2008, valstep=0.2, orientation="horizontal") |
||
| 471 | slider_date.on_changed(update) |
||
| 472 | |||
| 473 | #divider_textbox = make_axes_locatable(ax) |
||
| 474 | #textbox_ax = divider_textbox.append_axes("bottom", size="3%", pad="4%") |
||
| 475 | textbox_ax = fig.add_axes([0.3,0.015,0.5,0.05]) |
||
| 476 | textbox = TextBox(textbox_ax, 'Knot name:', initial='None') |
||
| 477 | textbox.on_submit(submit_textbox) |
||
| 478 | |||
| 479 | |||
| 480 | |||
| 481 | ax.set_xlim([-1.0, +1.0]) |
||
| 482 | ax.set_ylim([-1.0, +1.0]) |
||
| 483 | ax.set_aspect('equal') |
||
| 484 | |||
| 485 | fig.suptitle('S to select, R to rename, Q to deactivate selector') |
||
| 486 | |||
| 487 | print('Usage:', |
||
| 488 | '-> S to select, R to rename, Q to deactivate selector', |
||
| 489 | '-> To delete the knot, name it with a whitespace', |
||
| 490 | '-> You can select points from one component at a time', |
||
| 491 | '-> If you use the zoom or movement tools, remember to unselect them', |
||
| 492 | sep='\n') |
||
| 493 | |||
| 494 | plt.show() |
||
| 495 | |||
| 496 | out.display_output() |
||
| 497 | |||
| 498 | return mod |
||
| 499 | |||
| 902 | data.to_csv(f'{path}/{label}.csv', index=False) |
||