def test03_mueller_to_world_to_local(variant_scalar_mono_polarized):
    """
    At a few places, coordinate changes between local BSDF reference frame and
    world coordinates need to take place. This change also needs to be applied
    to Mueller matrices used in computations involving polarization state.

    In practice, this is always a simple rotation of reference Stokes vectors
    (for incident & outgoing directions) of the Mueller matrix.

    To test this behavior we take any Mueller matrix (e.g. linear polarizer)
    for some arbitrary incident/outgoing directions in world coordinates and
    compute the round trip going to local frame and back again.
    """
    from mitsuba.core import Frame3f, UnpolarizedSpectrum
    from mitsuba.render import SurfaceInteraction3f
    from mitsuba.render.mueller import linear_polarizer
    import numpy as np

    si = SurfaceInteraction3f()
    si.sh_frame = Frame3f(ek.normalize([1.0, 1.0, 1.0]))

    M = linear_polarizer(UnpolarizedSpectrum(1.0))

    # Random incident and outgoing directions
    wi_world = ek.normalize([0.2, 0.0, 1.0])
    wo_world = ek.normalize([0.0, -0.8, 1.0])

    wi_local = si.to_local(wi_world)
    wo_local = si.to_local(wo_world)

    M_local = si.to_local_mueller(M, wi_world, wo_world)
    M_world = si.to_world_mueller(M_local, wi_local, wo_local)

    assert ek.allclose(M, M_world, atol=1e-5)
Пример #2
0
def test_sample_ray(variant_scalar_rgb, direction, origin):
    sample1 = [0.32, 0.87]
    sample2 = [0.16, 0.44]

    sensor = make_sensor(direction=direction, origin=origin)

    # Test regular ray sampling
    ray = sensor.sample_ray(1., 1., sample1, sample2, True)
    assert ek.allclose(ray[0].o, origin)
    assert ek.allclose(ray[0].d, ek.normalize(direction))

    # Test ray differential sampling
    ray = sensor.sample_ray_differential(1., 1., sample2, sample1, True)
    assert ek.allclose(ray[0].o, origin)
    assert ek.allclose(ray[0].d, ek.normalize(direction))
    assert not ray[0].has_differentials
Пример #3
0
def weight_measurements(spec,
                        D,
                        sigma,
                        theta_i,
                        phi_i,
                        theta_o,
                        phi_o,
                        active=None):
    from mitsuba.core import MarginalContinuous2D0, Vector2f
    m_ndf = MarginalContinuous2D0(D, normalize=False)
    m_sigma = MarginalContinuous2D0(sigma, normalize=False)
    scaled = np.zeros(spec.shape)
    n_phi = spec.shape[-2]
    n_theta = spec.shape[-1]

    for i in range(phi_i.size):
        for j in range(theta_i.size):
            # Incient direction
            wi = spherical2cartesian(theta_i[j], phi_i[i])
            u_wi = Vector2f(theta2u(theta_i[j]), phi2u(phi_i[i]))
            # Outgoing direction
            wo = spherical2cartesian(theta_o[i, j].flatten(),
                                     phi_o[i, j].flatten())
            # Phase direction
            m = ek.normalize(wi + wo)
            theta_m, phi_m = cartesian2spherical(wo)
            u_m = Vector2f(theta2u(theta_m), phi2u(phi_m))
            # Scale by inverse jacobian
            jacobian = m_ndf.eval(u_m) / (4 * m_sigma.eval(u_wi))
            scaled[i, j] = spec[i, j] / np.reshape(jacobian, (n_phi, n_theta))
    if not active is None:
        n_wavelenths = spec.shape[2]
        for i in range(n_wavelenths):
            scaled[:, :, i][~active] = 0
    return scaled
