コード例 #1
0
def test_bias_correction():
    """Functional end to end test for active calibration"""

    np.random.seed(0)
    force_voltage_data, driving_data = generate_active_calibration_test_data(
        duration=20,
        sample_rate=78125,
        bead_diameter=1.03,
        stiffness=0.2,
        viscosity=1.002e-3,
        temperature=20,
        pos_response_um_volt=0.618,
        driving_sinusoid=(500, 31.95633),
        diode=(0.4, 13000),
    )

    model = ActiveCalibrationModel(driving_data, force_voltage_data, 78125,
                                   1.03, 32, 1.002e-3, 20)

    # Low blocking deliberately leads to higher bias (so it's easier to measure)
    block_size = 3
    power_spectrum_low = calculate_power_spectrum(
        force_voltage_data, 78125, num_points_per_block=block_size)

    fit_biased = fit_power_spectrum(power_spectrum_low,
                                    model,
                                    bias_correction=False)
    fit_debiased = fit_power_spectrum(power_spectrum_low,
                                      model,
                                      bias_correction=True)

    bias_corr = block_size / (block_size + 1)
    np.testing.assert_allclose(fit_debiased["D"].value,
                               fit_biased["D"].value * bias_corr)
    np.testing.assert_allclose(fit_debiased["err_D"].value,
                               fit_biased["err_D"].value * bias_corr)

    # Biased vs debiased estimates (in comments are the reference values for N_pts_per_block = 150
    # Note how the estimates are better on the right.
    comparisons = {
        "fc": [3310.651532245893,
               3310.651532245893],  # Ref: 3277.6576037747836
        "D": [1.472922058628551,
              1.1046915439714131],  # Ref: 1.0896306365192108
        "kappa": [0.15317517466591019,
                  0.2043759281959786],  # Ref: 0.20106518840690035
        "Rd": [0.6108705452113169,
               0.6106577513480039],  # Ref: 0.6168083172053238
        "Rf": [93.57020246100325, 124.8037447418174],  # Ref: 124.0186805098316
    }

    for key, values in comparisons.items():
        for fit, value in zip([fit_biased, fit_debiased], values):
            np.testing.assert_allclose(fit[key].value, value)

    assert fit_biased.params["Bias correction"].value is False
    assert fit_debiased.params["Bias correction"].value is True
コード例 #2
0
def test_integration_active_calibration_hydrodynamics_bulk(
        integration_test_parameters):
    shared_pars, simulation_pars = integration_test_parameters

    np.random.seed(10071985)
    shared_pars["distance_to_surface"] = None
    volts, nanostage = generate_active_calibration_test_data(
        10, **simulation_pars, **shared_pars)
    model = ActiveCalibrationModel(
        nanostage,
        volts,
        **shared_pars,
        sample_rate=simulation_pars["sample_rate"],
        driving_frequency_guess=33,
    )
    power_spectrum = calculate_power_spectrum(volts,
                                              simulation_pars["sample_rate"])
    fit = fit_power_spectrum(power_spectrum, model, bias_correction=False)

    expected_params = {
        "Sample density": 997.0,
        "Bead density": 1040.0,
        "Bead diameter": 1.03,
        "Viscosity": 0.0011,
        "Temperature": 25,
        "Driving frequency (guess)": 33,
        "Sample rate": 78125,
        "num_windows": 5,
        "Max iterations": 10000,
        "Fit tolerance": 1e-07,
        "Points per block": 2000,
    }
    expected_results = {
        "Rd": 0.6095674943889238,
        "kappa": 0.10359295685924054,
        "Rf": 63.14689914902713,
        "gamma_0": 1.0678273429551705e-08,
        "gamma_ex": 1.0978355408018856e-08,
        "fc": 1501.803370440244,
        "D": 1.0091069801313286,
        "f_diode": 14669.862556235465,
        "alpha": 0.41657472149713015,
        "err_fc": 11.599562805624199,
        "err_D": 0.007332334985757522,
        "err_f_diode": 376.8360414675165,
        "err_alpha": 0.014653541838852356,
        "chi_squared_per_deg": 0.8692145118092963,
        "backing": 14.917612794899505,
    }

    assert fit.params["Distance to surface"].value is None
    for key, value in expected_params.items():
        np.testing.assert_allclose(fit.params[key].value, value, err_msg=key)

    for key, value in expected_results.items():
        np.testing.assert_allclose(fit.results[key].value, value, err_msg=key)
