| Conditions | 26 |
| Total Lines | 265 |
| Code Lines | 169 |
| Lines | 47 |
| Ratio | 17.74 % |
| 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 |
||
| 172 | def KnotsId2dGUI(mod, use_arrows=False, arrow_pos=1.0): |
||
| 173 | """ |
||
| 174 | Prompt a GUI to select identified knots and alter their label, reprenting their 2D |
||
| 175 | spatial distribution in different times. |
||
| 176 | |||
| 177 | It can be used inside jupyter notebooks, using '%matplotlib widget' first. |
||
| 178 | |||
| 179 | Parameters: |
||
| 180 | ----------- |
||
| 181 | mod : :pd.DataFrame: |
||
| 182 | pandas.DataFrame containing every knot, with at least columns |
||
| 183 | 'label', 'date', 'X', 'Y', 'Flux (Jy)'. |
||
| 184 | |||
| 185 | Returns: |
||
| 186 | -------- |
||
| 187 | mod : :pd.DataFrame: |
||
| 188 | pandas.DataFrame containing every knot with their altered labels, with |
||
| 189 | at least columns 'label', 'date', 'X', 'Y', 'Flux (Jy)'. |
||
| 190 | """ |
||
| 191 | |||
| 192 | mod = mod.copy() |
||
| 193 | |||
| 194 | knots = dict(tuple(mod.groupby('label'))) |
||
| 195 | knots_names = list(knots.keys()) |
||
| 196 | knots_values = list(knots.values()) |
||
| 197 | knots_jyears = {k:Time(knots[k]['date'].to_numpy()).jyear for k in knots} |
||
| 198 | knots_X = {k:knots[k]['X'].to_numpy() for k in knots} |
||
| 199 | knots_Y = {k:knots[k]['Y'].to_numpy() for k in knots} |
||
| 200 | |||
| 201 | |||
| 202 | from matplotlib.widgets import Slider, Button, TextBox, RectangleSelector |
||
| 203 | |||
| 204 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,8)) |
||
| 205 | |||
| 206 | lineas = list() |
||
| 207 | flechas = list() |
||
| 208 | textos = list() |
||
| 209 | |||
| 210 | def draw_all(val=2008): |
||
| 211 | nonlocal lineas, flechas, textos |
||
| 212 | |||
| 213 | # instead of clearing the whole axis, remove artists |
||
| 214 | for linea in lineas: |
||
| 215 | if linea is not None: |
||
| 216 | linea.remove() |
||
| 217 | for texto in textos: |
||
| 218 | if texto is not None: |
||
| 219 | texto.remove() |
||
| 220 | for flecha in flechas: |
||
| 221 | if flecha is not None: |
||
| 222 | flecha.remove() |
||
| 223 | ax.set_prop_cycle(None) |
||
| 224 | |||
| 225 | lineas = list() |
||
| 226 | flechas = list() |
||
| 227 | textos = list() |
||
| 228 | |||
| 229 | xlim, ylim = ax.get_xlim(), ax.get_ylim() |
||
| 230 | |||
| 231 | #ax.clear() # either clear the whole axis or remove every artist separetly |
||
| 232 | |||
| 233 | for i, label in enumerate(knots_names): |
||
| 234 | years = knots_jyears[label] |
||
| 235 | idx = (val-1.5 < years) & (years < val) |
||
| 236 | x = knots_X[label][idx] |
||
| 237 | y = knots_Y[label][idx] |
||
| 238 | |||
| 239 | lineas.append(ax.plot(x, y, '.-', linewidth=0.6, alpha=0.4, label=label)[0]) |
||
| 240 | |||
| 241 | if use_arrows: |
||
| 242 | if len(x) > 1: |
||
| 243 | flechas.append(ax.quiver(x[:-1], |
||
| 244 | y[:-1], |
||
| 245 | arrow_pos*(x[1:] - x[:-1]), |
||
| 246 | arrow_pos*(y[1:] - y[:-1]), |
||
| 247 | scale_units='xy', angles='xy', scale=1, |
||
| 248 | width=0.0015, headwidth=10, headlength=10, headaxislength=6, |
||
| 249 | alpha=0.5, color=lineas[i].get_color())) |
||
| 250 | else: |
||
| 251 | flechas.append(None) |
||
| 252 | |||
| 253 | if len(x) > 0: |
||
| 254 | #textos.append(ax.annotate(label, (x[0], y[0]), (-28,-10), textcoords='offset points', color=lineas[i].get_color(), fontsize=14)) |
||
| 255 | textos.append(ax.text(x[-1]+0.015, y[-1]+0.015, label, {'color':lineas[i].get_color(), 'fontsize':14})) |
||
| 256 | else: |
||
| 257 | textos.append(None) |
||
| 258 | |||
| 259 | ax.set_xlim(xlim) |
||
| 260 | ax.set_ylim(ylim) |
||
| 261 | ax.set_aspect('equal') |
||
| 262 | |||
| 263 | fig.canvas.draw_idle() # if removed every artist separately instead of ax.clear() |
||
| 264 | |||
| 265 | draw_all() |
||
| 266 | |||
| 267 | def update(val): |
||
| 268 | nonlocal lineas, flechas, textos |
||
| 269 | |||
| 270 | for i, label in enumerate(knots_names): |
||
| 271 | years = knots_jyears[label] |
||
| 272 | idx = (val-1.5 < years) & (years < val) |
||
| 273 | x = knots_X[label][idx] |
||
| 274 | y = knots_Y[label][idx] |
||
| 275 | |||
| 276 | lineas[i].set_xdata(x) |
||
| 277 | lineas[i].set_ydata(y) |
||
| 278 | |||
| 279 | if textos[i] is not None: |
||
| 280 | if len(x) > 0: |
||
| 281 | textos[i].set_position((x[-1]+0.015, y[-1]+0.015)) |
||
| 282 | textos[i].set_text(label) |
||
| 283 | else: |
||
| 284 | textos[i].remove() |
||
| 285 | textos[i] = None |
||
| 286 | #textos[i].set_position((10, 10)) |
||
| 287 | else: |
||
| 288 | if len(x) > 0: |
||
| 289 | textos[i] = ax.text(x[-1]+0.02, y[-1]+0.02, label, {'color':lineas[i].get_color(), 'fontsize':14}) |
||
| 290 | |||
| 291 | if use_arrows: |
||
| 292 | if flechas[i] is not None: |
||
| 293 | flechas[i].remove() |
||
| 294 | flechas[i] = None |
||
| 295 | |||
| 296 | flechas[i] = ax.quiver(x[:-1], |
||
| 297 | y[:-1], |
||
| 298 | arrow_pos*(x[1:] - x[:-1]), |
||
| 299 | arrow_pos*(y[1:] - y[:-1]), |
||
| 300 | scale_units='xy', angles='xy', scale=1, |
||
| 301 | width=0.0015, headwidth=10, headlength=10, headaxislength=6, |
||
| 302 | alpha=0.5, color=lineas[i].get_color()) |
||
| 303 | |||
| 304 | fig.canvas.draw_idle() |
||
| 305 | |||
| 306 | |||
| 307 | |||
| 308 | selected_knot = None |
||
| 309 | selected_ind = None |
||
| 310 | selected_x = None |
||
| 311 | selected_y = None |
||
| 312 | |||
| 313 | |||
| 314 | View Code Duplication | def submit_textbox(text): |
|
| 315 | nonlocal mod, knots, knots_names, knots_values, knots_jyears, knots_X, knots_Y |
||
| 316 | |||
| 317 | log.debug('Submited with:') |
||
| 318 | log.debug(f' selected_knot {selected_knot}') |
||
| 319 | log.debug(f' selected_ind {selected_ind}') |
||
| 320 | log.debug(f' selected_x {selected_x}') |
||
| 321 | log.debug(f' selected_y {selected_y}') |
||
| 322 | |||
| 323 | if selected_knot is not None: |
||
| 324 | mod.loc[selected_ind, 'label'] = text.upper() |
||
| 325 | |||
| 326 | knots = dict(tuple(mod.groupby('label'))) |
||
| 327 | knots_names = list(knots.keys()) |
||
| 328 | knots_values = list(knots.values()) |
||
| 329 | knots_jyears = {k:Time(knots[k]['date'].to_numpy()).jyear for k in knots} |
||
| 330 | knots_X = {k:knots[k]['X'].to_numpy() for k in knots} |
||
| 331 | knots_Y = {k:knots[k]['Y'].to_numpy() for k in knots} |
||
| 332 | |||
| 333 | print(f"Updated index {selected_ind} to {text.upper()}") |
||
| 334 | else: |
||
| 335 | pass |
||
| 336 | |||
| 337 | draw_all(slider_date.val) |
||
| 338 | |||
| 339 | def line_select_callback(eclick, erelease): |
||
| 340 | nonlocal selected_knot,selected_x, selected_y, selected_ind |
||
| 341 | |||
| 342 | # 1 eclick and erelease are the press and release events |
||
| 343 | x1, y1 = eclick.xdata, eclick.ydata |
||
| 344 | x2, y2 = erelease.xdata, erelease.ydata |
||
| 345 | log.debug("GUI: (%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2)) |
||
| 346 | log.debug("GUI: The button you used were: %s %s" % (eclick.button, erelease.button)) |
||
| 347 | |||
| 348 | selected_knot = None |
||
| 349 | selected_x = None |
||
| 350 | selected_y = None |
||
| 351 | selected_ind = None |
||
| 352 | |||
| 353 | for i, label in enumerate(knots_names): |
||
| 354 | years = knots_jyears[label] |
||
| 355 | idx = (slider_date.val-1.5 < years) & (years < slider_date.val) |
||
| 356 | |||
| 357 | if np.sum(idx) == 0: |
||
| 358 | continue # we did not select any component from this component, next one |
||
| 359 | |||
| 360 | x = np.array(knots_X[label]) |
||
| 361 | y = np.array(knots_Y[label]) |
||
| 362 | |||
| 363 | # get points iside current rectangle for current date |
||
| 364 | rect_idx = (x1 < x) & ( x < x2) & (y1 < y) & ( y < y2) & idx |
||
| 365 | |||
| 366 | View Code Duplication | if np.sum(rect_idx) > 0: |
|
| 367 | textbox.set_val(label) |
||
| 368 | selected_knot = label |
||
| 369 | selected_x = x[rect_idx].ravel() |
||
| 370 | selected_y = y[rect_idx].ravel() |
||
| 371 | selected_ind = knots[label].index[rect_idx] |
||
| 372 | log.debug(f'Selected {label} points rect_idx {rect_idx} x {x[rect_idx]}, y {y[rect_idx]} with indices {selected_ind}') |
||
| 373 | textbox.begin_typing(None) |
||
| 374 | break # if we find selected components in this epoch, continue with renaming |
||
| 375 | else: |
||
| 376 | pass |
||
| 377 | |||
| 378 | update(slider_date.val) |
||
| 379 | |||
| 380 | |||
| 381 | View Code Duplication | def toggle_selector(event): |
|
| 382 | log.debug('GUI: Key pressed.') |
||
| 383 | if event.key in ['Q', 'q'] and toggle_selector.RS.active: |
||
| 384 | log.debug('Selector deactivated.') |
||
| 385 | toggle_selector.RS.set_active(False) |
||
| 386 | if event.key in ['S', 's'] and not toggle_selector.RS.active: |
||
| 387 | log.debug('Selector activated.') |
||
| 388 | toggle_selector.RS.set_active(True) |
||
| 389 | if event.key in ['R', 'r']: |
||
| 390 | log.debug('Selector deactivated.') |
||
| 391 | toggle_selector.RS.set_active(False) |
||
| 392 | textbox.begin_typing(None) |
||
| 393 | #textbox.set_val('') |
||
| 394 | |||
| 395 | |||
| 396 | toggle_selector.RS = RectangleSelector(ax, line_select_callback, |
||
| 397 | drawtype='box', useblit=True, |
||
| 398 | button=[1, 3], # don't use middle button |
||
| 399 | minspanx=0, minspany=0, |
||
| 400 | spancoords='data', |
||
| 401 | interactive=False) |
||
| 402 | |||
| 403 | |||
| 404 | #plt.connect('key_press_event', toggle_selector) |
||
| 405 | fig.canvas.mpl_connect('key_press_event', toggle_selector) |
||
| 406 | |||
| 407 | |||
| 408 | |||
| 409 | from mpl_toolkits.axes_grid1 import make_axes_locatable |
||
| 410 | |||
| 411 | divider_slider = make_axes_locatable(ax) |
||
| 412 | slider_ax = divider_slider.append_axes("top", size="3%", pad="4%") |
||
| 413 | slider_date = Slider(ax=slider_ax, label="Date", valmin=2007, valmax=2020, valinit=2008, valstep=0.2, orientation="horizontal") |
||
| 414 | slider_date.on_changed(update) |
||
| 415 | |||
| 416 | #divider_textbox = make_axes_locatable(ax) |
||
| 417 | #textbox_ax = divider_textbox.append_axes("bottom", size="3%", pad="4%") |
||
| 418 | textbox_ax = fig.add_axes([0.3,0.015,0.5,0.05]) |
||
| 419 | textbox = TextBox(textbox_ax, 'Knot name:', initial='None') |
||
| 420 | textbox.on_submit(submit_textbox) |
||
| 421 | |||
| 422 | |||
| 423 | |||
| 424 | ax.set_xlim([-1.0, +1.0]) |
||
| 425 | ax.set_ylim([-1.0, +1.0]) |
||
| 426 | ax.set_aspect('equal') |
||
| 427 | |||
| 428 | fig.suptitle('S to select, R to rename, Q to deactivate selector') |
||
| 429 | |||
| 430 | print('S to select, R to rename, Q to deactivate selector') |
||
| 431 | print('(you can select points from one component at a time)') |
||
| 432 | print('(if you use the zoom or movement tools, remember to unselect them)') |
||
| 433 | |||
| 434 | plt.show() |
||
| 435 | |||
| 436 | return mod |
||
| 437 | |||
| 818 | data.to_csv(f'{path}/{label}.csv', index=False) |
||