Пример #4
0
def test_sample_ray(variant_scalar_rgb, direction, origin):
    sample1 = [0.32, 0.87]
    sample2 = [0.16, 0.44]
    direction_str = ",".join([str(x) for x in direction])
    origin_str = ",".join([str(x) for x in origin])
    sensor = example_sensor(params=xml_direction(direction_str) +
                            xml_origin(origin_str))

    # Test regular ray sampling
    ray = sensor.sample_ray(1., 1., sample1, sample2, True)
    assert ek.allclose(ray[0].o, origin)
    assert ek.allclose(ray[0].d, ek.normalize(direction))

    # Test ray differential sampling
    ray = sensor.sample_ray_differential(1., 1., sample2, sample1, True)
    assert ek.allclose(ray[0].o, origin)
    assert ek.allclose(ray[0].d, ek.normalize(direction))
    assert not ray[0].has_differentials
def test02_chi2_rough_grazing(variant_packet_rgb):
    xml = """<float name="alpha" value="0.5"/>"""
    wi = ek.normalize([0.8, 0.3, 0.05])
    sample_func, pdf_func = BSDFAdapter("roughdielectric", xml, wi=wi)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3)

    assert chi2.run()
Пример #6
0
def test01_chi2_smooth(variant_packet_rgb):
    xml = """<float name="alpha" value="0.05"/>"""
    wi = ek.normalize([1.0, 1.0, 1.0])
    sample_func, pdf_func = BSDFAdapter("roughconductor", xml, wi=wi)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3,
                         ires=8)

    assert chi2.run()
def test07_chi2_rough_from_inside(variant_packet_rgb):
    xml = """<float name="alpha" value="0.5"/>"""
    wi = ek.normalize([0.2, -0.6, -0.5])
    sample_func, pdf_func = BSDFAdapter("roughdielectric", xml, wi=wi)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3,
                         res=201)

    assert chi2.run()
Пример #8
0
def test02_sample_ray(variant_packet_spectral, origin, direction, aperture_rad,
                      focus_dist):
    # Check the correctness of the sample_ray() method

    from mitsuba.core import sample_shifted, sample_rgb_spectrum, warp, Vector3f, Transform4f

    cam = create_camera(origin,
                        direction,
                        aperture=aperture_rad,
                        focus_dist=focus_dist)

    time = 0.5
    wav_sample = [0.5, 0.33, 0.1]
    pos_sample = [[0.2, 0.1, 0.2], [0.6, 0.9, 0.2]]
    aperture_sample = [0.5, 0.5]

    ray, spec_weight = cam.sample_ray(time, wav_sample, pos_sample,
                                      aperture_sample)

    # Importance sample wavelength and weight
    wav, spec = sample_rgb_spectrum(sample_shifted(wav_sample))

    assert ek.allclose(ray.wavelengths, wav)
    assert ek.allclose(spec_weight, spec)
    assert ek.allclose(ray.time, time)
    assert ek.allclose(ray.o, origin)

    # Check that a [0.5, 0.5] position_sample and [0.5, 0.5] aperture_sample
    # generates a ray that points in the camera direction

    ray, _ = cam.sample_ray(0, 0, [0.5, 0.5], [0.5, 0.5])
    assert ek.allclose(ray.d, direction, atol=1e-7)

    # ----------------------------------------
    # Check correctness of aperture sampling

    pos_sample = [0.5, 0.5]
    aperture_sample = [[0.9, 0.4, 0.2], [0.6, 0.9, 0.7]]
    ray, _ = cam.sample_ray(time, wav_sample, pos_sample, aperture_sample)

    ray_centered, _ = cam.sample_ray(time, wav_sample, pos_sample, [0.5, 0.5])

    trafo = Transform4f(cam.world_transform().eval(time).matrix.numpy())
    tmp = warp.square_to_uniform_disk_concentric(aperture_sample)
    aperture_v = trafo.transform_vector(aperture_rad *
                                        Vector3f(tmp.x, tmp.y, 0))

    # NOTE: here we assume near_clip = 1.0

    assert ek.allclose(ray.o, ray_centered.o + aperture_v)
    assert ek.allclose(ray.d,
                       ek.normalize(ray_centered.d * focus_dist - aperture_v))
def test06_chi2_rough_ggx_visible(variant_packet_rgb):
    xml = """<float name="alpha" value="0.5"/>
             <boolean name="sample_visible" value="true"/>
             <string name="distribution" value="ggx"/>
          """
    wi = ek.normalize([0.5, 0.5, 0.001])
    sample_func, pdf_func = BSDFAdapter("roughdielectric", xml, wi=wi)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3)

    assert chi2.run()