コード例 #3
0
def test_integration_active_calibration_hydrodynamics(
        integration_test_parameters):
    shared_pars, simulation_pars = integration_test_parameters

    np.random.seed(10071985)
    volts, nanostage = generate_active_calibration_test_data(
        10, **simulation_pars, **shared_pars)
    model = ActiveCalibrationModel(
        nanostage,
        volts,
        **shared_pars,
        sample_rate=simulation_pars["sample_rate"],
        driving_frequency_guess=33,
    )
    power_spectrum = calculate_power_spectrum(volts,
                                              simulation_pars["sample_rate"])
    fit = fit_power_spectrum(power_spectrum, model, bias_correction=False)
    expected_params = {
        "Sample density": 997.0,
        "Bead density": 1040.0,
        "Distance to surface": 0.7776500000000001,
        "Bead diameter": 1.03,
        "Viscosity": 0.0011,
        "Temperature": 25,
        "Driving frequency (guess)": 33,
        "Sample rate": 78125,
        "num_windows": 5,
        "Max iterations": 10000,
        "Fit tolerance": 1e-07,
        "Points per block": 2000,
    }
    expected_results = {
        "Rd": 0.6092796748780891,
        "kappa": 0.10388246375443001,
        "Rf": 63.29347374183399,
        "gamma_0": 1.0678273429551705e-08,
        "gamma_ex": 1.0989730336350438e-08,
        "fc": 1504.4416105821158,
        "D": 1.0090151317063,
        "err_fc": 13.075876724291339,
        "err_D": 0.0066021439072302835,
        "f_diode": 14675.638696737586,
        "alpha": 0.41651098052983593,
        "err_f_diode": 352.2917702189488,
        "err_alpha": 0.014231238753589254,
        "chi_squared_per_deg": 0.8659867914094764,
        "backing": 14.340689726784328,
    }

    for key, value in expected_params.items():
        np.testing.assert_allclose(fit.params[key].value, value, err_msg=key)

    for key, value in expected_results.items():
        np.testing.assert_allclose(fit.results[key].value, value, err_msg=key)
コード例 #4
0
def reference_calibration_result():
    data = np.load(
        os.path.join(os.path.dirname(__file__), "reference_spectrum.npz"))
    reference_spectrum = data["arr_0"]
    model = PassiveCalibrationModel(4.4, temperature=20, viscosity=0.001002)
    reference_spectrum = psc.calculate_power_spectrum(reference_spectrum,
                                                      sample_rate=78125,
                                                      num_points_per_block=100,
                                                      fit_range=(100.0,
                                                                 23000.0))
    ps_calibration = psc.fit_power_spectrum(power_spectrum=reference_spectrum,
                                            model=model,
                                            bias_correction=False)

    return ps_calibration, model, reference_spectrum
コード例 #5
0
def test_faxen_correction():
    """When hydro is off, but a height is given, the interpretation of the Lorentzian fit can still
    benefit from using the distance to the surface in a correction factor for the drag. This will
    only affect thermal calibration. This behaviour is tested here."""
    shared_pars = {
        "bead_diameter": 1.03,
        "viscosity": 1.1e-3,
        "temperature": 25,
        "rho_sample": 997.0,
        "rho_bead": 1040.0,
        "distance_to_surface": 1.03 / 2 + 400e-3,
    }
    sim_pars = {
        "sample_rate": 78125,
        "stiffness": 0.1,
        "pos_response_um_volt": 0.618,
        "driving_sinusoid": (500, 31.95633),
        "diode": (0.4, 15000),
    }

    np.random.seed(10071985)
    volts, _ = generate_active_calibration_test_data(
        10, hydrodynamically_correct=True, **sim_pars, **shared_pars)
    power_spectrum = psc.calculate_power_spectrum(volts,
                                                  sim_pars["sample_rate"])

    model = PassiveCalibrationModel(**shared_pars,
                                    hydrodynamically_correct=False)
    fit = psc.fit_power_spectrum(power_spectrum, model, bias_correction=False)

    # Fitting with *no* hydrodynamically correct model, but *with* Faxen's law
    np.testing.assert_allclose(fit.results["Rd"].value, 0.6136895577998873)
    np.testing.assert_allclose(fit.results["kappa"].value, 0.10312266251783221)
    np.testing.assert_allclose(fit.results["Rf"].value, 63.285301159715466)
    np.testing.assert_allclose(fit.results["gamma_0"].value,
                               1.0678273429551705e-08)

    # Disabling Faxen's correction on the drag makes the estimates *much* worse
    shared_pars["distance_to_surface"] = None
    model = PassiveCalibrationModel(**shared_pars,
                                    hydrodynamically_correct=False)
    fit = psc.fit_power_spectrum(power_spectrum, model, bias_correction=False)
    np.testing.assert_allclose(fit.results["Rd"].value, 0.741747603986908)
    np.testing.assert_allclose(fit.results["kappa"].value, 0.07058936587810064)
    np.testing.assert_allclose(fit.results["Rf"].value, 52.35949300703634)
    # Not affected since this is gamma bulk
    np.testing.assert_allclose(fit.results["gamma_0"].value,
                               1.0678273429551705e-08)
