A hand holding a filter at an angle in front of a white LED on a protractor; the beam projected onto the paper is split into red and yellow.
A 6 nm Hα filter held at an angle in front of a white LED, on a printed protractor. The transmitted beam has shifted from red toward yellow.

Why the angle matters

A narrowband astronomy filter is an interference filter. It reflects the unwanted wavelengths rather than absorbing them, using a stack of thin dielectric layers. The layers are tuned so that light at one particular wavelength interferes constructively on its way through. A 6 nm hydrogen alpha (Hα) filter passes a 6 nm-wide window centered on 656.3 nm and rejects the rest.

This tuning depends on the path length through the layer stack, which in turn depends on the angle of incidence. When the filter is tilted, the effective path shortens and the passband shifts toward shorter wavelengths. This is approximated by:

λ(θ) ≈ λ₀ · √( 1 − (sinθ / n_eff)² )

where θ is the angle of incidence and n_eff is the effective refractive index of the coating stack. Setting n_eff ≈ 2 as an approximation gives the following shifts for a 6 nm Hα filter.

Tilt Band center Shift As a fraction of a 6 nm band
656.3 nm
656.2 nm−0.1 nm2%
655.7 nm−0.6 nm10%
10°653.8 nm−2.5 nm41%
These values are illustrative. n_eff is specific to each filter's coating, so they show the scale of the effect rather than a specification.

At 10°, the window moves almost half its own width off the emission line being photographed. When the filter is mounted at an angle, the angle of incidence is also not uniform across the frame. Light reaching the left edge of the sensor arrives at a different angle than light reaching the right edge. The passband is therefore shifted by different amounts across the frame, which produces uneven brightness and a color gradient, and reduces the signal on one side of every exposure.

A mounting error of two degrees is not visible by eye.

Demonstrating the shift

The effect can be shown directly with a white LED, a printed protractor and the Hα filter held at increasing angles.

Filter held perpendicular to a white LED beam; the light projected on paper is pure red.
Square on. The filter passes Hα and the projected beam is pure red.
Filter held at a moderate angle; the projected beam shows red and yellow.
Tilted. The passband has moved blueward far enough to let yellow through.
Filter held at a steep angle; the projected beam is yellow-green.
Steeply tilted. Yellow-green: the passband has moved far enough that it no longer overlaps Hα.

These angles are large, at tens of degrees, and far greater than a mounting error would produce. The same effect occurs at 2°, but is too small to see. The script measures it instead.

Method: three frames of one LED

The filter is placed in front of the sensor and a small bright source is photographed in three positions: in the center of the field of view, toward the left edge, and toward the right edge. The exposure settings are kept the same for all three.

If the filter is parallel to the sensor, the left and right frames are symmetric. If it is tilted about the vertical axis, they are not, since light reaches one side at a steeper angle than the other and the passband there has shifted further.

A Canon EOS R8 body with the lens removed, showing a small square filter placed over the sensor.
The rig: a 6 nm Hα filter sitting in front of the sensor of a Canon EOS R8 — deliberately not quite square, for the test.
Hands holding the camera aimed at a red bike LED on a cardboard box, with the live view showing the LED centered.
Frame 2 of 3: a red bike light as the source, LED centered in the field of view.
The same camera aimed so the red LED appears at the left edge of the live view.
LED toward the left edge.
The same camera aimed so the red LED appears at the right edge of the live view.
LED toward the right edge. Three RAW files, one exposure setting.

In the RAW frames, the LED is a small dot against a mostly black background.

A dark frame with a small pink LED dot near the left edge.
Left
A dark frame with a small pink LED dot near the center.
Center
A dark frame with a small pink LED dot near the right edge.
Right

Averaging over the whole frame would drown the signal in black pixels, so the source is located first. The script identifies every pixel more than four standard deviations above the channel mean as belonging to the light source, takes the bounding box of those pixels, and recalculates the statistics inside that box.

Cropped close-up of the LED from the left frame, showing a red gradient.
Left, cropped to the source.
Cropped close-up of the LED from the center frame.
Center, cropped to the source.
Cropped close-up of the LED from the right frame.
Right, cropped to the source.

