Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose primary axial color to frontend #183

Merged
merged 3 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion raytracer/cherry-rs/examples/convexplano_lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ mod test_ri_info {
ParaxialView::new(&model, &FIELD_SPECS, false).expect("Could not create paraxial view");

let expected = axial_primary_color();
let results = view.axial_primary_color(&WAVELENGTHS).unwrap();
let results = view.axial_primary_color();

assert_eq!(expected.len(), results.len());
for (axis, expected_value) in expected.iter() {
Expand Down
23 changes: 17 additions & 6 deletions raytracer/cherry-rs/src/core/sequential_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub struct Gap {
pub struct SequentialModel {
surfaces: Vec<Surface>,
submodels: HashMap<SubModelID, SequentialSubModelBase>,
wavelengths: Vec<Float>,
}

/// A submodel of a sequential optical system.
Expand Down Expand Up @@ -327,6 +328,7 @@ impl SequentialModel {
Ok(Self {
surfaces,
submodels: models,
wavelengths: wavelengths.to_vec(),
})
}

Expand Down Expand Up @@ -356,6 +358,11 @@ impl SequentialModel {
.fold(0.0, |acc, x| acc.max(x))
}

/// Returns the wavelengths at which the system is modeled.
pub fn wavelengths(&self) -> &[Float] {
&self.wavelengths
}

/// Computes the unique IDs for each paraxial model.
fn calc_model_ids(surfaces: &[Surface], wavelengths: &[Float]) -> Vec<SubModelID> {
let mut ids = Vec::new();
Expand Down Expand Up @@ -490,17 +497,21 @@ impl<'a> SequentialSubModel for SequentialSubModelSlice<'a> {
}

impl Serialize for SubModelID {
// Serialize as a string like "0:Y" because tuples as map keys are difficult to work with in
// languages like Javascript.
// Serialize as a string like "0:Y" because tuples as map keys are difficult to
// work with in languages like Javascript.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialize as a string like "0:Y"
let key = format!("{}:{}", self.0, match self.1 {
Axis::X => "X",
Axis::Y => "Y",
});
let key = format!(
"{}:{}",
self.0,
match self.1 {
Axis::X => "X",
Axis::Y => "Y",
}
);
serializer.serialize_str(&key)
}
}
Expand Down
1 change: 1 addition & 0 deletions raytracer/cherry-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,5 @@ pub use views::{
};

// Re-exports from dependencies
#[cfg(feature = "ri-info")]
pub use lib_ria::Material;
30 changes: 10 additions & 20 deletions raytracer/cherry-rs/src/views/paraxial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type RayTransferMatrix = Array2<Float>;
#[derive(Debug)]
pub struct ParaxialView {
subviews: HashMap<SubModelID, ParaxialSubView>,
wavelengths: Vec<Float>,
}

/// A description of a paraxial optical system.
Expand All @@ -67,6 +68,7 @@ pub struct ParaxialView {
#[derive(Debug, Serialize)]
pub struct ParaxialViewDescription {
subviews: HashMap<SubModelID, ParaxialSubViewDescription>,
primary_axial_color: HashMap<Axis, Float>,
}

/// A paraxial subview of an optical system.
Expand Down Expand Up @@ -238,6 +240,7 @@ impl ParaxialView {

Ok(Self {
subviews: subviews?,
wavelengths: sequential_model.wavelengths().to_vec(),
})
}

Expand All @@ -254,6 +257,7 @@ impl ParaxialView {
.iter()
.map(|(id, subview)| (*id, subview.describe()))
.collect(),
primary_axial_color: self.axial_primary_color(),
}
}

Expand All @@ -275,34 +279,20 @@ impl ParaxialView {
/// enter the wavelengths for the Fraunhofer F and C lines as minimum and
/// maximum wavelengths to the underlying sequential model.
///
/// # Arguments
/// * `wavelengths` - The wavelengths to compute the axial primary color.
/// These must match the wavelengths used in the underlying sequential
/// model.
///
/// # Returns
/// A HashMap containing the axial primary color for each axis.
///
/// # Errors
/// An error is returned if the number of wavelengths does not match the
/// number of unique wavelengths in the paraxial view.
pub fn axial_primary_color(&self, wavelengths: &[Float]) -> Result<HashMap<Axis, Float>> {
let unique_wavelengths = self.subviews.keys().map(|id| id.0).collect::<Vec<usize>>();
if unique_wavelengths.len() != wavelengths.len() {
return Err(anyhow!(
"The number of wavelengths must match the number of unique wavelengths in the system."
));
}

pub fn axial_primary_color(&self) -> HashMap<Axis, Float> {
// Find the indexes of the minimum and maximum wavelengths. Return with the
// empty axial primary color if there are no wavelengths.
let min_wav_index = wavelengths
let min_wav_index = self
.wavelengths
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.total_cmp(b))
.map(|(index, _)| index)
.unwrap_or_default();
let max_wav_index = wavelengths
let max_wav_index = self
.wavelengths
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.total_cmp(b))
Expand Down Expand Up @@ -333,7 +323,7 @@ impl ParaxialView {
}
}

Ok(axial_primary_color)
axial_primary_color
}
}

Expand Down
2 changes: 1 addition & 1 deletion www/js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Materials dispersion data is now downloaded and added to the browser's IndexedDB database when the application starts.
- Users may now specify materials data to use in a lens design, and this data will be used to automatically compute refractive indexes at each wavelength.
- The results summary window now contains paraxial data computed at multiple wavelengths.
- The results summary window now contains paraxial data computed at multiple wavelengths and pximary axial color information.

## [0.2.0] 2024-12-09

Expand Down
118 changes: 73 additions & 45 deletions www/js/src/components/SummaryWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,59 @@ const formatValue = (value) => {

// Reusable table component that can be used in both modal and popup
const SummaryTable = ({ data, wavelengths, sorted_indexes, appModes }) => (
<table className="summary-table">
<colgroup>
<col />
<col span={appModes.refractiveIndex ? 1 : wavelengths.length} />
</colgroup>
<thead>
<tr>
<th scope="col">Parameter</th>
<th colSpan={appModes.refractiveIndex ? 1 : wavelengths.length} scope="colgroup">Value</th>
</tr>
{!appModes.refractiveIndex && (
<div>
<h3>Paraxial Data</h3>
<table className="summary-table">
<thead>
<tr>
<th scope="col">Wavelength, μm</th>
{sorted_indexes.map((i) => (
<th scope="col" key={i}>{wavelengths[i]}</th>
))}
<th>Parameter</th>
<th>Value</th>
</tr>
)}
</thead>
<tbody>
{Object.entries(data).map(([key, values]) => (
<tr key={key}>
<td>{key}</td>
{appModes.refractiveIndex ? (
<td>{formatValue(values[0])}</td>
) : (
sorted_indexes.map((i) => (
<td key={i}>{formatValue(values[i])}</td>
))
)}
</thead>
<tbody>
<tr>
<td>Primary Axial Color</td>
<td>{formatValue(data["Primary Axial Color"])}</td>
</tr>
</tbody>
</table>

<h3>Paraxial Data (wavelength-dependent)</h3>
<table className="summary-table">
<colgroup>
<col />
<col span={appModes.refractiveIndex ? 1 : wavelengths.length} />
</colgroup>
<thead>
<tr>
<th scope="col">Parameter</th>
<th colSpan={appModes.refractiveIndex ? 1 : wavelengths.length} scope="colgroup">Value</th>
</tr>
))}
</tbody>
</table>
{!appModes.refractiveIndex && (
<tr>
<th scope="col">Wavelength, μm</th>
{sorted_indexes.map((i) => (
<th scope="col" key={i}>{wavelengths[i]}</th>
))}
</tr>
)}
</thead>
<tbody>
{Object.entries(data.subviews).map(([key, values]) => (
<tr key={key}>
<td>{key}</td>
{appModes.refractiveIndex ? (
<td>{formatValue(values[0])}</td>
) : (
sorted_indexes.map((i) => (
<td key={i}>{formatValue(values[i])}</td>
))
)}
</tr>
))}
</tbody>
</table>
</div>
);

const Modal = ({ isOpen, onClose, children }) => {
Expand Down Expand Up @@ -115,8 +134,16 @@ const SummaryWindow = ({ description, isOpen, wavelengths, appModes, onClose })
// Update summary whenever description changes
useEffect(() => {
if (!description) return;


console.log(description.paraxial_view);

// For now we only deal with the Y axis as we don't support toric surfaces
const newSummary = {
"Primary Axial Color": description.paraxial_view.primary_axial_color.get("Y") || 0,
subviews: {}
};

const newSubviewSummaries = {
"Aperture Stop (surface index)": {},
"Effective Focal Length": {},
"Back Focal Distance": {},
Expand Down Expand Up @@ -147,23 +174,24 @@ const SummaryWindow = ({ description, isOpen, wavelengths, appModes, onClose })
// For now we only deal with the Y axis as we don't support toric surfaces
if (axis !== "Y") continue;

newSummary["Aperture Stop (surface index)"][wavelength_index] = subview.aperture_stop;
newSummary["Effective Focal Length"][wavelength_index] = subview.effective_focal_length;
newSummary["Back Focal Distance"][wavelength_index] = subview.back_focal_distance;
newSummary["Front Focal Distance"][wavelength_index] = subview.front_focal_distance;
newSummary["Paraxial Image Plane Location"][wavelength_index] = subview.paraxial_image_plane.location;
newSummary["Paraxial Image Plane Semi-Diameter"][wavelength_index] = subview.paraxial_image_plane.semi_diameter;
newSummary["Entrance Pupil Location"][wavelength_index] = subview.entrance_pupil.location;
newSummary["Entrance Pupil Semi-Diameter"][wavelength_index] = subview.entrance_pupil.semi_diameter;
newSummary["Exit Pupil Location"][wavelength_index] = subview.exit_pupil.location;
newSummary["Exit Pupil Semi-Diameter"][wavelength_index] = subview.exit_pupil.semi_diameter;
newSummary["Back Principal Plane"][wavelength_index] = subview.back_principal_plane;
newSummary["Front Principal Plane"][wavelength_index] = subview.front_principal_plane;
newSubviewSummaries["Aperture Stop (surface index)"][wavelength_index] = subview.aperture_stop;
newSubviewSummaries["Effective Focal Length"][wavelength_index] = subview.effective_focal_length;
newSubviewSummaries["Back Focal Distance"][wavelength_index] = subview.back_focal_distance;
newSubviewSummaries["Front Focal Distance"][wavelength_index] = subview.front_focal_distance;
newSubviewSummaries["Paraxial Image Plane Location"][wavelength_index] = subview.paraxial_image_plane.location;
newSubviewSummaries["Paraxial Image Plane Semi-Diameter"][wavelength_index] = subview.paraxial_image_plane.semi_diameter;
newSubviewSummaries["Entrance Pupil Location"][wavelength_index] = subview.entrance_pupil.location;
newSubviewSummaries["Entrance Pupil Semi-Diameter"][wavelength_index] = subview.entrance_pupil.semi_diameter;
newSubviewSummaries["Exit Pupil Location"][wavelength_index] = subview.exit_pupil.location;
newSubviewSummaries["Exit Pupil Semi-Diameter"][wavelength_index] = subview.exit_pupil.semi_diameter;
newSubviewSummaries["Back Principal Plane"][wavelength_index] = subview.back_principal_plane;
newSubviewSummaries["Front Principal Plane"][wavelength_index] = subview.front_principal_plane;

// Only extract these values once if appModes is set to refractive index
if (appModes.refractiveIndex) break;
}

newSummary.subviews = newSubviewSummaries;

setSummary(newSummary);
}, [description]);

Expand Down