Tutorials

Loop over all spectra in SMASS

To loop over all spectra in a given source, use the Spectra class.

smass = classy.Spectra(source = "SMASS") # select all asteroids based on source

for spec in smass:
    print(spec.target.name, spec.target.number)
Loop over all spectra in classy index

To loop over all spectra in your classy index, you can load the index and pass it to Spectra. As the index can be quite large, it’s recommended to do this in chunks.

idx = classy.index.load() # returns classy index as pd.DataFrame

# Load spectra in chunks of 100
N = 10

for i in range(0, len(idx), N):
    specs = classy.Spectra(idx.iloc[i:i+N])

You can set skip_target=True to skip target resolution via rocks and significantly speed up the process.

for i in range(0, len(idx), N):
    specs = classy.Spectra(idx.iloc[i:i+N], skip_target=True)
Excluding points based on SNR or flag values

To exclude a reflectance value from the classification, you can set it to NaN.

import numpy as np

import classy

# Get Gaia spectrum of Ceres
ceres = classy.Spectra(1, source="Gaia")  # returns classy.Spectra instance

# "ceres" is now a list of classy.Spectrum entries which only one element, the Gaia spectrum
# We simplify by removing the list and getting the first entry
ceres = ceres[0]  # returns classy.Spectrum instance

# Classify and plot the result
ceres.classify()
ceres.plot(taxonomy='mahlke')

# There are sketchy data points
# Exclude points where the flag is not 0
ceres.refl[ceres.flag != 0] = np.nan

# Exclude the last point
ceres.refl[-1] = np.nan

# Preprocess, classify and plot again
ceres.classify()
ceres.plot(taxonomy='mahlke')
From spectrum to classification

This tutorial shows how to collect and combine spectra of a single asteroid, perform some tasks, and classifying the spectra in different taxonomic schemes.

We start with imports and gathering the spectra of our target (1685) Toro from online repositories.

import classy

import pandas as pd

# ------
# Get spectra from online repositories
spectra = classy.Spectra("toro")

Next, we add our own spectrum of (1685) Toro to the list of remote spectra.

# ------
# Add my own observation
data = pd.read_csv(
    "my_toro_spectrum.csv",
    names=["wavelength", "reflectance", "uncertainty", "flag"],
    skiprows=1,
)

my_spec = classy.Spectrum(
    # mandatory
    wave=data["wavelength"],
    refl=data["reflectance"],
    # optional but used by classy
    refl_err=data["uncertainty"],
    flag=data["flag"],
    source="OBSZ2",
    name="toro",
    # optional and ignored by classy
    date_obs="2022/02/19",
    phase_angle=23,
)

# Add my spectrum to the literature ones
spectra = spectra + my_spec

An extract of my_toro_spec.csv looks like this:

wave,refl,unc,flag
0.4350,0.8798,0.0099,0
0.4375,0.8674,0.0090,0
0.4400,0.8682,0.0082,0
0.4425,0.8842,0.0075,0
0.4450,0.8672,0.0068,0
        [...]
2.4300,1.4123,0.0102,0
2.4350,1.4169,0.0103,0
2.4400,1.4095,0.0103,0
2.4450,1.4158,0.0105,0
2.4500,1.4178,0.0105,0

Let’s see what we data we have now.

# ------
# Print some information
print(f"There are {len(spectra)} spectra of (1685) Toro:")

for spec in spectra:
    # for the literature spectra
    if spec.source != "OBSZ2":
        # Print the source and reference
        source_shortbib = f"{spec.source} / {spec.shortbib}"
    # for my spectrum
    else:
        source_shortbib = "My Observation"

    # Add the covered wavelength range and the number of datapoints
    waverange = f"{spec.wave.min():.2f} - {spec.wave.max():.2f}µm"
    N = f"N={len(spec)}"

    print(
        f"  {source_shortbib:<33}{waverange:<15}{N}",
    )

This prints:

There are 10 spectra of (1685) Toro:
  Gaia / Galluccio+ 2022           0.37 - 1.03µm  N=16
  SMASS / Burbine and Binzel 2002  0.88 - 1.64µm  N=42
  SMASS / Binzel+ 2004             0.43 - 2.43µm  N=492
  MITHNEOS / Binzel+ 2019          0.43 - 2.48µm  N=531
  MITHNEOS / Binzel+ 2019          0.82 - 2.48µm  N=320
  MITHNEOS / Binzel+ 2019          0.43 - 2.45µm  N=523
  MITHNEOS / Binzel+ 2019          0.43 - 2.48µm  N=541
  MITHNEOS / Binzel+ 2019          0.43 - 2.48µm  N=572
  MITHNEOS / Binzel+ 2019          0.43 - 2.43µm  N=501
  My Observation                   0.43 - 2.45µm  N=493