Blue channel profiles

Within each cropped image, the script averages the blue channel along every row and every column, and plots the two profiles. The blue channel is used because it receives the least light through an Hα filter, and therefore responds most strongly when the passband shifts.

Two line plots for the left image: average blue value per column, peaking near 1200, and per row, rising to about 1400 before dropping sharply.
Left frame. Blue by column, then blue by row.
Two line plots for the center image: a symmetric arch peaking near 850, and a gentle ramp.
Center frame. A symmetric arch, peaking lower.
Two line plots for the right image: a column profile peaking near 880 and declining, and a row profile rising to about 1250.
Right frame. Compare the peak height and the slope against the left.

All values are 14-bit RAW values, with no white balance or gamma applied. The left crop peaks at roughly 1200 average blue per column, while the right crop peaks nearer 880, and the two profiles are not mirrored. This is the asymmetry the method detects. A filter mounted parallel to the sensor would produce two profiles that are near-reflections of each other, both similar in height to the center frame.

The verdict is not read off the plots. For each frame, the script computes the ratio of each color channel relative to the total light, compares the left and right ratios, and applies two rules.

  • Channels contributing less than 2% of the total light are skipped, since below that the comparison is between noise values.
  • If any remaining channel's ratio differs between left and right by more than 10%, the filter may be tilted.

Ratios are used rather than absolute values. The LED is not at the same distance from the lens in every shot, and vignetting darkens the edges of the frame. Both change the total amount of light collected, but neither changes how that light is divided between the color channels. Only the filter's passband does that.

Usage

pip install rawpy Pillow numpy matplotlib

python tiltedfilter.py \
  --center path/to/center.CR3 \
  --left   path/to/left.CR3 \
  --right  path/to/right.CR3

If no arguments are given, the script uses the filenames hardcoded at the top of the file. It writes center_crop.jpg, left_crop.jpg and right_crop.jpg, plus <position>_blue_by_column.png and <position>_blue_by_row.png for each of the three. The plots share a y-axis so that they can be compared. The script then prints one of two lines.

Your filter seems to have been mounted properly (parallel to the sensor).
Your filter might be tilted.

Inside the script

Everything lives in one AstroImageAnalyzer class: create_arr() loads the CR3 through rawpy and hands back a 16-bit RGB array; calc_stdev() takes the per-channel mean and standard deviation; crop() finds the bounding box of pixels above the sigma threshold; crop_to_jpeg() writes an 8-bit preview, which is used to check that the crop landed on the LED and not on a hot pixel; and blue_shift() builds the row and column profiles. A free function is_it_tilted() applies the two rules above, and _blue_plot_max_from_cropped() works out the shared y-axis maximum before any plotting starts.

The pipeline for each frame is: load → statistics → crop → statistics again. Recomputing after cropping is the important part: the first pass exists only to locate the light source, and every number used in the verdict comes from the second.

Tested configurations

Three combinations have been tested so far.

Red bike LED + 6 nm
Works. Correctly flags a tilted filter and correctly passes a properly mounted one. The automatic verdict is reliable for this pairing.
White bike LED + 6 nm OIII
Partly. Cropping and analysis work correctly, but the tilt signal is much weaker, and the automatic verdict may not flag a filter that is genuinely tilted. The reported left-versus-right percentage differences should be checked manually.
Ulanzi RGB light + 6 nm OIII
Works. This source puts far more energy through an OIII filter than a white bike light does, and the tilt is detected correctly.

The method depends on the match between the light source and the filter. The source must emit both inside and just outside the filter's passband, so that a small shift in the band produces a large change in the transmitted light. The OIII result with a white LED is therefore a limitation of the light source rather than of the method.

Two further limitations. The test detects tilt about the vertical axis only, since left and right are the only positions compared. A filter rotated about the horizontal axis would require top and bottom frames and the same analysis. Additionally, the 2% and 10% thresholds were chosen rather than derived. They work for the configurations above, and would need revisiting for a filter of a different bandwidth.

Resources