コード例 #6
0
def test_integration_passive_calibration_hydrodynamics(
        integration_test_parameters):
    shared_pars, simulation_pars = integration_test_parameters

    np.random.seed(10071985)
    volts, _ = generate_active_calibration_test_data(10, **simulation_pars,
                                                     **shared_pars)
    model = PassiveCalibrationModel(**shared_pars)
    power_spectrum = calculate_power_spectrum(volts,
                                              simulation_pars["sample_rate"])
    fit = fit_power_spectrum(power_spectrum, model, bias_correction=False)

    expected_params = {
        "Sample density": 997.0,
        "Bead density": 1040.0,
        "Distance to surface": 0.7776500000000001,
        "Bead diameter": 1.03,
        "Viscosity": 0.0011,
        "Temperature": 25,
        "Max iterations": 10000,
        "Fit tolerance": 1e-07,
        "Points per block": 2000,
        "Sample rate": 78125,
    }
    expected_results = {
        "Rd": 0.6181013468813382,
        "kappa": 0.10093835959160387,
        "Rf": 62.39013601556319,
        "gamma_0": 1.0678273429551705e-08,
        "fc": 1504.4416105821158,
        "D": 1.0090151317063,
        "err_fc": 13.075876724291339,
        "err_D": 0.0066021439072302835,
        "f_diode": 14675.638696737586,
        "alpha": 0.41651098052983593,
        "err_f_diode": 352.2917702189488,
        "err_alpha": 0.014231238753589254,
        "chi_squared_per_deg": 0.8659867914094764,
        "backing": 14.340689726784328,
    }

    for key, value in expected_params.items():
        np.testing.assert_allclose(fit.params[key].value, value, err_msg=key)

    for key, value in expected_results.items():
        np.testing.assert_allclose(fit.results[key].value, value, err_msg=key)
コード例 #7
0
def model_and_data(reference_models, diode_alpha, fast_sensor):
    """Generate model and data for this particular test"""
    params = {"bead_diameter": 1.03, "temperature": 20, "viscosity": 1.002e-3}
    data, f_sample = reference_models.lorentzian_td(4000,
                                                    1.14632,
                                                    alpha=diode_alpha,
                                                    f_diode=14000,
                                                    num_samples=78125)
    passive_model = PassiveCalibrationModel(**params, fast_sensor=fast_sensor)
    sine = np.sin(32.0 * 2.0 * np.pi * np.arange(0, 1, 1.0 / f_sample))
    active_model = ActiveCalibrationModel(sine,
                                          sine,
                                          f_sample,
                                          driving_frequency_guess=32,
                                          **params,
                                          fast_sensor=fast_sensor)
    power_spectrum = calculate_power_spectrum(data,
                                              f_sample,
                                              num_points_per_block=5)
    return (passive_model, active_model), power_spectrum
コード例 #8
0
def test_fit_settings(reference_models):
    """This test tests whether the algorithm parameters ftol, max_function_evals and
    analytical_fit_range for lk.fit_power_spectrum() are applied as intended."""
    sample_rate = 78125
    corner_frequency, diffusion_volt = 4000, 1.14632
    bead_diameter, temperature, viscosity = 1.03, 20, 1.002e-3

    # alpha = 1.0 means no diode effect
    data, f_sample = reference_models.lorentzian_td(corner_frequency,
                                                    diffusion_volt,
                                                    alpha=1.0,
                                                    f_diode=14000,
                                                    num_samples=sample_rate)
    model = PassiveCalibrationModel(bead_diameter,
                                    temperature=temperature,
                                    viscosity=viscosity)
    power_spectrum = psc.calculate_power_spectrum(data,
                                                  f_sample,
                                                  fit_range=(0, 23000),
                                                  num_points_per_block=200)

    # Won't converge with so few maximum function evaluations
    with pytest.raises(
            RuntimeError,
            match="The maximum number of function evaluations is exceeded"):
        psc.fit_power_spectrum(power_spectrum=power_spectrum,
                               model=model,
                               max_function_evals=1)

    # Make the analytical fit fail
    with pytest.raises(
            RuntimeError,
            match=
            "An empty power spectrum was passed to fit_analytical_lorentzian"):
        psc.fit_power_spectrum(power_spectrum=power_spectrum,
                               model=model,
                               analytical_fit_range=(10, 100))
