Skip to content

Commit

Permalink
Added hyperparameter optimization and updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
dineshpinto committed Aug 4, 2023
1 parent c303813 commit db5af34
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 34 deletions.
67 changes: 58 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pip install --upgrade qudi-hira-analysis

If you are publishing scientific results, you can cite this work as: https://doi.org/10.5281/zenodo.7604670

## Examples
## Usage

First set up the `DataHandler` object (henceforth referred to as `dh`) with the correct paths to the data and figure
folders.
Expand Down Expand Up @@ -89,7 +89,56 @@ The `load_measurements` function returns a dictionary containing the measurement
- The dictionary values are `MeasurementDataclass` objects whose schema is shown
visually [here](#measurement-dataclass-schema).

### Example 0: NV-PL measurements
### Example 0: 2D NV-ODMR measurements

```python
odmr_measurements = dh.load_measurements(measurement_str="2d_odmr_map")
odmr_measurements = dict(sorted(odmr_measurements.items()))

# Optional: Try and optimize the hyperparameters for the ODMR fitting
highest_min_r2, optimal_parameters = dh.optimize_hyperparameters(odmr_measurements, num_samples=100, num_params=3)

# Perform parallel (=num CPU cores) ODMR fitting
odmr_measurements = dh.raster_odmr_fitting(
odmr_measurements,
r2_thresh=0.95,
thresh_frac=0.5,
sigma_thresh_frac=0.1,
min_thresh=0.01,
)

# Calculate residuals and 2D ODMR map
pixels = int(np.sqrt(len(odmr_measurements)))
image = np.zeros((pixels, pixels))
residuals = np.zeros(len(odmr_measurements))

for idx, odmr in enumerate(odmr_measurements.values()):
row, col = odmr.xy_position
residuals[idx] = odmr.fit_model.rsquared

if len(odmr.fit_model.params) == 6:
# Single Lorentzian, no splitting
image[row, col] = 0
else:
if odmr.fit_model.rsquared < 0.95:
# Bad fit, set to NaN
image[row, col] = np.nan
else:
# Calculate splitting
splitting = np.abs(odmr.fit_model.best_values["l1_center"] - odmr.fit_model.best_values["l0_center"])
image[row, col] = splitting

fig, (ax, ax1) = plt.subplots(ncols=2)
# Plot residuals
sns.lineplot(residuals, ax=ax)
# Plot 2D ODMR map
sns.heatmap(image, cbar_kws={"label": r"$\Delta E$ (MHz)"}, ax=ax1)

# Save the figure to the figure folder specified earlier
dh.save_figures(filepath="2d_odmr_map_with_residuals", fig=fig, only_jpg=True)
```

### Example 1: NV-PL measurements

```python
pixel_scanner_measurements = dh.load_measurements(measurement_str="PixelScanner")
Expand All @@ -114,7 +163,7 @@ cbar.set_label("NV-PL (kcps)")
dh.save_figures(filepath="nv_pl_scan", fig=fig, only_jpg=True)
```

### Example 1: Nanonis AFM measurements
### Example 2: Nanonis AFM measurements

```python
afm_measurements = dh.load_measurements(measurement_str="Scan", extension=".sxm", qudi=False)
Expand Down Expand Up @@ -142,7 +191,7 @@ cbar.set_label("Height (nm)")
dh.save_figures(filepath="afm_topo", fig=fig, only_jpg=True)
```

### Example 2: Autocorrelation measurements (Antibunching fit)
### Example 3: Autocorrelation measurements (Antibunching fit)

```python
autocorrelation_measurements = dh.load_measurements(measurement_str="Autocorrelation")
Expand All @@ -163,7 +212,7 @@ for autocorrelation in autocorrelation_measurements.values():
dh.save_figures(filepath="autocorrelation_variation", fig=fig)
```

### Example 3: ODMR measurements (double Lorentzian fit)
### Example 4: ODMR measurements (double Lorentzian fit)

```python
odmr_measurements = dh.load_measurements(measurement_str="ODMR", pulsed=True)
Expand All @@ -179,7 +228,7 @@ for odmr in odmr_measurements.values():
dh.save_figures(filepath="odmr_variation", fig=fig)
```

### Example 4: Rabi measurements (sine exponential decay fit)
### Example 5: Rabi measurements (sine exponential decay fit)

```python
rabi_measurements = dh.load_measurements(measurement_str="Rabi", pulsed=True)
Expand All @@ -195,7 +244,7 @@ for rabi in rabi_measurements.values():
dh.save_figures(filepath="rabi_variation", fig=fig)
```

### Example 5: Temperature data
### Example 6: Temperature data

```python
temperature_measurements = dh.load_measurements(measurement_str="Temperature", qudi=False)
Expand All @@ -207,7 +256,7 @@ sns.lineplot(data=temperature, x="Time", y="Temperature", ax=ax)
dh.save_figures(filepath="temperature_monitoring", fig=fig)
```

### Example 6: PYS data (pi3diamond compatibility)
### Example 7: PYS data (pi3diamond compatibility)

```python
pys_measurements = dh.load_measurements(measurement_str="ndmin", extension=".pys", qudi=False)
Expand All @@ -218,7 +267,7 @@ sns.lineplot(x=pys["time_bins"], y=pys["counts"], ax=ax)
dh.save_figures(filepath="pys_measurement", fig=fig)
```

### Example 7: Bruker MFM data
### Example 8: Bruker MFM data

```python
bruker_measurements = dh.load_measurements(measurement_str="", extension=".001", qudi=False)
Expand Down
40 changes: 20 additions & 20 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 59 additions & 5 deletions qudi_hira_analysis/analysis_logic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import logging
import random
import re
from itertools import product
from typing import Tuple, TYPE_CHECKING

import numpy as np
Expand Down Expand Up @@ -287,13 +289,65 @@ def analyse_mean_norm(

return signal_data, error_data

def raster_odmr_fitting(
def optimize_hyperparameters(
self,
measurements: dict[str, MeasurementDataclass],
num_samples: int = 10,
num_params: int = 3,
) -> Tuple[float, Tuple[float, float, float]]:
"""
This method optimizes the hyperparameters of the ODMR analysis.
It does so by randomly sampling a subset of the measurements and
then optimizing the hyperparameters for them.
Args:
measurements: A dictionary of measurements to optimize the hyperparameters for.
num_params: The number of parameters to optimize.
num_samples: The number of measurements to sample.
Returns:
The optimal hyperparameters.
"""
r2_threshs = np.around(np.linspace(start=0.9, stop=0.99, num=num_params), decimals=2)
thresh_fracs = np.around(np.linspace(start=0.5, stop=0.9, num=num_params), decimals=1)
sigma_thresh_fracs = np.around(np.linspace(start=0.1, stop=0.2, num=num_params), decimals=1)

odmr_sample = {}
for k, v in random.sample(sorted(measurements.items()), k=num_samples):
odmr_sample[k] = v

highest_min_r2 = 0
optimal_params = (0, 0, 0)

for idx, (r2_thresh, thresh_frac, sigma_thresh_frac) in enumerate(
product(r2_threshs, thresh_fracs, sigma_thresh_fracs)):
odmr_sample = self.raster_odmr_fitting(
odmr_sample,
r2_thresh=r2_thresh,
thresh_frac=thresh_frac,
sigma_thresh_frac=sigma_thresh_frac,
min_thresh=0.01,
progress_bar=False
)

r2s = np.zeros(len(odmr_sample))
for _idx, odmr in enumerate(odmr_sample.values()):
r2s[_idx] = odmr.fit_model.rsquared
min_r2 = np.min(r2s)

if highest_min_r2 < min_r2:
highest_min_r2 = min_r2
optimal_params = (r2_thresh, thresh_frac, sigma_thresh_frac)

return highest_min_r2, optimal_params

@staticmethod
def raster_odmr_fitting(
odmr_measurements: dict[str, MeasurementDataclass],
r2_thresh: float = 0.95,
thresh_frac: float = 0.3,
min_thresh: float = 0.25,
sigma_thresh_frac: float = 0.3,
thresh_frac: float = 0.5,
sigma_thresh_frac: float = 0.15,
min_thresh: float = 0.01,
extract_pixel_from_filename: bool = True,
progress_bar: bool = True
) -> dict[str, MeasurementDataclass]:
Expand All @@ -307,7 +361,7 @@ def raster_odmr_fitting(
min_thresh:
sigma_thresh_frac:
extract_pixel_from_filename: Extract `(row, col)` (in this format) from filename
progress_bar: Show progress bar
Returns:
List of ODMR data with fit, fit model and pixels in MeasurementDataclass
"""
Expand Down

0 comments on commit db5af34

Please sign in to comment.