Пример #10
0
def test02_eval(variant_packet_spectral, spectrum_key):
    # Check that eval() return the same values as the 'radiance' spectrum

    from mitsuba.render import SurfaceInteraction3f

    shape, spectrum = create_emitter_and_spectrum(spectrum_key)
    emitter = shape.emitter()

    it = SurfaceInteraction3f.zero(3)
    assert ek.allclose(emitter.eval(it), spectrum.eval(it))

    # Check that eval return 0.0 when direction points inside the shape

    it.wi = ek.normalize([0.2, 0.2, -0.5])
    assert ek.allclose(emitter.eval(it), 0.0)
Пример #11
0
def test05_chi2_aniso_ggx_visible(variant_packet_rgb):
    xml = """<float name="alpha_u" value="0.2"/>
             <float name="alpha_v" value="0.05"/>
             <string name="distribution" value="ggx"/>
             <boolean name="sample_visible" value="true"/>"""
    wi = ek.normalize([1.0, 1.0, 1.0])
    sample_func, pdf_func = BSDFAdapter("roughconductor", xml, wi=wi)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3,
                         ires=8)

    assert chi2.run()
def test03_chi2_rough_beckmann_all(variant_packet_rgb):
    xml = """<float name="alpha" value="0.5"/>
             <boolean name="sample_visible" value="false"/>
             <string name="distribution" value="beckmann"/>
          """
    wi = ek.normalize([0.5, 0.0, 0.5])
    sample_func, pdf_func = BSDFAdapter("roughdielectric", xml, wi=wi)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3,
                         res=201)

    assert chi2.run()
def test11_chi2_aniso_lobe_trans(variant_packet_rgb):
    from mitsuba.render import BSDFContext

    xml = """
    <float name="alpha_u" value="0.5"/>
    <float name="alpha_v" value="0.2"/>
    """
    wi = ek.normalize([-0.5, -0.5, 0.1])
    ctx = BSDFContext()
    ctx.component = 1
    sample_func, pdf_func = BSDFAdapter("roughdielectric", xml, wi=wi, ctx=ctx)

    chi2 = ChiSquareTest(domain=SphericalDomain(),
                         sample_func=sample_func,
                         pdf_func=pdf_func,
                         sample_dim=3,
                         res=201)

    assert chi2.run()
Пример #14
0
def log_(a0):
    if not a0.IsFloat:
        raise Exception("log(): requires floating point operands!")
    ar, sr = _check1(a0)
    if not a0.IsSpecial:
        for i in range(sr):
            ar[i] = _ek.log(a0[i])
    elif a0.IsComplex:
        ar.real = .5 * _ek.log(_ek.squared_norm(a0))
        ar.imag = _ek.arg(a0)
    elif a0.IsQuaternion:
        qi_n = _ek.normalize(a0.imag)
        rq = _ek.norm(a0)
        acos_rq = _ek.acos(a0.real / rq)
        log_rq = _ek.log(rq)
        ar.imag = qi_n * acos_rq
        ar.real = log_rq
    else:
        raise Exception("log(): unsupported array type!")
    return ar
Пример #15
0
def test_sampling(variant_scalar_rgb, center, radius):
    """
    We construct an irradiance meter attached to a sphere and assert that sampled
    rays originate at the sphere's surface
    """
    from mitsuba.core import Vector3f

    center_v = Vector3f(center)
    sphere = example_shape(radius, center_v)
    sensor = sphere.sensor()
    num_samples = 100

    wav_samples = np.random.rand(num_samples)
    pos_samples = np.random.rand(num_samples, 2)
    dir_samples = np.random.rand(num_samples, 2)

    for i in range(100):
        ray = sensor.sample_ray_differential(0.0, wav_samples[i],
                                             pos_samples[i], dir_samples[i])[0]

        # assert that the ray starts at the sphere surface
        assert ek.allclose(ek.norm(center_v - ray.o), radius)
        # assert that all rays point away from the sphere center
        assert ek.dot(ek.normalize(ray.o - center_v), ray.d) > 0.0