コード例 #9
0
def test_near_surface_consistency():
    """For small beads, the results (Rf, Rd, and kappa) with or without the hydrodynamically correct
    model should be the same near a surface.

    Since internally, the effect of the surface is handled very differently in all the calibration
    options (hydro on/off, active on/off), this provides an integration test that actually tests
    whether all the parts are functioning correctly.

    AC on,  Hydro off - Drag is measured and used directly.
    AC on,  Hydro on  - Drag is measured and used directly.
    AC off, Hydro off - Surface effect on drag coefficient is modelled with Faxen's law.
    AC off, Hydro on  - Surface effect on drag coefficient is in equation describing the spectrum.
    """
    bead_diameter = 0.5
    shared_pars = {
        "bead_diameter": bead_diameter,
        "viscosity": 1.1e-3,
        "temperature": 25,
        "rho_sample": 997.0,
        "rho_bead": 1040.0,
        "distance_to_surface": bead_diameter,
    }
    sim_pars = {
        "sample_rate": 78125,
        "stiffness": 0.1,
        "pos_response_um_volt": 0.618,
        "driving_sinusoid": (500, 31.95633),
        "diode": (0.6, 15000),
    }

    np.random.seed(1337)
    volts, stage = generate_active_calibration_test_data(
        10, hydrodynamically_correct=True, **sim_pars, **shared_pars)
    power_spectrum = calculate_power_spectrum(volts, sim_pars["sample_rate"])

    active_pars = {
        "force_voltage_data": volts,
        "driving_data": stage,
        "sample_rate": 78125,
        "driving_frequency_guess": 32,
    }

    def fit_spectrum(active, hydro):
        model = (ActiveCalibrationModel(
            **active_pars, **shared_pars, hydrodynamically_correct=hydro)
                 if active else PassiveCalibrationModel(
                     **shared_pars, hydrodynamically_correct=hydro))
        return fit_power_spectrum(power_spectrum, model)

    parameters_of_interest = {
        "kappa": sim_pars["stiffness"],
        "Rd": sim_pars["pos_response_um_volt"],
        "Rf": sim_pars["stiffness"] * sim_pars["pos_response_um_volt"] * 1e3,
    }

    for active_calibration in (True, False):
        for hydrodynamic_model in (True, False):
            fit = fit_spectrum(active_calibration, hydrodynamic_model)

            # Note that the corner frequency of the hydrodynamic model is specified in bulk, while
            # the regular model has its corner frequency specified at the current height.
            fc_bulk = (fit["fc"].value
                       if hydrodynamic_model else fit["fc"].value *
                       faxen_factor(shared_pars["distance_to_surface"] *
                                    1e-6, bead_diameter * 1e-6 / 2))
            np.testing.assert_allclose(fc_bulk, 3070.33, rtol=2e-2)
            for param, ref_value in parameters_of_interest.items():
                np.testing.assert_allclose(fit[param].value,
                                           ref_value,
                                           rtol=2e-2)
