09 - Stars

Stars from the Tycho-2 catalog: Orion, and Barnard’s star moving over a century.

Orion

Barnard’s star 100 year motion

09 - Stars

09 - Stars

Data

File

What it is

From

tycho2.hrsc

The Tycho-2 star catalog, in Huira’s format

media.huira.space

You will need this catalog whenever you render stars with Huira: in the later examples, and in your own scenes, which for a space scene is almost always. It is about 120 MB, so keep one copy somewhere central on your machine rather than in this folder, and pass its path wherever you load stars. From the folder you choose, download it with:

curl -fLO https://media.huira.space/data/stars/tycho2/tycho2.hrsc

The Tycho-2 catalog is licensed for non-commercial use only.

Licenses, and how to credit the data: Tycho-2 star catalog.

Run

From this folder, in Python:

python stars.py /path/to/tycho2.hrsc

or in C++, built as in Running the examples:

../../build/09_stars /path/to/tycho2.hrsc

Each writes its images to output/, in the folder it runs from.

Source

The complete example, in both languages. View this example on GitHub.

"""
09 - Stars

Demonstrates:
    - Loading stars, down to a faintest magnitude, with their positions at an epoch
    - The scene's axes: x, y and z of the J2000 (ICRF) frame, so a right ascension and
      declination give a direction
    - Pointing a camera at a direction with look_at, with celestial north up
    - Exposing for stars: seconds rather than milliseconds, and a fast lens
    - Dynamic stars: moving the catalog to another epoch with update_star_epoch, without
      reloading it (Barnard's star, which moves fastest across the sky, over a century)

Builds on: 01 First render
Data: tycho2.hrsc, the Tycho-2 star catalog (see README.md)

Run:
    python stars.py /path/to/tycho2.hrsc

Writes:
    output/09_stars.png                 Orion
    output/09_stars_barnard_2000.png    Barnard's star in 2000...
    output/09_stars_barnard_2100.png    ...and in 2100
"""

import math
import sys
from pathlib import Path

import huira
from huira.rgb import Scene, SceneView, Renderer

from huira.units import Millimeter as mm
from huira.units import Micrometer as um
from huira.units import Second as sec


def direction(ra_deg, dec_deg):
    """The unit vector toward a right ascension and declination, in the J2000 frame."""
    ra = math.radians(ra_deg)
    dec = math.radians(dec_deg)
    return huira.Vec3(math.cos(dec) * math.cos(ra), math.cos(dec) * math.sin(ra), math.sin(dec))


def render(scene, camera, camera_model, exposure, filename):
    scene_view = SceneView(scene, exposure, camera)
    frame_buffer = camera_model.make_frame_buffer()
    frame_buffer.enable_sensor_response()
    Renderer().render(scene_view, frame_buffer)

    output_dir = Path("output")
    output_dir.mkdir(exist_ok=True)
    huira.write_png(output_dir / filename, huira.linear_to_srgb(frame_buffer.sensor_response))


def orion(catalog):
    scene = Scene()

    # load_stars reads the catalog, keeping the stars down to the faintest magnitude given, and
    # moves each by its proper motion to the time given. Each star's spectrum comes from its
    # temperature, so stars have their colors.
    time = huira.Time("2026-01-01T00:00:00")
    scene.load_stars(catalog, time, 11.0)

    # A 35 mm lens at f/2.8 on a sensor of 10 um pixels: about 25 x 16 degrees.
    camera_model = scene.new_camera_model()
    camera_model.set_focal_length(mm(35))
    camera_model.set_fstop(2.8)
    camera_model.configure_sensor_from_pitch((1536, 1024), um(10))

    # The scene's axes are those of J2000, so a direction from right ascension and declination
    # can be looked at directly (look_at takes a position; a unit vector is a point 1 m away).
    # Up is the celestial north pole, +z. Orion's belt is at about RA 84, Dec -1 degrees:
    camera = scene.root.new_instance(camera_model)
    camera.look_at(direction(84.0, -1.0), huira.Vec3(0, 0, 1))

    # Stars are faint: expose for seconds.
    render(scene, camera, camera_model, huira.Interval.from_centered(time, sec(2)),
           "09_stars.png")


def barnards_star(catalog):
    scene = Scene()

    # load_dynamic_stars keeps the catalog's proper motions, so update_star_epoch can move every
    # star to another time later, without reading the catalog again.
    year_2000 = huira.Time("2000-01-01T12:00:00")
    year_2100 = huira.Time("2100-01-01T12:00:00")
    scene.load_dynamic_stars(catalog, year_2000, 12.0)

    # A 300 mm lens: about 1 degree across. Barnard's star (RA 269.45, Dec 4.69 in 2000) moves
    # about 10 arcseconds a year, so a century carries it 17 arcminutes north: about 150 pixels.
    # The camera points halfway along that path.
    camera_model = scene.new_camera_model()
    camera_model.set_focal_length(mm(300))
    camera_model.set_fstop(4)
    camera_model.configure_sensor_from_pitch((512, 512), um(10))

    camera = scene.root.new_instance(camera_model)
    camera.look_at(direction(269.45, 4.83), huira.Vec3(0, 0, 1))

    render(scene, camera, camera_model, huira.Interval.from_centered(year_2000, sec(5)),
           "09_stars_barnard_2000.png")

    scene.update_star_epoch(year_2100)
    render(scene, camera, camera_model, huira.Interval.from_centered(year_2100, sec(5)),
           "09_stars_barnard_2100.png")


