python/src/materials.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "bindings.h" | ||
| 2 | #include "common.h" | ||
| 3 | #include "objects.h" | ||
| 4 | |||
| 5 | #include "terraingen/materials/materials.h" | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <optional> | ||
| 9 | #include <vector> | ||
| 10 | |||
| 11 | namespace nb = nanobind; | ||
| 12 | using namespace nb::literals; | ||
| 13 | |||
| 14 | namespace terraingen::python { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | /// `photometry` as a list of `HapkeParameters` or None, one per `MaterialClass`. | ||
| 19 | 1 | std::vector<std::optional<HapkeParameters>> ToList(const MaterialPhotometry& photometry) { | |
| 20 | 3 | return {photometry.begin(), photometry.end()}; | |
| 21 | } | ||
| 22 | |||
| 23 | /// `classes` as `MaterialPhotometry`. | ||
| 24 | /// @throws std::invalid_argument for a list of the wrong length. | ||
| 25 | 3 | MaterialPhotometry FromList(const std::vector<std::optional<HapkeParameters>>& classes) { | |
| 26 | 3 | if (classes.size() != kMaterialClasses) { | |
| 27 | ✗ | throw std::invalid_argument("Photometry needs one entry (or None) per material class"); | |
| 28 | } | ||
| 29 | 3 | MaterialPhotometry photometry{}; | |
| 30 | 3 | std::copy(classes.begin(), classes.end(), photometry.begin()); | |
| 31 | 3 | return photometry; | |
| 32 | } | ||
| 33 | |||
| 34 | } // namespace | ||
| 35 | |||
| 36 | 44 | void BindMaterials(nb::module_& m) { | |
| 37 | auto options = nb::class_<MaterialOptions>( | ||
| 38 | m, "MaterialOptions", | ||
| 39 | "How a material map is made: bedrock where the ground beneath the material layers is bare " | ||
| 40 | "(`exposure`, None " | ||
| 41 | 44 | "for none), and dust in patches covering about `dust_cover` of the surface."); | |
| 42 | 44 | BindKwargsInit<MaterialOptions>(options); | |
| 43 | 44 | options.def_rw("exposure", &MaterialOptions::exposure) | |
| 44 | 44 | .def_rw("dust_cover", &MaterialOptions::dustCover) | |
| 45 | 44 | .def_rw("dust_patch", &MaterialOptions::dustPatch) | |
| 46 | .def_rw("seed", &MaterialOptions::seed); | ||
| 47 | 44 | m.def("validate_material_options", &ValidateMaterialOptions, "options"_a, | |
| 48 | "Raises ValueError for invalid options."); | ||
| 49 | 44 | m.def( | |
| 50 | "materials_at", | ||
| 51 | ✗ | [](const PyTerrain& terrain, const MaterialOptions& material, double x, double y, | |
| 52 | double spacing) { | ||
| 53 | 1 | const MaterialShares shares = MaterialsAt(terrain.Get(), material, x, y, spacing); | |
| 54 | 2 | return std::vector<double>(shares.begin(), shares.end()); | |
| 55 | }, | ||
| 56 | 44 | "terrain"_a, "options"_a, "x"_a, "y"_a, "spacing"_a, | |
| 57 | "The share of each MaterialClass at (x, y), in class order: soil, then bedrock where the " | ||
| 58 | "ground beneath the first material layer is bare, dust in patches, and each material " | ||
| 59 | "layer's " | ||
| 60 | "cover in order."); | ||
| 61 | 44 | m.def( | |
| 62 | "evaluate_materials", | ||
| 63 | ✗ | [](const PyTerrain& terrain, const MaterialOptions& material, const RasterGrid& grid, | |
| 64 | double spacing, unsigned threads) { | ||
| 65 | 4 | return EvaluateMaterials(terrain.Get(), material, grid, | |
| 66 | 6 | EvaluationOptions{spacing, threads}); | |
| 67 | }, | ||
| 68 | 176 | "terrain"_a, "options"_a, "grid"_a, "spacing"_a = 0.0, "threads"_a = 0, | |
| 69 | 44 | nb::call_guard<nb::gil_scoped_release>(), | |
| 70 | "One raster per MaterialClass over `grid`: each cell's share of that class."); | ||
| 71 | |||
| 72 | auto hapke = nb::class_<HapkeParameters>( | ||
| 73 | m, "HapkeParameters", | ||
| 74 | "Hapke parameters: single-scattering `albedo` w, two-term Henyey-Greenstein `b` and `c`, " | ||
| 75 | 44 | "and macroscopic `roughness` theta-bar in degrees."); | |
| 76 | 44 | BindKwargsInit<HapkeParameters>(hapke); | |
| 77 | 44 | hapke.def_rw("albedo", &HapkeParameters::albedo) | |
| 78 | 44 | .def_rw("b", &HapkeParameters::b) | |
| 79 | 44 | .def_rw("c", &HapkeParameters::c) | |
| 80 | 44 | .def_rw("roughness", &HapkeParameters::roughness) | |
| 81 | ✗ | .def("__eq__", [](const HapkeParameters& a, const HapkeParameters& b) { return a == b; }); | |
| 82 | 44 | m.def( | |
| 83 | 1 | "mars_pancam_photometry", [] { return ToList(MarsPancamPhotometry()); }, | |
| 84 | "Hapke parameters of Martian soil, sand, dust, bedrock and rock at 753 nm from Pancam " | ||
| 85 | "(Johnson et " | ||
| 86 | "al. 2006a, b, as tabulated by Fernando et al. 2013, and Opportunity's Eagle crater " | ||
| 87 | "outcrop " | ||
| 88 | "for bedrock), one per MaterialClass."); | ||
| 89 | 44 | m.def( | |
| 90 | "mix_photometry", | ||
| 91 | ✗ | [](const std::vector<double>& shares, | |
| 92 | const std::vector<std::optional<HapkeParameters>>& photometry) { | ||
| 93 | 2 | if (shares.size() != kMaterialClasses) { | |
| 94 | ✗ | throw std::invalid_argument("Mixing needs one share per material class"); | |
| 95 | } | ||
| 96 | 2 | MaterialShares mixed{}; | |
| 97 | 2 | std::copy(shares.begin(), shares.end(), mixed.begin()); | |
| 98 | 2 | return MixPhotometry(mixed, FromList(photometry)); | |
| 99 | }, | ||
| 100 | 44 | "shares"_a, "photometry"_a, | |
| 101 | "The share-weighted mean Hapke parameters of a mix of materials."); | ||
| 102 | 44 | m.def( | |
| 103 | "evaluate_photometry", | ||
| 104 | ✗ | [](const std::vector<Raster<float>>& materials, | |
| 105 | const std::vector<std::optional<HapkeParameters>>& photometry) { | ||
| 106 | 1 | return EvaluatePhotometry(materials, FromList(photometry)); | |
| 107 | }, | ||
| 108 | 44 | "materials"_a, "photometry"_a, | |
| 109 | "Rasters of the albedo, b, c and roughness that material rasters mix to."); | ||
| 110 | 44 | } | |
| 111 | |||
| 112 | } // namespace terraingen::python | ||
| 113 |