コード例 #10
0
def test_good_fit_integration_test(
    reference_models,
    corner_frequency,
    diffusion_constant,
    alpha,
    f_diode,
    num_samples,
    viscosity,
    bead_diameter,
    temperature,
    err_fc,
    err_d,
    err_f_diode,
    err_alpha,
):
    data, f_sample = reference_models.lorentzian_td(corner_frequency,
                                                    diffusion_constant, alpha,
                                                    f_diode, num_samples)
    model = PassiveCalibrationModel(bead_diameter,
                                    temperature=temperature,
                                    viscosity=viscosity)
    power_spectrum = psc.calculate_power_spectrum(data,
                                                  f_sample,
                                                  fit_range=(0, 15000),
                                                  num_points_per_block=20)
    ps_calibration = psc.fit_power_spectrum(power_spectrum=power_spectrum,
                                            model=model,
                                            bias_correction=False)

    np.testing.assert_allclose(ps_calibration["fc"].value,
                               corner_frequency,
                               rtol=1e-4)
    np.testing.assert_allclose(ps_calibration["D"].value,
                               diffusion_constant,
                               rtol=1e-4,
                               atol=0)
    np.testing.assert_allclose(ps_calibration["alpha"].value, alpha, rtol=1e-4)
    np.testing.assert_allclose(ps_calibration["f_diode"].value,
                               f_diode,
                               rtol=1e-4)

    gamma = sphere_friction_coefficient(viscosity, bead_diameter * 1e-6)
    kappa_true = 2.0 * np.pi * gamma * corner_frequency * 1e3
    rd_true = (np.sqrt(sp.constants.k * sp.constants.convert_temperature(
        temperature, "C", "K") / gamma / diffusion_constant) * 1e6)
    np.testing.assert_allclose(ps_calibration["kappa"].value,
                               kappa_true,
                               rtol=1e-4)
    np.testing.assert_allclose(ps_calibration["Rd"].value, rd_true, rtol=1e-4)
    np.testing.assert_allclose(ps_calibration["Rf"].value,
                               rd_true * kappa_true * 1e3,
                               rtol=1e-4)
    np.testing.assert_allclose(ps_calibration["chi_squared_per_deg"].value,
                               0,
                               atol=1e-9)  # Noise free

    np.testing.assert_allclose(ps_calibration["err_fc"].value, err_fc)
    np.testing.assert_allclose(ps_calibration["err_D"].value,
                               err_d,
                               rtol=1e-4,
                               atol=0)
    np.testing.assert_allclose(ps_calibration["err_f_diode"].value,
                               err_f_diode)
    np.testing.assert_allclose(ps_calibration["err_alpha"].value,
                               err_alpha,
                               rtol=1e-6)
コード例 #11
0
ファイル: test_axial.py プロジェクト: lumicks/pylake
def test_axial_calibration(reference_models, hydro):
    """We currently have no way to perform active axial calibration.

    However, we can make the passive calibration slightly more accurate by transferring the active
    calibration result from the lateral calibration over to it.

    This test performs an integration test which tests whether carrying over the drag coefficient
    from a lateral calibration to an axial one produces the correct results. To test this, we
    deliberately mis-specify our viscosity in the models (since active calibration is more
    robust against mis-specification of viscosity and bead radius).

    Approaching the surface leads to an increase in the drag coefficient:

        gamma(h) = gamma_bulk * correction_factor(h)

    For lateral this factor is given by a different function than axial. The experimental drag
    coefficient returned by the calibration procedure (gamma_ex) is the back-corrected bulk drag
    coefficient gamma_bulk. This can be directly transferred to the axial direction which then
    applies the correct forward correction factor for axial. This test verifies that this behaviour
    stays preserved (as we rely on it)."""
    np.random.seed(17256246)
    viscosity = 0.0011
    shared_pars = {"bead_diameter": 0.5, "temperature": 20}
    sim_params = {
        "sample_rate": 78125,
        "duration": 10,
        "stiffness": 0.05,
        "pos_response_um_volt": 0.6,
        "driving_sinusoid": (500, 31.9563),
        "diode": (1.0, 10000),
        **shared_pars,
    }
    # For reference, these parameters lead to a true bulk gamma of:
    gamma_ref = 5.183627878423158e-09

    # Distance to the surface
    dist = 1.5 * shared_pars["bead_diameter"] / 2

    def height_simulation(height_factor):
        """We hack in height dependence using the viscosity. Since the calibration procedure covers
        the same bead, we can do this (gamma_bulk is linearly proportional to the viscosity and
        bead size).

        height_factor : callable
            Provides the height dependent drag model.
        """
        return generate_active_calibration_test_data(
            **sim_params,
            viscosity=viscosity * height_factor(
                dist * 1e-6, shared_pars["bead_diameter"] * 1e-6 / 2),
        )

    volts_lateral, stage = height_simulation(faxen_factor)
    lateral_model = ActiveCalibrationModel(
        stage,
        volts_lateral,
        **shared_pars,
        sample_rate=sim_params["sample_rate"],
        viscosity=viscosity *
        2,  # We mis-specify viscosity since we measure experimental drag
        driving_frequency_guess=32,
        hydrodynamically_correct=hydro,
        distance_to_surface=dist,
    )
    ps_lateral = calculate_power_spectrum(volts_lateral, sample_rate=78125)
    lateral_fit = fit_power_spectrum(ps_lateral, lateral_model)
    np.testing.assert_allclose(
        lateral_fit[lateral_model._measured_drag_fieldname].value,
        gamma_ref,
        rtol=5e-2)

    # Axial calibration
    axial_model = PassiveCalibrationModel(
        **shared_pars,
        viscosity=viscosity *
        2,  # We deliberately mis-specify the viscosity to test the transfer
        hydrodynamically_correct=False,
        distance_to_surface=dist,
        axial=True,
    )

    # Transfer the result to axial calibration
    axial_model._set_drag(
        lateral_fit[lateral_model._measured_drag_fieldname].value)

    volts_axial, stage = height_simulation(brenner_axial)
    ps_axial = calculate_power_spectrum(volts_axial, sample_rate=78125)
    axial_fit = fit_power_spectrum(ps_axial, axial_model)
    np.testing.assert_allclose(axial_fit["gamma_ex_lateral"].value,
                               gamma_ref,
                               rtol=5e-2)
    np.testing.assert_allclose(axial_fit["kappa"].value,
                               sim_params["stiffness"],
                               rtol=5e-2)
    assert (axial_fit["gamma_ex_lateral"].description ==
            "Bulk drag coefficient from lateral calibration")