We can inspect them visually as well. classy shows the reflectance values and, if provided, the uncertainty as a shaded region around the spectrum.

_images/toro_spectra.png _images/toro_spectra_dark.png

We see that the SMASS and MITHNEOS spectra are densely sampled yet noisy. We can apply different smoothing techniques in a simple for-loop.

# ------
# Apply smoothing with specific parameters for each spectrum
for spec in spectra:
    if spec.source == "MITHNEOS":
        spec.smooth(method="savgol", window_length=int(len(spec) / 10), polyorder=3)
    elif spec.source == "SMASS":
        spec.smooth(method="spline", k=3, s=0.5)

Again, we can visually inspect the result.

_images/toro_smoothed.png _images/toro_smoothed_dark.png

It could be easier to visually compare the spectra if they had the same normalisation.

# ------
# Normalize to 1.25µm if this wavelength was observed
wave_norm = 1.25

for spec in spectra:
    if spec.wave.min() < wave_norm <= spec.wave.max():
        spec.normalize(at=wave_norm)

# Inspect the result
spectra.plot()
_images/toro_normalised.png _images/toro_normalised_dark.png

Now we get to classifying the spectra. Note that classy will automatically apply the necessary normalisations and wavelength grids required for each taxonomy to the reflectance spectra prior to classification, and revert the changes after classifying.

# ------
# Classify spectra in possible schemes
for spec in spectra:
    spec.classify()  # taxonomy='mahlke' is default
    spec.classify(taxonomy="demeo")
    spec.classify(taxonomy="tholen")

Now we can inspect the classes. If the required wavelength range for the Tholen 1984 and DeMeo+ 2009 taxonomies are not covered (and the taxonomies cannot be applied), the corresponding attributes are simply empty strings.

# print the classification results
for spec in spectra:
    # for the literature spectra
    if spec.source != "OBSZ2":
        # Print the source and reference
        source_shortbib = f"{spec.source} / {spec.shortbib}"
    # for my spectrum
    else:
        source_shortbib = "My Observation"

    # Add the covered wavelength range and the number of datapoints
    waverange = f"{spec.wave.min():.2f} - {spec.wave.max():.2f}µm"
    N = f"N={len(spec)}"

    print(
        f"  {source_shortbib:<33}{waverange:<15}{N:<5} T84: {spec.class_tholen:<3}DM09: {spec.class_demeo:<4}M22:{spec.class_:<2}({spec.prob*100:.1f}%)",
    )

This prints:

Gaia / Galluccio+ 2022           0.37 - 1.03µm  N=16  T84: S  DM09:     M22:S (90.2%)
SMASS / Burbine and Binzel 2002  0.88 - 1.64µm  N=42  T84:    DM09:     M22:S (99.9%)
SMASS / Binzel+ 2004             0.43 - 2.43µm  N=492 T84:    DM09:     M22:S (98.8%)
MITHNEOS / Binzel+ 2019          0.43 - 2.48µm  N=531 T84:    DM09: S   M22:Q (52.6%)
MITHNEOS / Binzel+ 2019          0.82 - 2.48µm  N=320 T84:    DM09:     M22:S (65.5%)
MITHNEOS / Binzel+ 2019          0.43 - 2.45µm  N=523 T84:    DM09: Sqw M22:S (98.7%)
MITHNEOS / Binzel+ 2019          0.43 - 2.48µm  N=541 T84:    DM09: Sqw M22:Q (52.7%)
MITHNEOS / Binzel+ 2019          0.43 - 2.48µm  N=572 T84:    DM09: Sqw M22:S (97.0%)
MITHNEOS / Binzel+ 2019          0.43 - 2.43µm  N=501 T84:    DM09:     M22:Q (77.5%)
My Observation                   0.43 - 2.45µm  N=493 T84:    DM09: Sqw M22:S (99.9%)

We can inspect the classification result in a plot:

spectra.plot(taxonomy='mahlke')  # taxonomy='mahlke' is default
_images/toro_classified.png _images/toro_classified_dark.png
Duplicating a Spectrum

To compare different preprocessing strategies, it might be useful to create a copy of an existing Spectrum. Use the python built-in function copy.deepcopy() for this.

>>> import classy
>>> import copy
>>> baucis = classy.Spectra(172, source='SMASS')[0]  # returns classy.Spectrum
>>> baucis_copy = copy.deepcopy(baucis)  # create identical copy
>>> baucis_copy.smooth()  # smooth only the copy
>>> spectra = baucis + baucis_copy  # returns classy.Spectra
>>> spectra.plot()  # compare