|
84 | 84 | "import numpy as np\n", |
85 | 85 | "import pandas as pd\n", |
86 | 86 | "import matplotlib.pyplot as plt\n", |
87 | | - "from astropy.time import Time" |
| 87 | + "from astropy.time import Time\n", |
| 88 | + "from lsst.utils.plotting import (get_multiband_plot_colors,\n", |
| 89 | + " get_multiband_plot_symbols)" |
88 | 90 | ] |
89 | 91 | }, |
90 | 92 | { |
|
143 | 145 | "colors = prop_cycle.by_key()['color']" |
144 | 146 | ] |
145 | 147 | }, |
| 148 | + { |
| 149 | + "cell_type": "markdown", |
| 150 | + "id": "fd91df61-fb09-48e4-88a1-bd9fa6786aec", |
| 151 | + "metadata": {}, |
| 152 | + "source": [ |
| 153 | + "Define the colors and symbols to use in plots to represent the six LSST filters, $ugrizy$." |
| 154 | + ] |
| 155 | + }, |
| 156 | + { |
| 157 | + "cell_type": "code", |
| 158 | + "execution_count": null, |
| 159 | + "id": "f9d6274f-1d2f-428e-b84b-2d3a8fb79fb9", |
| 160 | + "metadata": {}, |
| 161 | + "outputs": [], |
| 162 | + "source": [ |
| 163 | + "filter_names = ['u', 'g', 'r', 'i', 'z', 'y']\n", |
| 164 | + "filter_colors = get_multiband_plot_colors()\n", |
| 165 | + "filter_symbols = get_multiband_plot_symbols()" |
| 166 | + ] |
| 167 | + }, |
146 | 168 | { |
147 | 169 | "cell_type": "markdown", |
148 | 170 | "id": "6d5dae6e-e177-497a-b61f-b078809ae987", |
|
160 | 182 | "id": "dd380a4c-858b-42cf-bd2c-43fb9311c96a", |
161 | 183 | "metadata": {}, |
162 | 184 | "source": [ |
163 | | - "### 2.1 Query the Minor Planet Center\n", |
| 185 | + "### 2.1. Query the Minor Planet Center\n", |
164 | 186 | "\n", |
165 | 187 | "Use the Minor Planet Center's `get_orb` API to retrieve the orbit information for all 2,098 objects in the Rubin First Look Solar System discoveries and store the list of retrieved dictionaries in a DataFrame called `discovery_orbits`. Return fields for the object's provisional designation, absolute magnitude, semimajor axis, eccentricity, inclination, argument of perihelion, longitude of ascending node, time of perihelion passage, the total number of observations used in the orbit fit, and the orbit classification information. The request may take roughly 10 minutes or more to complete.\n", |
166 | 188 | "\n", |
|
238 | 260 | "id": "b57066e8-f27d-4f1b-932f-529f4bb75cb7", |
239 | 261 | "metadata": {}, |
240 | 262 | "source": [ |
241 | | - "### 2.2 Plot orbit classifications" |
| 263 | + "### 2.2. Plot orbit classifications" |
242 | 264 | ] |
243 | 265 | }, |
244 | 266 | { |
|
256 | 278 | "metadata": {}, |
257 | 279 | "outputs": [], |
258 | 280 | "source": [ |
259 | | - "a, e, inc = discovery_orbits[\"semimajor_axis\"], discovery_orbits[\"eccentricity\"], discovery_orbits[\"inclination\"]\n", |
| 281 | + "a = discovery_orbits[\"semimajor_axis\"]\n", |
| 282 | + "e = discovery_orbits[\"eccentricity\"]\n", |
| 283 | + "inc = discovery_orbits[\"inclination\"]\n", |
260 | 284 | "inner_orbits = discovery_orbits[a < 7]\n", |
261 | 285 | "outer_orbits = discovery_orbits[a >= 7]\n", |
262 | 286 | "\n", |
|
279 | 303 | " 99: \"Other\"\n", |
280 | 304 | "}\n", |
281 | 305 | "\n", |
282 | | - "fig, axes = plt.subplots(1, 2, figsize=(8,4))\n", |
| 306 | + "fig, axes = plt.subplots(1, 2, figsize=(8, 4))\n", |
283 | 307 | "plt.subplots_adjust(wspace=0.25)\n", |
284 | 308 | "\n", |
285 | 309 | "for ax, oo in zip(axes, (inner_orbits, outer_orbits)):\n", |
|
291 | 315 | " s = 10\n", |
292 | 316 | " marker, markers = markers[0], markers[1:]\n", |
293 | 317 | " label = orbit_type_map[otype]\n", |
294 | | - " ax.scatter(orb[\"semimajor_axis\"], orb[\"eccentricity\"], s=s, marker=marker, label=f\"{label} ({len(orb)})\")\n", |
| 318 | + " ax.scatter(orb[\"semimajor_axis\"], orb[\"eccentricity\"], s=s,\n", |
| 319 | + " marker=marker, label=f\"{label} ({len(orb)})\")\n", |
295 | 320 | "\n", |
296 | 321 | " for handle in ax.legend().legend_handles:\n", |
297 | 322 | " handle.set_sizes([10])\n", |
298 | | - " ax.legend(loc = 'upper right', bbox_to_anchor=(0.92, 1), fontsize=6)\n", |
| 323 | + " ax.legend(loc='upper right', bbox_to_anchor=(0.92, 1), fontsize=6)\n", |
299 | 324 | " ax.set_xlabel(\"semimajor axis (au)\")\n", |
300 | 325 | " ax.set_ylabel(\"eccentricity\")\n", |
301 | 326 | "\n", |
|
313 | 338 | " s = 10\n", |
314 | 339 | " marker, markers = markers[0], markers[1:]\n", |
315 | 340 | " label = orbit_type_map[otype]\n", |
316 | | - " ax.scatter(orb[\"semimajor_axis\"], orb[\"inclination\"], s=s, marker=marker, label=f\"{label} ({len(orb)})\")\n", |
| 341 | + " ax.scatter(orb[\"semimajor_axis\"], orb[\"inclination\"], s=s,\n", |
| 342 | + " marker=marker, label=f\"{label} ({len(orb)})\")\n", |
317 | 343 | "\n", |
318 | 344 | " for handle in ax.legend().legend_handles:\n", |
319 | 345 | " handle.set_sizes([10])\n", |
320 | | - " ax.legend(loc = 'upper right', fontsize=6)\n", |
| 346 | + " ax.legend(loc='upper right', fontsize=6)\n", |
321 | 347 | " ax.set_xlabel(\"semimajor axis (au)\")\n", |
322 | 348 | " ax.set_ylabel(\"inclination (deg)\")" |
323 | 349 | ] |
|
351 | 377 | "id": "2edbdaba-551d-4b72-a346-16227a9c45fb", |
352 | 378 | "metadata": {}, |
353 | 379 | "source": [ |
354 | | - "### 3.1 Query the Minor Planet Center\n", |
| 380 | + "### 3.1. Query the Minor Planet Center\n", |
355 | 381 | "\n", |
356 | 382 | "Use the Minor Planet Center's `get_obs` API to retrieve all observations for the 2,098 objects in the Rubin First Look Solar System discoveries and store them in a pandas DataFrame called `discovery_observations`, reducing the number of columns to those needed and converting selected strings to floats using numpy's `to_numeric` module. The request may take up to 40 minutes or more to complete." |
357 | 383 | ] |
|
383 | 409 | "\n", |
384 | 410 | "for i, obj in enumerate(targets[1:]):\n", |
385 | 411 | " response = requests.get(\"https://data.minorplanetcenter.net/api/get-obs\",\n", |
386 | | - " json={\"desigs\": [obj], \"output_format\":[\"ADES_DF\"]})\n", |
| 412 | + " json={\"desigs\": [obj], \"output_format\": [\"ADES_DF\"]})\n", |
387 | 413 | " if response.ok:\n", |
388 | 414 | " ades_df = pd.DataFrame(response.json()[0]['ADES_DF'])\n", |
389 | 415 | " else:\n", |
|
399 | 425 | " 'poscov13', 'poscov22', 'poscov23',\n", |
400 | 426 | " 'poscov33', 'precdec', 'precra',\n", |
401 | 427 | " 'prectime', 'prog', 'rastar', 'rcv',\n", |
402 | | - " 'ref', 'remarks', 'rmscorr','rmsdelay',\n", |
| 428 | + " 'ref', 'remarks', 'rmscorr', 'rmsdelay',\n", |
403 | 429 | " 'rmsdist', 'rmsdoppler', 'rmsfit',\n", |
404 | 430 | " 'rmspa', 'rmstime', 'seeing', 'subfmt',\n", |
405 | 431 | " 'subfrm', 'sys', 'trkid', 'trx', 'unctime'], axis=1)\n", |
406 | | - " discovery_observations = pd.concat([discovery_observations, ingest_observations], ignore_index=True)\n", |
| 432 | + " discovery_observations = pd.concat([discovery_observations, ingest_observations],\n", |
| 433 | + " ignore_index=True)\n", |
407 | 434 | " discovery_observations['dec'] = pd.to_numeric(discovery_observations['dec'])\n", |
408 | 435 | " discovery_observations['ra'] = pd.to_numeric(discovery_observations['ra'])\n", |
409 | 436 | " discovery_observations['rmsdec'] = pd.to_numeric(discovery_observations['rmsdec'])\n", |
|
482 | 509 | "id": "2a885ec6-9861-435b-9025-998005fa6735", |
483 | 510 | "metadata": {}, |
484 | 511 | "source": [ |
485 | | - "### 3.2 Examine the magnitude variation for an example discovery\n", |
| 512 | + "### 3.2. Examine the magnitude variation for an example discovery\n", |
486 | 513 | "\n", |
487 | 514 | "Sort `discovery_orbits` by number of observations in descending order to find the objects with the largest number of observations. Print the 10 objects with the highest number of observations." |
488 | 515 | ] |
|
494 | 521 | "metadata": {}, |
495 | 522 | "outputs": [], |
496 | 523 | "source": [ |
497 | | - "discovery_orbits_observations_sorted_desc = discovery_orbits.sort_values(by='nobs_total', ascending=False)\n", |
| 524 | + "discovery_orbits_observations_sorted_desc = discovery_orbits.sort_values(by='nobs_total',\n", |
| 525 | + " ascending=False)\n", |
498 | 526 | "discovery_orbits_observations_sorted_desc.head(10)" |
499 | 527 | ] |
500 | 528 | }, |
|
513 | 541 | "metadata": {}, |
514 | 542 | "outputs": [], |
515 | 543 | "source": [ |
516 | | - "select = np.isin(Rubin_discovery_observations['provid'], '2025 MX21', invert=False)\n", |
| 544 | + "object_name = '2025 MX21'\n", |
| 545 | + "select = np.isin(Rubin_discovery_observations['provid'], object_name, invert=False)\n", |
517 | 546 | "selected_Rubin_discovery_observations = Rubin_discovery_observations[select].copy()\n", |
518 | 547 | "len(selected_Rubin_discovery_observations)" |
519 | 548 | ] |
|
534 | 563 | "outputs": [], |
535 | 564 | "source": [ |
536 | 565 | "MJDmin, MJDmax = 60797, 60797.2\n", |
537 | | - "MJD, mag = selected_Rubin_discovery_observations['epoch'], selected_Rubin_discovery_observations['mag']\n", |
| 566 | + "MJD = selected_Rubin_discovery_observations['epoch']\n", |
| 567 | + "mag = selected_Rubin_discovery_observations['mag']\n", |
538 | 568 | "full_obs = selected_Rubin_discovery_observations\n", |
539 | 569 | "single_night_obs = selected_Rubin_discovery_observations[(MJDmin < MJD) & (MJD < MJDmax)]\n", |
540 | 570 | "\n", |
541 | 571 | "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n", |
542 | 572 | "\n", |
| 573 | + "axes[0].axvline(MJDmin + 0.1, lw=10, alpha=0.2, color='grey')\n", |
| 574 | + "a = 0\n", |
543 | 575 | "for ax, mags in zip(axes, (full_obs, single_night_obs)):\n", |
544 | | - " markers = \"s^*+xDh\"*2\n", |
545 | | - " for label, mag in mags.groupby('band'):\n", |
546 | | - " s = 10\n", |
547 | | - " marker, markers = markers[0], markers[1:]\n", |
548 | | - " ax.scatter(mag['epoch'], mag['mag'], s=s, marker=marker, label=f\"{label} ({len(mag)})\")\n", |
| 576 | + " for filt in ['g', 'r', 'i']:\n", |
| 577 | + " select_filt = np.isin(mags['band'], filt, invert=False)\n", |
| 578 | + " tx = np.where(select_filt == 1)[0]\n", |
| 579 | + " ax.scatter(mags['epoch'][select_filt], mags['mag'][select_filt],\n", |
| 580 | + " s=10, marker=filter_symbols[filt],\n", |
| 581 | + " color=filter_colors[filt],\n", |
| 582 | + " label=f\"{filt} ({len(tx)})\")\n", |
| 583 | + " if a == 1:\n", |
| 584 | + " ax.errorbar(mags['epoch'][select_filt], mags['mag'][select_filt],\n", |
| 585 | + " yerr=mags['rmsmag'][select_filt], fmt='None',\n", |
| 586 | + " ecolor=filter_colors[filt])\n", |
| 587 | + " del select_filt, tx\n", |
| 588 | + " a += 1\n", |
549 | 589 | "\n", |
550 | 590 | " for handle in ax.legend().legend_handles:\n", |
551 | 591 | " handle.set_sizes([10])\n", |
552 | 592 | " ax.invert_yaxis()\n", |
553 | 593 | " ax.set_xlabel('MJD')\n", |
554 | 594 | " ax.set_ylabel('Magnitude')\n", |
555 | 595 | " ax.minorticks_on()\n", |
556 | | - "fig.suptitle(f\"Rubin First Look Solar System Object Discovery {selected_Rubin_discovery_observations['provid'].values[0]} Magnitude Variation\")\n", |
| 596 | + "fig.suptitle(f\"Rubin First Look Solar System Object Discovery {object_name} Magnitude Variation\")\n", |
557 | 597 | "\n", |
558 | 598 | "plt.show()" |
559 | 599 | ] |
|
566 | 606 | "> **Figure 2:** Magnitude over time for the selected Rubin First Look Solar System object discovery both for the full observation period (left) and on a single night (right) to see the magnitude variation. Observations were taken in $g$-, $r$-, and $i$-band." |
567 | 607 | ] |
568 | 608 | }, |
| 609 | + { |
| 610 | + "cell_type": "markdown", |
| 611 | + "id": "8af622a5-3d13-4a27-9c99-393360927989", |
| 612 | + "metadata": {}, |
| 613 | + "source": [ |
| 614 | + "### 3.3. Evaluate photometric quality\n", |
| 615 | + "\n", |
| 616 | + "In a full Rubin data release, the photometry for moving objects in the `DiaSource` catalog comes with flag parameters to indicate potential issues with the photometric measurement or calibration (e.g., contamination from cosmic ray flux, sources at the edge of a detector), and the difference- and direct-images are available for visual checks on the source being measured.\n", |
| 617 | + "Observational metadata such as seeing, sky background, and limiting metadata are also available.\n", |
| 618 | + "These aspects of a full Rubin data release enable users to identify and reject potential poor-quality measurements from an analysis.\n", |
| 619 | + "\n", |
| 620 | + "However, for the RFL photometry released to the MPC, the only indicator of photometric quality available is the `rmsmag` column." |
| 621 | + ] |
| 622 | + }, |
| 623 | + { |
| 624 | + "cell_type": "markdown", |
| 625 | + "id": "590e0d1f-d15b-4944-8a36-76d1091fdd73", |
| 626 | + "metadata": {}, |
| 627 | + "source": [ |
| 628 | + "Show the distribution of all `rmsmag` values in order to approximate the boundary between the distribution of \"acceptable\" or \"normal\" measurements, and the tail of \"outlier\" measurements." |
| 629 | + ] |
| 630 | + }, |
| 631 | + { |
| 632 | + "cell_type": "code", |
| 633 | + "execution_count": null, |
| 634 | + "id": "738421d1-3180-4a67-9bf5-4537e083294b", |
| 635 | + "metadata": {}, |
| 636 | + "outputs": [], |
| 637 | + "source": [ |
| 638 | + "fig = plt.figure(figsize=(8, 3))\n", |
| 639 | + "plt.axvline(0.25, ls='dashed', alpha=0.7, color='black')\n", |
| 640 | + "for filt in ['g', 'r', 'i']:\n", |
| 641 | + " select_filt = np.isin(Rubin_discovery_observations['band'],\n", |
| 642 | + " filt, invert=False)\n", |
| 643 | + " plt.hist(Rubin_discovery_observations['rmsmag'][select_filt], 80,\n", |
| 644 | + " histtype='step', log=True, color=filter_colors[filt], label=filt)\n", |
| 645 | + "plt.xlabel('rms mag')\n", |
| 646 | + "plt.ylabel('log(N)')\n", |
| 647 | + "plt.legend(loc='upper right')\n", |
| 648 | + "plt.show()" |
| 649 | + ] |
| 650 | + }, |
| 651 | + { |
| 652 | + "cell_type": "markdown", |
| 653 | + "id": "4a447a05-0d3a-435a-a28a-2b1f45865444", |
| 654 | + "metadata": {}, |
| 655 | + "source": [ |
| 656 | + "> **Figure 3:** The log of the number of photometric measurements with a given `rmsmag` value. The grey line is drawn at $0.25$ mag, the approximate (by-eye) division between the distribution of `rmsmag` values and the tail of \"outliers\"." |
| 657 | + ] |
| 658 | + }, |
| 659 | + { |
| 660 | + "cell_type": "markdown", |
| 661 | + "id": "dd3811a9-a6cb-4649-aba1-e4cc060de302", |
| 662 | + "metadata": {}, |
| 663 | + "source": [ |
| 664 | + "Show scatter plots of the `rmsmag` values as a function of `mag` values, per filter." |
| 665 | + ] |
| 666 | + }, |
| 667 | + { |
| 668 | + "cell_type": "code", |
| 669 | + "execution_count": null, |
| 670 | + "id": "94881aa2-32d8-4647-92c7-e6bbde01209b", |
| 671 | + "metadata": {}, |
| 672 | + "outputs": [], |
| 673 | + "source": [ |
| 674 | + "fig, axes = plt.subplots(1, 3, figsize=(10, 5))\n", |
| 675 | + "for f, filt in enumerate(['g', 'r', 'i']):\n", |
| 676 | + " select = np.isin(Rubin_discovery_observations['band'], filt, invert=False)\n", |
| 677 | + " axes[f].axhline(0.25, alpha=0.5, color='grey')\n", |
| 678 | + " axes[f].plot(Rubin_discovery_observations['mag'][select],\n", |
| 679 | + " Rubin_discovery_observations['rmsmag'][select],\n", |
| 680 | + " 'o', ms=2, mew=0, alpha=0.7, color=filter_colors[filt])\n", |
| 681 | + " axes[f].set_xlabel('mag')\n", |
| 682 | + " axes[f].set_title(filt)\n", |
| 683 | + "axes[0].set_ylabel('rms mag')\n", |
| 684 | + "plt.show()" |
| 685 | + ] |
| 686 | + }, |
| 687 | + { |
| 688 | + "cell_type": "markdown", |
| 689 | + "id": "1d196cd2-5a3a-4e66-8308-a0cd1f23106c", |
| 690 | + "metadata": {}, |
| 691 | + "source": [ |
| 692 | + "> **Figure 4:** The `rmsmag` as a function of `mag` for the $g$-, $r$-, and $i$-band photometric measurements. The main locus of points in each plot represents \"normal\" photometric measurements, and the outliers are clearly visible. The horizontal lines drawn at `rmsmag` $=0.25$ mag represent about the upper bound on the \"inliers\"." |
| 693 | + ] |
| 694 | + }, |
| 695 | + { |
| 696 | + "cell_type": "markdown", |
| 697 | + "id": "07571984-17db-4c2d-b297-96d79f088909", |
| 698 | + "metadata": {}, |
| 699 | + "source": [ |
| 700 | + "The above demonstrates a simple, by-eye evaluation that `rmsmag` $> 0.25` mag indicates an \"outlier\" photometric meaurement for the RFL data sent to the MPC.\n", |
| 701 | + "\n", |
| 702 | + "Inclusion of these data points in any analysis should be done with caution." |
| 703 | + ] |
| 704 | + }, |
569 | 705 | { |
570 | 706 | "cell_type": "code", |
571 | 707 | "execution_count": null, |
572 | | - "id": "9eb55b51-a84b-4404-bea1-ff593692aa69", |
| 708 | + "id": "ab073769-fba2-4821-82c9-141dca24a75d", |
573 | 709 | "metadata": {}, |
574 | 710 | "outputs": [], |
575 | 711 | "source": [] |
|
0 commit comments