コード例 #12
0
ファイル: convenience.py プロジェクト: lumicks/pylake
def calibrate_force(
    force_voltage_data,
    bead_diameter,
    temperature,
    *,
    viscosity=None,
    active_calibration=False,
    driving_data=np.asarray([]),
    driving_frequency_guess=37,
    axial=False,
    hydrodynamically_correct=False,
    rho_sample=None,
    rho_bead=1060.0,
    distance_to_surface=None,
    fast_sensor=False,
    sample_rate=78125,
    num_points_per_block=2000,
    fit_range=(1e2, 23e3),
    excluded_ranges=[],
    fixed_diode=None,
    drag=None,
):
    """Determine force calibration factors.

    The power spectrum calibration algorithm implemented here is based on [1]_, [2]_, [3]_, [4]_,
    [5]_, [6]_.

    References
    ----------
    .. [1] Berg-Sørensen, K. & Flyvbjerg, H. Power spectrum analysis for optical tweezers. Rev. Sci.
           Instrum. 75, 594 (2004).
    .. [2] Tolić-Nørrelykke, I. M., Berg-Sørensen, K. & Flyvbjerg, H. MatLab program for precision
           calibration of optical tweezers. Comput. Phys. Commun. 159, 225–240 (2004).
    .. [3] Hansen, P. M., Tolic-Nørrelykke, I. M., Flyvbjerg, H. & Berg-Sørensen, K.
           tweezercalib 2.1: Faster version of MatLab package for precise calibration of optical
           tweezers. Comput. Phys. Commun. 175, 572–573 (2006).
    .. [4] Berg-Sørensen, K., Peterman, E. J. G., Weber, T., Schmidt, C. F. & Flyvbjerg, H. Power
           spectrum analysis for optical tweezers. II: Laser wavelength dependence of parasitic
           filtering, and how to achieve high bandwidth. Rev. Sci. Instrum. 77, 063106 (2006).
    .. [5] Tolić-Nørrelykke, S. F, and Flyvbjerg, H, "Power spectrum analysis with least-squares
           fitting: amplitude bias and its elimination, with application to optical tweezers and
           atomic force microscope cantilevers." Review of Scientific Instruments 81.7 (2010)
    .. [6] Tolić-Nørrelykke S. F, Schäffer E, Howard J, Pavone F. S, Jülicher F and Flyvbjerg, H.
           Calibration of optical tweezers with positional detection in the back focal plane,
           Review of scientific instruments 77, 103101 (2006).

    Parameters
    ----------
    force_voltage_data : array_like
        Uncalibrated force data in volts.
    bead_diameter : float
        Bead diameter [um].
    temperature : float
        Liquid temperature [Celsius].
    viscosity : float, optional
        Liquid viscosity [Pa*s].
        When omitted, the temperature will be used to look up the viscosity of water at that
        particular temperature.
    active_calibration : bool, optional
        Active calibration, when set to True, driving_data must also be provided.
    driving_data : array_like, optional
        Array of driving data.
    driving_frequency_guess : float, optional
         Guess of the driving frequency.
    axial : bool, optional
        Is this an axial calibration? Only valid for a passive calibration.
    hydrodynamically_correct : bool, optional
        Enable hydrodynamically correct model.
    rho_sample : float, optional
        Density of the sample [kg/m**3]. Only used when using hydrodynamically correct model.
    rho_bead : float, optional
        Density of the bead [kg/m**3]. Only used when using hydrodynamically correct model.
    distance_to_surface : float, optional
        Distance from bead center to the surface [um]
        When specifying `None`, the model will use an approximation which is only suitable for
        measurements performed deep in bulk.
    fast_sensor : bool, optional
         Fast sensor? Fast sensors do not have the diode effect included in the model.
    sample_rate : float, optional
         Sample rate at which the signals were acquired.
    fit_range : tuple of float, optional
        Tuple of two floats (f_min, f_max), indicating the frequency range to use for the full model
        fit. [Hz]
    num_points_per_block : int, optional
        The spectrum is first block averaged by this number of points per block.
        Default: 2000.
    excluded_ranges : list of tuple of float, optional
        List of ranges to exclude specified as a list of (frequency_min, frequency_max).
    drag : float, optional
        Overrides the drag coefficient to this particular value.
    fixed_diode : float, optional
        Fix diode frequency to a particular frequency.
    """
    if active_calibration:
        if axial:
            raise ValueError("Active calibration is not supported for axial force.")
        if drag:
            raise ValueError("Drag coefficient cannot be carried over to active calibration.")

    if fixed_diode and fast_sensor:
        raise ValueError("When using fast_sensor=True, there is no diode model to fix.")

    if active_calibration and driving_data.size == 0:
        raise ValueError("Active calibration requires the driving_data to be defined.")

    model_params = {
        "bead_diameter": bead_diameter,
        "viscosity": viscosity,
        "temperature": temperature,
        "fast_sensor": fast_sensor,
        "distance_to_surface": distance_to_surface,
        "hydrodynamically_correct": hydrodynamically_correct,
        "rho_sample": rho_sample,
        "rho_bead": rho_bead,
    }

    model = (
        ActiveCalibrationModel(
            driving_data,
            force_voltage_data,
            sample_rate,
            driving_frequency_guess=driving_frequency_guess,
            **model_params,
        )
        if active_calibration
        else PassiveCalibrationModel(**model_params, axial=axial)
    )

    if drag:
        model._set_drag(drag)

    if fixed_diode:
        model._filter = FixedDiodeModel(fixed_diode)

    ps = calculate_power_spectrum(
        force_voltage_data,
        sample_rate,
        fit_range,
        num_points_per_block,
        excluded_ranges=excluded_ranges,
    )

    return fit_power_spectrum(ps, model)