Пример #16
0
p = np.array([np.random.randint(res[0]), np.random.randint(res[1])]) * inv_res

ray, _ = camera.sample_ray_differential(0.0, 0.5, p, [0.5, 0.5])
si = scene.ray_intersect(ray)
ei = SpecularManifold.sample_emitter_interaction(si, scene.emitters(), sampler)

solutions = []
solutions_uv = []
solutions_p = []
n_tries = 500
for tries in range(n_tries):
    print("Find solutions: %.2f%%" % (100 * tries / (n_tries - 1)), end="\r")
    success, si_final, _ = mf.sample_path(spec_shape, si, ei, sampler)
    if not success:
        continue
    direction = ek.normalize(si_final.p - si.p)

    duplicate = False
    for s in solutions:
        if np.abs((direction @ s) - 1) < 1e-5:
            duplicate = True
            break
    if duplicate:
        continue

    solutions.append(direction)
    solutions_uv.append(si_final.uv)
    solutions_p.append(si_final.p)
print("")

# print("Found {} solutions.".format(len(solutions)))
for j in range(res[0]):
    print("Identify glinty pixels: %.2f%%" % (100 * j / (res[0] - 1)),
          end="\r")
    for i in range(res[1]):
        if glint_mask[i, j] == 0:
            continue
        p = np.array([j + 0.5, i + 0.5]) * inv_res

        ray, _ = camera.sample_ray_differential(0.0, 0.5, p, [0.5, 0.5])
        si = scene.ray_intersect(ray)
        si.compute_partials(ray)

        ei = SpecularManifold.sample_emitter_interaction(
            si, scene.emitters(), sampler)

        wi = ek.normalize(ray.o - si.p)
        wo = ek.normalize(ei.p - si.p)
        h = ek.normalize(wi + wo)

        n_offset = np.array([0, 0, 1])
        for k in range(1):
            success, uv_final, _, = mf.sample_glint(ray.o, ei, si, sampler,
                                                    n_offset)
            if success:
                glinty_pixels.append((i, j))
                break
print("")

# print("Found {} glinty pixels".format(len(glinty_pixels)))