def main():
    if len(sys.argv) != 2:
        sys.exit("Usage: python stars.py <tycho2.hrsc>\nREADME.md says where to get it.")
    catalog = Path(sys.argv[1])

    orion(catalog)
    barnards_star(catalog)


if __name__ == "__main__":
    main()
// 09 - Stars
//
// Demonstrates:
//   - Loading stars, down to a faintest magnitude, with their positions at an epoch
//   - The scene's axes: x, y and z of the J2000 (ICRF) frame, so a right ascension and
//     declination give a direction
//   - Pointing a camera at a direction with look_at, with celestial north up
//   - Exposing for stars: seconds rather than milliseconds, and a fast lens
//   - Dynamic stars: moving the catalog to another epoch with update_star_epoch, without
//     reloading it (Barnard's star, which moves fastest across the sky, over a century)
//
// Builds on: 01 First render
// Data: tycho2.hrsc, the Tycho-2 star catalog (see README.md)
//
// Run:
//   ./09_stars /path/to/tycho2.hrsc
//
// Writes:
//   output/09_stars.png                 Orion
//   output/09_stars_barnard_2000.png    Barnard's star in 2000...
//   output/09_stars_barnard_2100.png    ...and in 2100

#include <cmath>
#include <filesystem>
#include <iostream>
#include <string>

#include "huira/huira.hpp"

namespace fs = std::filesystem;

using namespace huira::units::literals;

using TSpectral = huira::RGB;

// The unit vector toward a right ascension and declination, in the J2000 frame.
static huira::Vec3<double> direction(double ra_deg, double dec_deg)
{
    const double ra = ra_deg * huira::PI<double>() / 180.0;
    const double dec = dec_deg * huira::PI<double>() / 180.0;
    return {std::cos(dec) * std::cos(ra), std::cos(dec) * std::sin(ra), std::sin(dec)};
}

static void render(huira::Scene<TSpectral>& scene,
                   const huira::InstanceHandle<TSpectral>& camera,
                   const huira::CameraModelHandle<TSpectral>& camera_model,
                   const huira::Interval& exposure,
                   const std::string& filename)
{
    huira::SceneView<TSpectral> scene_view(scene, exposure, camera);
    auto frame_buffer = camera_model.make_frame_buffer();
    frame_buffer.enable_sensor_response();
    huira::Renderer<TSpectral> renderer;
    renderer.render(scene_view, frame_buffer);

    fs::create_directories("output");
    huira::write_image_png(fs::path("output") / filename,
                           huira::linear_to_srgb(frame_buffer.sensor_response()));
}

static void orion(const fs::path& catalog)
{
    huira::Scene<TSpectral> scene;

    // load_stars reads the catalog, keeping the stars down to the faintest magnitude given, and
    // moves each by its proper motion to the time given. Each star's spectrum comes from its
    // temperature, so stars have their colors.
    huira::Time time("2026-01-01T00:00:00");
    scene.load_stars(catalog, time, 11.f);

    // A 35 mm lens at f/2.8 on a sensor of 10 um pixels: about 25 x 16 degrees.
    auto camera_model = scene.new_camera_model();
    camera_model.set_focal_length(35_mm);
    camera_model.set_fstop(2.8f);
    camera_model.configure_sensor_from_pitch({1536, 1024}, 10_um);

    // The scene's axes are those of J2000, so a direction from right ascension and declination
    // can be looked at directly (look_at takes a position; a unit vector is a point 1 m away).
    // Up is the celestial north pole, +z. Orion's belt is at about RA 84, Dec -1 degrees:
    auto camera = scene.root.new_instance(camera_model);
    camera.look_at(direction(84.0, -1.0), huira::Vec3<double>{0.0, 0.0, 1.0});

    // Stars are faint: expose for seconds.
    render(scene, camera, camera_model, huira::Interval::from_centered(time, 2_s), "09_stars.png");
}

static void barnards_star(const fs::path& catalog)
{
    huira::Scene<TSpectral> scene;

    // load_dynamic_stars keeps the catalog's proper motions, so update_star_epoch can move every
    // star to another time later, without reading the catalog again.
    huira::Time year_2000("2000-01-01T12:00:00");
    huira::Time year_2100("2100-01-01T12:00:00");
    scene.load_dynamic_stars(catalog, year_2000, 12.f);

    // A 300 mm lens: about 1 degree across. Barnard's star (RA 269.45, Dec 4.69 in 2000) moves
    // about 10 arcseconds a year, so a century carries it 17 arcminutes north: about 150 pixels.
    // The camera points halfway along that path.
    auto camera_model = scene.new_camera_model();
    camera_model.set_focal_length(300_mm);
    camera_model.set_fstop(4);
    camera_model.configure_sensor_from_pitch({512, 512}, 10_um);

    auto camera = scene.root.new_instance(camera_model);
    camera.look_at(direction(269.45, 4.83), huira::Vec3<double>{0.0, 0.0, 1.0});

    render(scene,
           camera,
           camera_model,
           huira::Interval::from_centered(year_2000, 5_s),
           "09_stars_barnard_2000.png");

    scene.update_star_epoch(year_2100);
    render(scene,
           camera,
           camera_model,
           huira::Interval::from_centered(year_2100, 5_s),
           "09_stars_barnard_2100.png");
}

int main(int argc, char** argv)
{
    if (argc != 2) {
        std::cerr << "Usage: 09_stars <tycho2.hrsc>\nREADME.md says where to get it.\n";
        return 1;
    }
    const fs::path catalog = argv[1];

    orion(catalog);
    barnards_star(catalog);
}