コード例 #13
0
ファイル: test_simulations.py プロジェクト: lumicks/pylake
 def power(data):
     return calculate_power_spectrum(data,
                                     params["sample_rate"],
                                     fit_range=(1, 2e4)).power
コード例 #14
0
def test_integration_active_calibration(
    stiffness,
    viscosity,
    temperature,
    pos_response_um_volt,
    driving_sinusoid,
    diode,
    driving_frequency_guess,
    power_density,
    response_power,
):
    """Functional end to end test for active calibration"""
    sample_rate, bead_diameter = 78125, 1.03
    np.random.seed(0)
    force_voltage_data, driving_data = generate_active_calibration_test_data(
        duration=20,
        sample_rate=sample_rate,
        bead_diameter=bead_diameter,
        stiffness=stiffness,
        viscosity=viscosity,
        temperature=temperature,
        pos_response_um_volt=pos_response_um_volt,
        driving_sinusoid=driving_sinusoid,
        diode=diode,
    )

    model = ActiveCalibrationModel(
        driving_data,
        force_voltage_data,
        sample_rate,
        bead_diameter,
        driving_frequency_guess,
        viscosity,
        temperature,
    )

    # Validate estimation of the driving input
    np.testing.assert_allclose(model.driving_amplitude,
                               driving_sinusoid[0] * 1e-9,
                               rtol=1e-5)
    np.testing.assert_allclose(model.driving_frequency,
                               driving_sinusoid[1],
                               rtol=1e-5)

    np.testing.assert_allclose(model._response_power_density,
                               power_density,
                               rtol=1e-5)
    num_points_per_window = int(
        np.round(sample_rate * model.num_windows / model.driving_frequency))
    freq_axis = np.fft.rfftfreq(num_points_per_window, 1.0 / sample_rate)
    np.testing.assert_allclose(model._frequency_bin_width,
                               freq_axis[1] - freq_axis[0])

    power_spectrum = calculate_power_spectrum(force_voltage_data, sample_rate)
    fit = fit_power_spectrum(power_spectrum, model)

    np.testing.assert_allclose(fit["kappa"].value, stiffness, rtol=5e-2)
    np.testing.assert_allclose(fit["alpha"].value, diode[0], rtol=5e-2)
    np.testing.assert_allclose(fit["f_diode"].value, diode[1], rtol=5e-2)
    np.testing.assert_allclose(fit["Rd"].value,
                               pos_response_um_volt,
                               rtol=5e-2)

    response_calc = fit["Rd"].value * fit["kappa"].value * 1e3
    np.testing.assert_allclose(fit["Rf"].value, response_calc, rtol=1e-9)

    kt = scipy.constants.k * scipy.constants.convert_temperature(
        temperature, "C", "K")
    drag_coeff_calc = kt / (fit["D"].value * fit["Rd"].value**2)
    np.testing.assert_allclose(
        fit["gamma_0"].value,
        sphere_friction_coefficient(viscosity, bead_diameter * 1e-6),
        rtol=1e-9,
    )
    np.testing.assert_allclose(fit["gamma_ex"].value,
                               drag_coeff_calc * 1e12,
                               rtol=1e-9)

    np.testing.assert_allclose(fit["Bead diameter"].value, bead_diameter)
    np.testing.assert_allclose(fit["Driving frequency (guess)"].value,
                               driving_frequency_guess)
    np.testing.assert_allclose(fit["Sample rate"].value, sample_rate)
    np.testing.assert_allclose(fit["Viscosity"].value, viscosity)
    np.testing.assert_allclose(fit["num_windows"].value, 5)

    np.testing.assert_allclose(fit["driving_amplitude"].value,
                               driving_sinusoid[0] * 1e-3,
                               rtol=1e-5)
    np.testing.assert_allclose(fit["driving_frequency"].value,
                               driving_sinusoid[1],
                               rtol=1e-5)
    np.testing.assert_allclose(fit["driving_power"].value,
                               response_power,
                               rtol=1e-6)