idx = 238  # Manually chosen index that gives nice looking results
def test02_sample_pol_world(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Spectrum, UnpolarizedSpectrum
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f, fresnel_conductor
    from mitsuba.render.mueller import stokes_basis, rotate_mueller_basis

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Create a Silver conductor BSDF and reflect different polarization states
    # at a 45˚ angle.
    # This test takes place in world coordinates and thus involves additional
    # coordinate system rotations.
    #
    # The setup is as follows:
    # - The mirror is positioned at [0, 0, 0], angled s.t. the surface normal
    #   is along [1, 1, 0].
    # - Incident ray w1 = [-1, 0, 0] strikes the mirror at a 45˚ angle and
    #   reflects into direction w2 = [0, 1, 0]
    # - The corresponding Stokes bases are b1 = [0, 1, 0] and b2 = [1, 0, 0].

    # Setup
    eta = 0.136125 + 4.010625j  # IoR values of Ag at 635.816284nm
    bsdf = load_string("""<bsdf version='2.0.0' type='conductor'>
                              <spectrum name="eta" value="{}"/>
                              <spectrum name="k" value="{}"/>
                          </bsdf>""".format(eta.real, eta.imag))
    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.n = ek.normalize([1.0, 1.0, 0.0])
    si.sh_frame = Frame3f(si.n)

    # Incident / outgoing directions and their Stokes bases
    w1 = ek.scalar.Vector3f([-1, 0, 0])
    b1 = [0, 1, 0]
    w2 = ek.scalar.Vector3f([0, 1, 0])
    b2 = [1, 0, 0]

    # Perform actual reflection
    si.wi = si.to_local(-w1)
    bs, M_tmp = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])
    M_world = si.to_world_mueller(M_tmp, -si.wi, bs.wo)

    # Test that outgoing direction is as predicted
    assert ek.allclose(si.to_world(bs.wo), w2, atol=1e-5)

    # Align reference frames s.t. polarization is expressed w.r.t. b1 & b2
    M_world = rotate_mueller_basis(M_world, w1, stokes_basis(w1), b1, w2,
                                   stokes_basis(w2), b2)

    # Apply to unpolarized light and verify that it is equivalent to normal Fresnel
    a0 = M_world @ spectrum_from_stokes([1, 0, 0, 0])
    F = fresnel_conductor(si.wi[2], ek.scalar.Complex2f(eta.real, eta.imag))
    a0 = a0[0, 0]
    assert ek.allclose(a0[0], F, atol=1e-3)

    # Apply to horizontally polarized light (linear at 0˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still horizontally polarized
    a1 = M_world @ spectrum_from_stokes([1, +1, 0, 0])
    assert ek.allclose(a1[0, 0], ek.abs(a1[1, 0]), atol=1e-3)  # 1)
    a1 /= a1[0, 0]
    assert ek.allclose(a1, spectrum_from_stokes([1, 1, 0, 0]), atol=1e-3)  # 2)

    # Apply to vertically polarized light (linear at 90˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still vertically polarized
    a2 = M_world @ spectrum_from_stokes([1, -1, 0, 0])
    assert ek.allclose(a2[0, 0], ek.abs(a2[1, 0]), atol=1e-3)  # 1)
    a2 /= a2[0, 0]
    assert ek.allclose(a2, spectrum_from_stokes([1, -1, 0, 0]),
                       atol=1e-3)  # 2)

    # Apply to (positive) diagonally polarized light (linear at +45˚)
    # Test that..
    # 1) The polarization is flipped to -45˚
    # 2) There is now also some (left) circular polarization
    a3 = M_world @ spectrum_from_stokes([1, 0, +1, 0])
    assert ek.all(a3[2, 0] < UnpolarizedSpectrum(0.0))
    assert ek.all(a3[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to (negative) diagonally polarized light (linear at -45˚)
    # Test that..
    # 1) The polarization is flipped to +45˚
    # 2) There is now also some (right) circular polarization
    a4 = M_world @ spectrum_from_stokes([1, 0, -1, 0])
    assert ek.all(a4[2, 0] > UnpolarizedSpectrum(0.0))
    assert ek.all(a4[3, 0] > UnpolarizedSpectrum(0.0))

    # Apply to right circularly polarized light
    # Test that the polarization is flipped to left circular
    a5 = M_world @ spectrum_from_stokes([1, 0, 0, +1])
    assert ek.all(a5[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to left circularly polarized light
    # Test that the polarization is flipped to right circular
    a6 = M_world @ spectrum_from_stokes([1, 0, 0, -1])
    assert ek.all(a6[3, 0] > UnpolarizedSpectrum(0.0))
Пример #19
0
def test03_sample_ray_diff(variant_packet_spectral, origin, direction,
                           aperture_rad, focus_dist):
    # Check the correctness of the sample_ray_differential() method

    from mitsuba.core import sample_shifted, sample_rgb_spectrum, warp, Vector3f, Transform4f

    cam = create_camera(origin,
                        direction,
                        aperture=aperture_rad,
                        focus_dist=focus_dist)

    time = 0.5
    wav_sample = [0.5, 0.33, 0.1]
    pos_sample = [[0.2, 0.1, 0.2], [0.6, 0.9, 0.2]]
    aperture_sample = [0.5, 0.5]

    ray, spec_weight = cam.sample_ray_differential(time, wav_sample,
                                                   pos_sample, aperture_sample)

    # Importance sample wavelength and weight
    wav, spec = sample_rgb_spectrum(sample_shifted(wav_sample))

    assert ek.allclose(ray.wavelengths, wav)
    assert ek.allclose(spec_weight, spec)
    assert ek.allclose(ray.time, time)
    assert ek.allclose(ray.o, origin)

    # ----------------------------------------_
    # Check that the derivatives are orthogonal

    assert ek.allclose(ek.dot(ray.d_x - ray.d, ray.d_y - ray.d), 0, atol=1e-7)

    # Check that a [0.5, 0.5] position_sample and [0.5, 0.5] aperture_sample
    # generates a ray that points in the camera direction

    ray_center, _ = cam.sample_ray_differential(0, 0, [0.5, 0.5], [0.5, 0.5])
    assert ek.allclose(ray_center.d, direction, atol=1e-7)

    # ----------------------------------------
    # Check correctness of the ray derivatives

    aperture_sample = [[0.9, 0.4, 0.2], [0.6, 0.9, 0.7]]
    ray_center, _ = cam.sample_ray_differential(0, 0, [0.5, 0.5],
                                                aperture_sample)

    # Deltas in screen space
    dx = 1.0 / cam.film().crop_size().x
    dy = 1.0 / cam.film().crop_size().y

    # Sample the rays by offsetting the position_sample with the deltas (aperture centered)
    ray_dx, _ = cam.sample_ray_differential(0, 0, [0.5 + dx, 0.5],
                                            aperture_sample)
    ray_dy, _ = cam.sample_ray_differential(0, 0, [0.5, 0.5 + dy],
                                            aperture_sample)

    assert ek.allclose(ray_dx.d, ray_center.d_x)
    assert ek.allclose(ray_dy.d, ray_center.d_y)

    # --------------------------------------
    # Check correctness of aperture sampling

    pos_sample = [0.5, 0.5]
    aperture_sample = [[0.9, 0.4, 0.2], [0.6, 0.9, 0.7]]
    ray, _ = cam.sample_ray(time, wav_sample, pos_sample, aperture_sample)

    ray_centered, _ = cam.sample_ray(time, wav_sample, pos_sample, [0.5, 0.5])

    trafo = Transform4f(cam.world_transform().eval(time).matrix.numpy())
    tmp = warp.square_to_uniform_disk_concentric(aperture_sample)
    aperture_v = trafo.transform_vector(aperture_rad *
                                        Vector3f(tmp.x, tmp.y, 0))

    # NOTE: here we assume near_clip = 1.0

    assert ek.allclose(ray.o, ray_centered.o + aperture_v)
    assert ek.allclose(ray.d,
                       ek.normalize(ray_centered.d * focus_dist - aperture_v))
def test02_sample_pol_local(variant_scalar_mono_polarized):
    from mitsuba.core import Frame3f, Transform4f, Spectrum, UnpolarizedSpectrum, Vector3f
    from mitsuba.core.xml import load_string
    from mitsuba.render import BSDFContext, TransportMode, SurfaceInteraction3f, fresnel_conductor
    from mitsuba.render.mueller import stokes_basis, rotate_mueller_basis

    def spectrum_from_stokes(v):
        res = Spectrum(0.0)
        for i in range(4):
            res[i, 0] = v[i]
        return res

    # Create a Silver conductor BSDF and reflect different polarization states
    # at a 45˚ angle.
    # All tests take place directly in local BSDF coordinates. Here we only
    # want to make sure that the output of this looks like what you would
    # expect from a Mueller matrix describing specular reflection on a mirror.

    eta = 0.136125 + 4.010625j  # IoR values of Ag at 635.816284nm
    bsdf = load_string("""<bsdf version='2.0.0' type='conductor'>
                              <spectrum name="eta" value="{}"/>
                              <spectrum name="k" value="{}"/>
                          </bsdf>""".format(eta.real, eta.imag))

    theta_i = 45 * ek.pi / 180
    wi = Vector3f([-ek.sin(theta_i), 0, ek.cos(theta_i)])

    ctx = BSDFContext()
    ctx.mode = TransportMode.Importance
    si = SurfaceInteraction3f()
    si.p = [0, 0, 0]
    si.wi = wi
    n = [0, 0, 1]
    si.sh_frame = Frame3f(n)

    bs, M_local = bsdf.sample(ctx, si, 0.0, [0.0, 0.0])
    wo = bs.wo

    # Rotate into standard coordinates for specular reflection
    bi_s = ek.normalize(ek.cross(n, -wi))
    bi_p = ek.normalize(ek.cross(-wi, bi_s))
    bo_s = ek.normalize(ek.cross(n, wo))
    bo_p = ek.normalize(ek.cross(wo, bi_s))

    M_local = rotate_mueller_basis(M_local, -wi, stokes_basis(-wi), bi_p, wo,
                                   stokes_basis(wo), bo_p)

    # Apply to unpolarized light and verify that it is equivalent to normal Fresnel
    a0 = M_local @ spectrum_from_stokes([1, 0, 0, 0])
    F = fresnel_conductor(ek.cos(theta_i),
                          ek.scalar.Complex2f(eta.real, eta.imag))
    a0 = a0[0, 0]
    assert ek.allclose(a0[0], F, atol=1e-3)

    # Apply to horizontally polarized light (linear at 0˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still horizontally polarized
    a1 = M_local @ spectrum_from_stokes([1, +1, 0, 0])
    assert ek.allclose(a1[0, 0], ek.abs(a1[1, 0]), atol=1e-3)  # 1)
    a1 /= a1[0, 0]
    assert ek.allclose(a1, spectrum_from_stokes([1, 1, 0, 0]), atol=1e-3)  # 2)

    # Apply to vertically polarized light (linear at 90˚)
    # Test that it is..
    # 1) still fully polarized (though with reduced intensity)
    # 2) still vertically polarized
    a2 = M_local @ spectrum_from_stokes([1, -1, 0, 0])
    assert ek.allclose(a2[0, 0], ek.abs(a2[1, 0]), atol=1e-3)  # 1)
    a2 /= a2[0, 0]
    assert ek.allclose(a2, spectrum_from_stokes([1, -1, 0, 0]),
                       atol=1e-3)  # 2)

    # Apply to (positive) diagonally polarized light (linear at +45˚)
    # Test that..
    # 1) The polarization is flipped to -45˚
    # 2) There is now also some (left) circular polarization
    a3 = M_local @ spectrum_from_stokes([1, 0, +1, 0])
    assert ek.all(a3[2, 0] < UnpolarizedSpectrum(0.0))
    assert ek.all(a3[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to (negative) diagonally polarized light (linear at -45˚)
    # Test that..
    # 1) The polarization is flipped to +45˚
    # 2) There is now also some (right) circular polarization
    a4 = M_local @ spectrum_from_stokes([1, 0, -1, 0])
    assert ek.all(a4[2, 0] > UnpolarizedSpectrum(0.0))
    assert ek.all(a4[3, 0] > UnpolarizedSpectrum(0.0))

    # Apply to right circularly polarized light
    # Test that the polarization is flipped to left circular
    a5 = M_local @ spectrum_from_stokes([1, 0, 0, +1])
    assert ek.all(a5[3, 0] < UnpolarizedSpectrum(0.0))

    # Apply to left circularly polarized light
    # Test that the polarization is flipped to right circular
    a6 = M_local @ spectrum_from_stokes([1, 0, 0, -1])
    assert ek.all(a6[3, 0] > UnpolarizedSpectrum(0.0))
p = np.array([np.random.randint(res[0]), np.random.randint(res[1])]) * inv_res

ray, _ = camera.sample_ray_differential(0.0, 0.5, p, [0.5, 0.5])
si = scene.ray_intersect(ray)
ei = SpecularManifold.sample_emitter_interaction(si, scene.emitters(), sampler)

solutions = []
solutions_uv = []
solutions_p = []
n_tries = 500
for tries in range(n_tries):
    print("Find solutions: %.2f%%" % (100 * tries / (n_tries - 1)), end="\r")
    success, si_final, _ = mf.sample_path(spec_shape, si, ei, sampler)
    if not success:
        continue
    direction = ek.normalize(si_final.p - si.p)

    duplicate = False
    for s in solutions:
        if np.abs((direction @ s) - 1) < 1e-5:
            duplicate = True
            break
    if duplicate:
        continue

    solutions.append(direction)
    solutions_uv.append(si_final.uv)
    solutions_p.append(si_final.p)
print("")

# print("Found {} solutions.".format(len(solutions)))