コード例 #15
0
def test_faxen_correction_active():
    """Active calibration should barely be affected by surface corrections for the drag coefficient.
    However, the interpretation of gamma_ex, which may be carried over to the other calibration
    *is* important, so this should be covered by a specific test."""
    shared_pars = {
        "bead_diameter": 1.03,
        "viscosity": 1.1e-3,
        "temperature": 25,
        "rho_sample": 997.0,
        "rho_bead": 1040.0,
        "distance_to_surface": 1.03 / 2 + 500e-3,
    }
    sim_pars = {
        "sample_rate": 78125,
        "stiffness": 0.1,
        "pos_response_um_volt": 0.618,
        "driving_sinusoid": (500, 31.95633),
        "diode": (0.4, 15000),
    }

    np.random.seed(10071985)
    volts, stage = generate_active_calibration_test_data(
        10, hydrodynamically_correct=True, **sim_pars, **shared_pars)
    power_spectrum = calculate_power_spectrum(volts, sim_pars["sample_rate"])

    active_pars = {
        "force_voltage_data": volts,
        "driving_data": stage,
        "sample_rate": 78125,
        "driving_frequency_guess": 32,
        "hydrodynamically_correct": False,
    }

    model = ActiveCalibrationModel(**active_pars, **shared_pars)
    fit = fit_power_spectrum(power_spectrum, model, bias_correction=False)

    # Fitting with *no* hydrodynamically correct model, but *with* Faxen's law
    np.testing.assert_allclose(fit.results["Rd"].value, 0.5979577465734786)
    np.testing.assert_allclose(fit.results["kappa"].value, 0.10852140970454485)
    np.testing.assert_allclose(fit.results["Rf"].value, 64.89121760190687)
    # gamma_0 and gamma_ex should be the same, since gamma_ex is corrected to be "in bulk".
    np.testing.assert_allclose(fit.results["gamma_0"].value,
                               1.0678273429551705e-08)
    np.testing.assert_allclose(fit.results["gamma_ex"].value,
                               1.1271667835127709e-08)

    # Disabling Faxen's correction on the drag makes the estimates *much* worse
    shared_pars["distance_to_surface"] = None
    model = ActiveCalibrationModel(**active_pars, **shared_pars)
    fit = fit_power_spectrum(power_spectrum, model, bias_correction=False)

    np.testing.assert_allclose(fit.results["Rd"].value, 0.5979577465734786)
    np.testing.assert_allclose(fit.results["kappa"].value, 0.10852140970454485)
    np.testing.assert_allclose(fit.results["Rf"].value, 64.89121760190687)
    # Not affected since this is gamma bulk
    np.testing.assert_allclose(fit.results["gamma_0"].value,
                               1.0678273429551705e-08)
    # The drag is now much different, since we're not using Faxen's law to back-correct the drag
    # to its actual bulk value.
    np.testing.assert_allclose(
        fit.results[model._measured_drag_fieldname].value,
        1.571688034506783e-08)