diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2408c6d451f0ae7feb64eb0a8844de4f4e7845c7..52bfa5c87153d7c02d47247af0824e4c1c605676 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # CHANGELOG
 
+## Unreleased
+
+_Restructured package for easier maintenance and extension._
+
+
 ## 0.1.0 - 2024-04-17
 
 _Initial release_
diff --git a/README.md b/README.md
index d77bb1e117b157840f4188072cd200af4e36d20c..ae66777491e2b87bd7e2fc57d859b8d37238adf2 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,41 @@
-# SNLO Helper
+# SNLO-Helper
 
-Utilises SNLO software for simulation of nonlinear processes in crystals.
+SNLO-Helper helps to use [SNLO](https://as-photonics.com/products/snlo/) software for simulation of nonlinear optical processes in crystals.
 
-An autoclicker clicks different buttons/fills fields in order to automate SNLO simulations.
+An autoclicker clicks different buttons and fills fields in order to automate SNLO simulations.
+Afterwards, it can retrieve the results and return them as a dictionary.
 
-Beware, that the script does its work with your mouse and keyboard, so you should not interact with the computer in the meanwhile.
+Note that the script does use your mouse and keyboard, so you should not interact with the computer at the same time.
 The autoclicker can be interrupted by moving the mouse into the top right corner of the screen.
 
 
 ## Installation
 
-Install it in this folder via `pip install -e .` or via `pip install git+https://git.rwth-aachen.de/nloqo/snlo-helper.git` to download it in the background and install it.
+Install it executing `pip install -e .` in this folder or via `pip install git+https://git.rwth-aachen.de/nloqo/snlo-helper.git` to download it in the background and install it.
 
 
 ## Usage
 
-- You can execute `snlohelper.py` in a console, which imports all the methods and sets the screen resolution factor.
-- Alternatively, you can import methods from `snlohelper`. In this case you have to set the screen resolution with `set_screenfactors()` yourself.
+Import `snlohelper.snlo` as a starting point.
+If your screen resolution differs from HD, you have to set screenfactors with `utils.set_screenfactors`.
+That will rescale all positions to your current screen resolution.
 
-Use the methods as convenient.
+Here is a small snippet how to do a 2D mix of long pulses:
+```
+from snlohelper import snlo
+
+snlo.utils.set_screenfactors()
+
+sim = snlo.TwoDMixLP()  # create a class for 2D mix
+sim.open()  # click the corresponding button to open 2D mix
+sim.configure({"Wavelengths (nm)": [1064.5, None, None]})  # configure it
+result = sim.run_and_read()  # run it
+print(result)
+```
+
+For more examples see the `examples` folder.
+
+
+## Contribution
+
+You are welcome to contribute to this library. Just open an issue for suggestions or bug reports and open a pull request for code contributions.
diff --git a/examples/OPA-simulation.py b/examples/OPA-simulation.py
deleted file mode 100644
index 6ff02c750c744d7d0ee0157398464f3cdc99f72c..0000000000000000000000000000000000000000
--- a/examples/OPA-simulation.py
+++ /dev/null
@@ -1,907 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
-created on 25.05.2022 by Jan Frederic Kinder
-"""
-
-import pickle
-import pyautogui as gui
-import matplotlib.pyplot as plt
-import analysis.modules_JFK as m  # module from NLOQO/EG-Labor/DataAnalysis, check PYTHONPATH in Spyder
-import numpy as np
-from socket import gethostname
-from datetime import datetime  # get current time
-
-import snlohelper as snlo
-
-# imports all of snlohelper, risk of namespace-confusion...
-from snlohelper import *
-
-"""define some global filepath variables based upon the computer name:"""
-
-
-def get_results_path():
-    pc_name = gethostname()
-    if pc_name == "WhiteShard":
-        return "M:/"
-    elif pc_name == "POPS":
-        return (
-            "C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/simulation/"
-        )
-    elif pc_name == "Myres":
-        return "D:/"
-    else:
-        print("data location not specified, use generic path: M:/")
-        return "M:/"
-
-
-def get_plots_path():
-    pc_name = gethostname()
-    if pc_name == "WhiteShard":
-        return "M:/"
-    elif pc_name == "POPS":
-        return (
-            f"C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/Ausgabe/"
-        )
-    elif pc_name == "Myres":
-        return "D:/"
-    else:
-        print("data location not specified, use generic path: M:/")
-        return "M:/"
-
-
-results_path = (
-    get_results_path()
-)  #'C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/simulation/'
-plots_path = (
-    get_plots_path()
-)  # f'C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/Ausgabe/'
-
-
-# %% tests
-
-# %%% test for display of pulse durations:
-id_beam = snlo.import_snlo_file("ID_BEAM.dat")
-sig_beam = snlo.import_snlo_file("SIG_BEAM.dat")
-pmp_beam = snlo.import_snlo_file("PMP_BEAM.dat")
-
-# fit idler duration
-x = 1e9 * id_beam.T[0]  # ns
-y = id_beam.T[1] / max(id_beam.T[1])
-idl = m.gaussian_fit(np.array([x, y]).T, False)[0]
-fwhm_ns_idl = abs(2 * np.sqrt(2 * np.log(2)) * idl[2])
-x_lists = [x.copy()]
-y_lists = [y.copy()]
-
-# fit signal duration
-x = 1e9 * sig_beam.T[0]  # ns
-y = sig_beam.T[1] / max(sig_beam.T[1])
-sig = m.gaussian_fit(np.array([x, y]).T, False)[0]
-fwhm_ns_sig = abs(2 * np.sqrt(2 * np.log(2)) * sig[2])
-x_lists.append(x)
-y_lists.append(y)
-
-# fit pump duration
-x = 1e9 * pmp_beam.T[0]  # ns
-y = pmp_beam.T[1] / max(pmp_beam.T[1])
-pmp = m.gaussian_fit(np.array([x, y]).T, False)[0]
-fwhm_ns_pmp = abs(2 * np.sqrt(2 * np.log(2)) * pmp[2])
-x_lists.append(x)
-y_lists.append(y)
-
-fig, ax = plt.subplots(1, 3)
-fits = idl, sig, pmp
-fwhms = fwhm_ns_idl, fwhm_ns_sig, fwhm_ns_pmp
-titles = "idler (Red1)", "signal (Red2)", "pump (Blue)"
-colors = "red", "green", "blue"
-x_fit = np.arange(min(x), max(x), 0.1)
-
-for i in range(len(ax)):
-    m.plot_options(fig, ax[i], xlabel="time (ns)", image_width=15, aspect_ratio=3)
-    ax[i].plot(x_lists[i], y_lists[i], ".", color=colors[i])
-    ax[i].set_title(titles[i], color=colors[i])
-    ax[i].plot(x_fit, m.gauss(x_fit, *fits[i]), "-", color=colors[i])
-    ax[i].text(0, 0, f"FWHM\n{fwhms[i]:.1f}ns", ha="center", color=colors[i])
-m.plot_options(
-    fig,
-    ax[0],
-    image_width=15,
-    aspect_ratio=3,
-    xlabel="time (ns)",
-    ylabel="normalized power",
-)
-plt.tight_layout()
-plt.show()
-
-# %%% screenshot with offset (top left corner)
-off = 300, 300
-image = gui.screenshot(region=(off[0], off[1], 500, 550))
-plt.figure()
-plt.imshow(image)
-plt.show()
-
-# %%% import SNLO files
-# time, power, phase, Mx^2, My^2, Rad Curv x, Rad Curv y, X-tilt, w_x^2, w_y^2
-with open("C:/SNLO/ID_BEAM.dat", "r") as f:
-    idler = f.read()
-with open("C:/SNLO/SIG_BEAM.dat", "r") as f:
-    signal = f.read()
-with open("C:/SNLO/PMP_BEAM.dat", "r") as f:
-    pump = f.read()
-print(idler)
-
-# %%% plot spectra
-with open("C:/SNLO/OPA2D_SP.dat", "r") as f:
-    input = f.readlines()
-spectr = []
-for i in input:
-    element = i.split()
-    # pectr.append(float(j) for j in element)
-    spectr.append(
-        [float(element[0]), float(element[1]), float(element[2]), float(element[3])]
-    )
-spectr = np.array(spectr.copy())
-
-x = spectr.T[0]  # in MHz
-y_idl = spectr.T[1]
-y_sig = spectr.T[2]
-y_pmp = spectr.T[3]
-
-idl = m.gaussian_fit(np.array([x, y_idl]).T, False)[0]
-sig = m.gaussian_fit(np.array([x, y_sig]).T, False)[0]
-pmp = m.gaussian_fit(np.array([x, y_pmp]).T, False)[0]
-
-fwhm_idl = 2 * np.sqrt(2 * np.log(2)) * idl[2]
-fwhm_sig = 2 * np.sqrt(2 * np.log(2)) * sig[2]
-fwhm_pmp = 2 * np.sqrt(2 * np.log(2)) * pmp[2]
-print(f"idl: {fwhm_idl:.3f}MHz => {2*np.log(2)/np.pi/fwhm_idl*1e3:.3f}ns")
-print(f"sig: {fwhm_sig:.3f}MHz => {2*np.log(2)/np.pi/fwhm_sig*1e3:.3f}ns")
-print(f"pmp: {fwhm_pmp:.3f}MHz => {2*np.log(2)/np.pi/fwhm_pmp*1e3:.3f}ns")
-
-fig, ax = plt.subplots()
-m.plot_options(fig, ax, xlabel="detuning (MHz)", ylabel="intensity (arb. u.)")
-ax.plot(x, y_idl, "r.", label="idler")
-ax.plot(x, y_sig, "g.", label="signal")
-ax.plot(x, y_pmp, "b.", label="pump")
-
-x_fit = np.arange(min(x), max(x), 1)
-ax.plot(x_fit, m.gauss(x_fit, *idl), "r")
-ax.plot(x_fit, m.gauss(x_fit, *sig), "g")
-ax.plot(x_fit, m.gauss(x_fit, *pmp), "b")
-
-ax.legend(loc="upper left")
-plt.tight_layout()
-plt.show()
-
-
-# %%% import simulation files
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA1_pump-dependence.pkl"
-result = pickle.load(open("simulation/" + file_name, "rb"))
-
-
-# %% parameters for OPA1
-# %%% calc radius of curvature OPA1
-wavelength_nm = 1064.16
-ref_index = snlo.n_lnb(wavelength_nm, axis="o")
-waist_size_mm = 0.4897
-focus_pos_mm = 25  # focus center of crystal
-result = snlo.focus(wavelength_nm, ref_index, waist_size_mm, focus_pos_mm)
-print(f"radius of curvature: {result[2]:.3f}mm")
-
-
-# %%% parameters 2D mix LP for OPA1
-idler_nm = 3545
-signal_nm = 1520.6
-pump_nm = 1064.16  # check!
-seedpower_w = 0.96
-pump_j = 0.000000499
-pumpduration_ns = 8
-fwhm_seed_mm = 0.65 / 2 * 1.18  # 1/e^2 radius to FWHM
-fwhm_pump_mm = 0.83 / 2 * 1.18  # 1/e^2 radius to FWHM
-
-values = [
-    [idler_nm, signal_nm, pump_nm],
-    [
-        snlo.n_lnb(idler_nm, axis="o"),
-        snlo.n_lnb(signal_nm, axis="o"),
-        snlo.n_lnb(pump_nm, axis="o"),
-    ],
-    [0, 0, 0],
-    [0.01, 0.02, 0.01],  # coating specs input
-    [0.01, 0.02, 0.01],  # coating specs output
-    [0, 0, 0],  # crystal loss 1/mm
-    [seedpower_w, 0.0000000000000001, pump_j],
-    [0, 0, pumpduration_ns],
-    [fwhm_seed_mm, fwhm_pump_mm, fwhm_pump_mm],
-    [1, 1, 1],  # supergaussian coefficient
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],  # walkoff
-    [0, 0, 0],  # offset
-    [766.97, 4258, 23270],  # radius of curvature
-    [50, 32, 32],  # integration points
-    [50, 0, 0],  # crystal length, grid, 0: auto: 2.4485x1.9588mm
-    [14.77],  # via QPM, LNB_M
-    [0],  # delta k
-    [1000],  # distance to image
-    [25],  # time steps
-]
-# todo: crystal reflectivities (data sheet)
-
-# %%% init 2D-mix-LP parameters
-snlo.open_function("2D-mix-LP")
-for index in range(len(values)):
-    if len(values[index]) == 1:
-        pos = list(snlo.dict_2dmixlp.values())[index][0]
-        snlo.set_value(pos, values[index][0])
-    else:
-        for element in range(len(values[index])):
-            snlo.set_value(
-                list(snlo.dict_2dmixlp.values())[index][element], values[index][element]
-            )
-
-
-# %% OPA1 pump energy dependence
-# start with 1e-5
-energies = np.arange(1e-6, 0.7e-3, 1e-5)
-print(energies)
-print(len(energies))
-
-
-# %%% run OPA1 pump energy dependence
-results = []
-for energy in energies:
-    set_value(snlo.dict_2dmixlp["Energy/power (J or W)"][2], energy)
-    snlo.moveto(*dict_2dmixlp["Accept"])
-    # snlo.moveto(780, 800)
-    gui.click()
-    dict = run_2dmixlp()
-    m.set_key(dict, "pump pulse energy (J)", energy)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict, dict2]))
-    snlo.moveto(*dict_2dmixlp["Change Inputs"])
-    # (600, 250)
-    gui.click()
-
-# %% save OPA1 pump dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA1_pump-dependence.pkl"
-pickle.dump(results, open(results_path + file_name, "wb"))
-
-
-# %%% plot results
-file_name = "2022-06-09_OPA1_pump-dependence.pkl"
-# results = snlo.merge_dict(pickle.load(open('simulation/' + file_name, 'rb')))
-results = snlo.merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-x = np.array(results["pump pulse energy (J)"]).T[0] * 1e6
-y = np.array(results["Output pulse energy (mJ)"]).T[0][0] * 1e3
-y2 = np.array(results["duration_idl_ns"]).T[0]
-y3 = np.array(results["duration_sig_ns"]).T[0]
-y4 = np.array(results["duration_pmp_ns"]).T[0]
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig,
-    ax1,
-    image_width=15,
-    aspect_ratio=2,
-    xlabel="pump pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y, "r-", label="idler pulse energy")
-ax1.legend(loc="upper left")
-ax12 = ax1.twinx()
-m.plot_options(fig, ax12, image_width=15, aspect_ratio=2)
-ax12.plot(
-    x, 1e-6 * y / (1e-9 * y2), "b--", label="single pass gain"
-)  # divided by 1W...
-ax12.set_ylabel("single pass gain", color="blue")
-ax12.legend(loc="lower right")
-
-m.plot_options(
-    fig,
-    ax2,
-    ticks=["auto", 2],
-    image_width=15,
-    aspect_ratio=2,
-    xlabel="pump pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse duration (ns)",
-)
-ax2.plot(x, y2, "r-", label="idler")
-ax2.plot(x, y3, "g--", label="sig")
-ax2.plot(x[y4 < 100], y4[y4 < 100], "b-", label="pump")
-ax2.set_ylim([0, max(y4[y4 < 100] + 0.5)])
-ax2.legend(loc="lower center", ncol=3, frameon=True, handlelength=1.5, mode="expand")
-plt.tight_layout()
-# m.save_plot('OPA1_pump_sim')
-plt.show()
-
-# %% conversion efficiency OPA1
-x = np.array(results["pump pulse energy (J)"]).T[0] * 1e6
-y = np.array(results["Output pulse energy (mJ)"]).T * 1e3
-eff = ((y[0] + y[1]) / x)[0]
-
-fig, ax1 = plt.subplots()
-m.plot_options(
-    fig,
-    ax1,
-    image_width=15,
-    xlabel="pump pulse energy ($\mathrm{\mu J}$)",
-    ylabel="pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y[0][0], "r-", label="idler pulse energy")
-ax1.plot(x, y[1][0], "g-", label="signal pulse energy")
-ax1.plot(x, y[2][0], "b-", label="residual pump pulse energy")
-ax1.legend(loc="upper left")
-
-ax12 = ax1.twinx()
-m.plot_options(fig, ax12, image_width=15, aspect_ratio=2)
-ax12.plot(x[eff < 1], eff[eff < 1], "k--", label="conversion efficiency")
-ax12.set_ylabel("conversion efficiency")
-
-ax12.legend(loc="lower right")
-
-plt.tight_layout()
-# m.save_plot('OPA1_pump_sim')
-plt.show()
-
-# %% OPA1 seed power dependence
-powers = np.arange(1e-6, 1.5, 20e-3)
-
-# %%% run seed dependence OPA1
-set_value(dict_2dmixlp["Energy/power (J or W)"][2], 0.5e-3)
-results = []
-for power in powers:
-    set_value(dict_2dmixlp["Energy/power (J or W)"][0], power)
-    gui.moveTo(*coords(780, 800))
-    gui.click()
-    dict1 = run_2dmixlp()
-    m.set_key(dict1, "seed power (W)", power)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict1, dict2]))
-    gui.moveTo(*coords(600, 250))
-    gui.click()
-# save OPA1 seed dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA1_seed-dependence.pkl"
-pickle.dump(results, open("simulation/" + file_name, "wb"))
-
-# %%% import and plot OPA1 pump dependence
-file_name = "2022-06-09_OPA1_seed-dependence.pkl"
-results = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-x = np.array(results["seed power (W)"]).T[0]
-y = np.array(results["Output pulse energy (mJ)"]).T[0][0] * 1e3
-y2 = np.array(results["duration_idl_ns"]).T[0]
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig,
-    ax1,
-    xlabel="(cw) idler power (W)",
-    ylabel="idler pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y, "-")
-
-m.plot_options(
-    fig, ax2, xlabel="(cw) idler power (W)", ylabel="idler pulse duration (ns)"
-)
-ax2.plot(x, y2, "-")
-plt.tight_layout()
-# m.save_plot('OPA1_seed_sim')
-plt.show()
-
-# %% OPA1: Paper values
-pump_energy = 0.5e-3
-seed_power = 1
-set_value(dict_2dmixlp["Energy/power (J or W)"][2], pump_energy)
-set_value(dict_2dmixlp["Energy/power (J or W)"][0], seed_power)
-moveto(dict_2dmixlp["Accept"])
-gui.click()
-dict1 = run_2dmixlp()
-m.set_key(dict1, "seed power (W)", seed_power)
-m.set_key(dict1, "pump pulse energy (J)", pump_energy)
-dict2 = get_spectr()
-results = merge_dict([dict1, dict2])
-# %% gather OPA1 values
-duration_idl_ns = results["duration_idl_ns"][0]
-fwhm_idl_MHz = results["fwhm_idl_MHz"][0]
-energy_idl_mJ = results["Output pulse energy (mJ)"][0][0]
-y = np.array(results["Output pulse energy (mJ)"]) * 1e3
-efficiency = (y[0, 0] + y[0, 1]) / y[0, 2]
-gain = (1e-3 * energy_idl_mJ) / (1e-9 * duration_idl_ns) / results["seed power (W)"][0]
-
-print(f"idler pulse energy: {energy_idl_mJ * 1e3:.3f}uJ")
-print(f"conversion efficiency: {efficiency*1e2:.3f}%")
-print(f"single pass gain: {gain:.1f}")
-print(f"idler duration:{duration_idl_ns:.3f}ns")
-print(f"idler bandwidth: {fwhm_idl_MHz:.3f}MHz")
-
-# %% parameters for OPA2
-# %%% calc radius of curvature OPA2 - pump
-idler_nm = 3545
-wavelength_nm = 1064.16
-ref_index = n(pm_theta(idler_nm, wavelength_nm), wavelength_nm, 25)
-waist_size_mm = 1.6 / 2 * 1.18  # 1/e^2 radius to FWHM
-focus_pos_mm = 15  # focus center of crystal
-result = focus(wavelength_nm, ref_index, waist_size_mm, focus_pos_mm)
-print(f"radius of curvature: {result[2]:.3f}mm")
-
-# %%% calc radius of curvature OPA2 - idler
-wavelength_nm = 3545
-ref_index = n_lnb(wavelength_nm, axis="o")
-waist_size_mm = 3.9 / 2 * 1.18  # 1/e^2 radius to FWHM
-focus_pos_mm = 15  # focus center of crystal
-result = focus(wavelength_nm, ref_index, waist_size_mm, focus_pos_mm)
-print(f"radius of curvature: {result[2]:.3f}mm")
-
-# %%% calc radius of curvature OPA2 - signal
-wavelength_nm = 1520.6
-ref_index = n_lnb(wavelength_nm, axis="o")
-waist_size_mm = 3.9 / 2 * 1.18  # 1/e^2 radius to FWHM
-focus_pos_mm = 15  # focus center of crystal
-result = focus(wavelength_nm, ref_index, waist_size_mm, focus_pos_mm)
-print(f"radius of curvature: {result[2]:.3f}mm")
-
-# %%% calc pulse duration of the idler seed
-file_name = "2022-05-23_OPA1_seed-dependence.pkl"
-results = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-i = 50
-print("pump pulse energy: 0.5mJ")
-power = results["seed power (W)"][i][0]
-print(f"idler seed power: {power}W")
-duration = results["duration_idl_ns"][i][0]
-print(f"idler duration: {duration:.3f}ns")
-energy = 1e3 * results["Output pulse energy (mJ)"][i][0][0]
-print(f"idler pulse energy: {energy:.3f}uJ")
-
-# %%% parameters 2D-mix-LP for OPA2
-idler_nm = 3545
-signal_nm = 1520.6
-pump_nm = 1064.16
-seedenergy_j = 44.5e-6  # was 46.9uJ (seed: 1W, pump: 0.5mJ)
-pump_j = 23e-3
-pulseduration_idler_ns = 6.217  # abgelesen aus seed dep
-pumpduration_ns = 8
-fwhm_seed_mm = 3.9 / 2 * 1.18  # 1/e^2 radius to FWHM
-fwhm_pump_mm = 1.6 / 2 * 1.18  # 1/e^2 radius to FWHM
-
-values = [
-    [idler_nm, signal_nm, pump_nm],
-    [
-        n_lnb(idler_nm, axis="o"),
-        n_lnb(signal_nm, axis="o"),
-        n(pm_theta(idler_nm, pump_nm), pump_nm, 25),
-    ],
-    [0, 0, 0],
-    [0.05, 0.005, 0.001],  # coating specs input, NC24, NC25
-    [0.05, 0.005, 0.001],  # coating specs output
-    [0, 0, 0],  # crystal loss 1/mm
-    [seedenergy_j, 1e-16, pump_j],
-    [pulseduration_idler_ns, 0, pumpduration_ns],
-    [fwhm_seed_mm, fwhm_seed_mm, fwhm_pump_mm],
-    [1, 1, 1],  # supergaussian coefficient
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 37.14],  # walkoff
-    [0, 0, 0],  # offset
-    [1631400, 9172700, 525250],  # todo: radius of curvature
-    [50, 32, 32],  # integration points
-    [30, 8, 8],  # crystal length, grid in mm
-    [-4.04],  # via QPM, LNB_M
-    [0],  # delta k
-    [1000],  # distance to image
-    [25],  # time steps
-]
-# todo: import power time traces and calculate Gaussian FWHM for idler after OPA1 and OPA2-1
-
-# %%% init 2D-mix-LP parameters for OPA2
-open_function("2D-mix-LP")
-for index in range(len(values)):
-    if len(values[index]) == 1:
-        pos = list(dict_2dmixlp.values())[index][0]
-        set_value(pos, values[index][0])
-    else:
-        for element in range(len(values[index])):
-            set_value(
-                list(dict_2dmixlp.values())[index][element], values[index][element]
-            )
-
-
-# %% OPA2 pump energy dependence
-# start with 1e-5
-energies = np.arange(1e-6, 25e-3, 250e-6)
-print(energies)
-print(len(energies))
-# set seed energy (was 20uJ)
-set_value(dict_2dmixlp["Energy/power (J or W)"][0], 4.45e-05)
-
-# %%% run OPA2 pump energy dependence
-results = []
-for energy in energies:
-    set_value(dict_2dmixlp["Energy/power (J or W)"][2], energy)
-    gui.moveTo(*coords(780, 800))
-    gui.click()
-    dict = run_2dmixlp()
-    m.set_key(dict, "pump pulse energy (J)", energy)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict, dict2]))
-    gui.moveTo(*coords(600, 250))
-    gui.click()
-
-# %% save OPA2 pump dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA2_pump-dependence.pkl"
-# pickle.dump(results, open('simulation/' + file_name, 'wb'))
-pickle.dump(results, open(results_path + file_name, "wb"))
-
-
-# %%% plot results
-file_name = "2022-06-09_OPA2_pump-dependence.pkl"
-# results = merge_dict(pickle.load(open('simulation/' + file_name, 'rb')))
-results = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-
-x = np.array(results["pump pulse energy (J)"]).T[0] * 1e3
-y = np.array(results["Output pulse energy (mJ)"]).T[0][0]
-y2 = np.array(results["duration_idl_ns"]).T[0]
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig, ax1, xlabel="pump pulse energy (mJ)", ylabel="idler pulse energy (mJ)"
-)
-ax1.plot(x, y, "b-")
-
-m.plot_options(
-    fig, ax2, xlabel="pump pulse energy (mJ)", ylabel="idler pulse duration (ns)"
-)
-ax2.plot(x[y2 < 100], y2[y2 < 100], "-")
-# ax2.set_ylim([0, 20])
-plt.tight_layout()
-# m.save_plot('OPA2_pump_sim')
-plt.show()
-
-# %% OPA2 seed energy dependence
-energies = np.arange(1e-7, 40e-6, 0.5e-6)
-print(energies)
-print(len(energies))
-
-# %%% run seed dependence OPA2
-set_value(dict_2dmixlp["Energy/power (J or W)"][2], 22.8e-3)
-results = []
-for energy in energies:
-    set_value(dict_2dmixlp["Energy/power (J or W)"][0], energy)
-    snlo.moveto(*dict_2dmixlp["Accept"])
-    # gui.moveTo(*coords(780, 800))
-    gui.click()
-    dict = run_2dmixlp()
-    m.set_key(dict, "seed pulse energy (J)", energy)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict, dict2]))
-    # gui.moveTo(*coords(600, 250))
-    snlo.moveto(*dict_2dmixlp["Change Inputs"])
-    gui.click()
-
-
-# save OPA2 seed dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA2_seed-dependence.pkl"
-pickle.dump(results, open(results_path + file_name, "wb"))
-
-# %%% import OPA2 seed dependence
-file_name = "2022-06-09_OPA2_seed-dependence.pkl"
-results = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-x = np.array(results["seed pulse energy (J)"]).T[0] * 1e6
-y = np.array(results["Output pulse energy (mJ)"]).T[0][0]
-y2 = np.array(results["duration_idl_ns"]).T[0]
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig,
-    ax1,
-    xlabel="seed pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse energy (mJ)",
-)
-ax1.plot(x, y, "b-")
-
-m.plot_options(
-    fig,
-    ax2,
-    xlabel="seed pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse duration (ns)",
-)
-ax2.plot(x[y2 < 100], y2[y2 < 100], "-")
-plt.tight_layout()
-# m.save_plot('OPA2_seed_sim')
-plt.show()
-
-# %%% init 2D mix LP for second OPA2 crystal
-idler_nm = 3545
-signal_nm = 1520.6
-pump_nm = 1064.16
-seedenergy_j = 20e-6
-pump_j = 23e-3
-pumpduration_ns = 8
-fwhm_seed_mm = 3.9 / 2 * 1.18  # 1/e^2 radius to FWHM
-fwhm_pump_mm = 1.6 / 2 * 1.18  # 1/e^2 radius to FWHM
-
-values = [
-    [idler_nm, signal_nm, pump_nm],
-    [
-        n_lnb(idler_nm, axis="o"),
-        n_lnb(signal_nm, axis="o"),
-        n(pm_theta(idler_nm, pump_nm), pump_nm, 25),
-    ],
-    [0, 0, 0],
-    [0.05, 0.005, 0.001],  # coating specs input, NC24, NC25
-    [0.05, 0.005, 0.001],  # coating specs output
-    [0, 0, 0],
-    [seedenergy_j, 1e-16, pump_j],
-    [pumpduration_ns, 0, pumpduration_ns],
-    [fwhm_seed_mm, fwhm_seed_mm, fwhm_pump_mm],
-    [1, 1, 1],  # supergaussian coefficient
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, -37.14],  # walkoff
-    [0, 0, 1.123],  # offset
-    [1631400, 9172700, 515780],  # todo: radius of curvature
-    [50, 32, 32],  # integration points
-    [30, 8, 8],  # crystal length, grid
-    [-4.04],  # via QPM, LNB_M
-    [0],  # delta k
-    [1000],  # distance to image
-    [25],  # time steps
-]
-# variables: idler-energy, signal-energy, pump-energy, pulse-duratios (?)
-
-# %%% full input 2D-mix-LP - OPA2
-open_function("2D-mix-LP")
-for index in range(len(values)):
-    if len(values[index]) == 1:
-        pos = list(dict_2dmixlp.values())[index][0]
-        set_value(pos, values[index][0])
-    else:
-        for element in range(len(values[index])):
-            set_value(
-                list(dict_2dmixlp.values())[index][element], values[index][element]
-            )
-
-# %% OPA2 pump energy dependence second crystal
-energies = np.arange(1e-6, 25e-3, 250e-6)
-print(energies)
-print(len(energies))
-# need pump dependence for first crystal
-file_name = "2022-06-15_OPA2_pump-dependence.pkl"
-result_xtal1 = pickle.load(open(results_path + file_name, "rb"))
-
-# print(np.array(result_xtal1[0]['Output pulse energy (mJ)']))
-
-# %%% run OPA2 pump energy dependence for the second crystal
-results = []
-for energy in energies:
-    index = list(energies).index(energy)
-    pump_energy = result_xtal1[index]["pump pulse energy (J)"][0]
-    input_energies = np.array(result_xtal1[index]["Output pulse energy (mJ)"])[0]
-    input_durations = (
-        result_xtal1[index]["duration_idl_ns"],
-        result_xtal1[index]["duration_sig_ns"],
-        result_xtal1[index]["duration_pmp_ns"],
-    )
-
-    # ignore the rest, if the Gaussian fit failed after the first stage
-    if (
-        input_durations[0][0] > 100
-        or input_durations[1][0] > 100
-        or input_durations[2][0] > 100
-    ):
-        continue
-
-    set_value(dict_2dmixlp["Energy/power (J or W)"][0], 1e-3 * input_energies[0])
-    set_value(dict_2dmixlp["Energy/power (J or W)"][1], 1e-3 * input_energies[1])
-    set_value(dict_2dmixlp["Energy/power (J or W)"][2], 1e-3 * input_energies[2])
-
-    set_value(dict_2dmixlp["Pulse duration (fwhm ns)"][0], input_durations[0][0])
-    set_value(dict_2dmixlp["Pulse duration (fwhm ns)"][1], input_durations[1][0])
-    set_value(dict_2dmixlp["Pulse duration (fwhm ns)"][2], input_durations[2][0])
-
-    snlo.moveto(*dict_2dmixlp["Accept"])
-    gui.click()
-    dict = run_2dmixlp()
-    m.set_key(dict, "pump pulse energy (J)", energy)
-    m.set_key(dict, "xtal1 pump pulse energy (J)", pump_energy)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict, dict2]))
-    snlo.moveto(*dict_2dmixlp["Change Inputs"])
-    gui.click()
-
-# save OPA2 pump dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA2_pump-dependence_xtal2.pkl"
-pickle.dump(results, open(results_path + file_name, "wb"))
-
-
-# %%% plot results OPA2 pump dep
-energies = np.arange(1e-6, 25e-3, 250e-6)
-
-file_name = "2022-06-09_OPA2_pump-dependence.pkl"
-# results = merge_dict(pickle.load(open('simulation/' + file_name, 'rb')))
-results = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-file_name = "2022-06-15_OPA2_pump-dependence_xtal2.pkl"
-# results2 = merge_dict(pickle.load(open('simulation/' + file_name, 'rb')))
-results2 = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-
-x1 = np.array(results["pump pulse energy (J)"]).T[0] * 1e3
-y1 = np.array(results["Output pulse energy (mJ)"]).T[0][0]
-x2 = np.array(results2["pump pulse energy (J)"]).T[0] * 1e3
-y2 = np.array(results2["Output pulse energy (mJ)"]).T[0][0]
-
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig, ax1, xlabel="pump pulse energy (mJ)", ylabel="idler pulse energy (mJ)"
-)
-ax1.plot(x1, y1, "b--", label="only first crystal")
-ax1.plot(x2, y2, "r-", label="both crystals")
-ax1.legend(loc="upper left")
-
-y1 = np.array(results["duration_idl_ns"]).T[0]
-y2 = np.array(results2["duration_idl_ns"]).T[0]
-
-limit = 10
-m.plot_options(
-    fig, ax2, xlabel="pump pulse energy (mJ)", ylabel="idler pulse duration (ns)"
-)
-ax2.plot(x1[y1 < limit], y1[y1 < limit], "b--", label="only first crystal")
-ax2.plot(x2[y2 < limit], y2[y2 < limit], "r-", label="both crystals")
-ax2.set_ylim([-1, 10])
-ax2.legend(loc="upper left")
-plt.tight_layout()
-# m.save_plot('OPA2_pump_sim_full')
-plt.show()
-
-# %% OPA2 seed energy dependence second crystal
-energies = np.arange(1e-7, 40e-6, 0.5e-6)
-print(energies)
-print(len(energies))
-# need pump dependence for first crystal
-file_name = "2022-06-15_OPA2_seed-dependence.pkl"
-# result_xtal1 = pickle.load(open('simulation/' + file_name, 'rb'))
-result_xtal1 = pickle.load(open(results_path + file_name, "rb"))
-
-# print(np.array(result_xtal1[0]['Output pulse energy (mJ)']))
-
-# %%% run OPA2 seed energy dependence for the second crystal
-results = []
-for energy in energies:
-    index = list(energies).index(energy)
-    seed_energy = result_xtal1[index]["seed pulse energy (J)"][0]
-    input_energies = np.array(result_xtal1[index]["Output pulse energy (mJ)"])[0]
-    input_durations = (
-        result_xtal1[index]["duration_idl_ns"],
-        result_xtal1[index]["duration_sig_ns"],
-        result_xtal1[index]["duration_pmp_ns"],
-    )
-
-    # ignore the rest, if the Gaussian fit failed after the first stage
-    if (
-        input_durations[0][0] > 100
-        or input_durations[1][0] > 100
-        or input_durations[2][0] > 100
-    ):
-        continue
-
-    set_value(dict_2dmixlp["Energy/power (J or W)"][0], 1e-3 * input_energies[0])
-    set_value(dict_2dmixlp["Energy/power (J or W)"][1], 1e-3 * input_energies[1])
-    set_value(dict_2dmixlp["Energy/power (J or W)"][2], 1e-3 * input_energies[2])
-
-    set_value(dict_2dmixlp["Pulse duration (fwhm ns)"][0], input_durations[0][0])
-    set_value(dict_2dmixlp["Pulse duration (fwhm ns)"][1], input_durations[1][0])
-    set_value(dict_2dmixlp["Pulse duration (fwhm ns)"][2], input_durations[2][0])
-    snlo.moveto(*dict_2dmixlp["Accept"])
-    gui.click()
-    dict = run_2dmixlp()
-    m.set_key(dict, "seed pulse energy (J)", energy)
-    m.set_key(dict, "xtal1 seed pulse energy (J)", seed_energy)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict, dict2]))
-    snlo.moveto(*dict_2dmixlp["Change Inputs"])
-    gui.click()
-
-# save OPA2 seed dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA2_seed-dependence_xtal2.pkl"
-# pickle.dump(results, open('simulation/' + file_name, 'wb'))
-pickle.dump(results, open(results_path + file_name, "wb"))
-
-# %%% plot results
-file_name = "2022-06-09_OPA2_seed-dependence.pkl"
-results = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-file_name = "2022-06-15_OPA2_seed-dependence_xtal2.pkl"
-results2 = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-# x = np.array(result['seed pulse energy (J)']).T[0]*1e6
-# y1 = np.array(result['Output pulse energy (mJ)']).T[0][0]
-# y2 = np.array(result2['Output pulse energy (mJ)']).T[0][0]
-
-x1 = np.array(results["seed pulse energy (J)"]).T[0] * 1e6
-y1 = np.array(results["Output pulse energy (mJ)"]).T[0][0]
-x2 = np.array(results2["seed pulse energy (J)"]).T[0] * 1e6
-y2 = np.array(results2["Output pulse energy (mJ)"]).T[0][0]
-
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig,
-    ax1,
-    xlabel="seed pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse energy (mJ)",
-)
-ax1.plot(x1, y1, "b--", label="only first crystal")
-ax1.plot(x2, y2, "r-", label="both crystals")
-ax1.legend(loc="upper left")
-
-limit = 10
-m.plot_options(
-    fig,
-    ax2,
-    xlabel="seed pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse duration (ns)",
-)
-ax2.plot(x1[y1 < limit], y1[y1 < limit], "b--", label="only first crystal")
-ax2.plot(x2[y2 < limit], y2[y2 < limit], "r-", label="both crystals")
-
-ax2.legend(loc="upper left")
-
-plt.tight_layout()
-# m.save_plot('OPA2_seed_sim_full', path=plots_path)
-plt.show()
-
-# %% conversion efficiency OPA2
-file_name = "2022-06-15_OPA2_pump-dependence_xtal2.pkl"
-results2 = merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-x = np.array(results2["pump pulse energy (J)"]).T[0] * 1e3
-y = np.array(results2["Output pulse energy (mJ)"]).T
-eff = ((y[0] + y[1]) / x)[0]
-
-fig, ax1 = plt.subplots()
-m.plot_options(
-    fig,
-    ax1,
-    image_width=15,
-    xlabel="pump pulse energy (mJ)",
-    ylabel="pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y[0][0], "r-", label="idler pulse energy")
-ax1.plot(x, y[1][0], "g-", label="signal pulse energy")
-ax1.plot(x, y[2][0], "b-", label="residual pump pulse energy")
-ax1.legend(loc="upper left")
-
-ax12 = ax1.twinx()
-m.plot_options(fig, ax12, image_width=15, aspect_ratio=2)
-ax12.plot(x[eff < 1], eff[eff < 1], "k--", label="conversion efficiency")
-ax12.set_ylabel("conversion efficiency")
-
-ax12.legend(loc="lower right")
-
-plt.tight_layout()
-# m.save_plot('OPA1_pump_sim')
-plt.show()
diff --git a/examples/lnbVsAgs-example.py b/examples/lnbVsAgs-example.py
deleted file mode 100644
index 418fa945999a360f3d271a9d110a427978ab01c2..0000000000000000000000000000000000000000
--- a/examples/lnbVsAgs-example.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Thu May 25 13:01:56 2023
-
-@author: moneke
-"""
-
-# %% import modules
-
-import numpy as np
-import matplotlib as plt
-
-from snlohelper import *  # noqa
-from snlohelper import set_screenfactors, TwoDMixLP
-
-# %% Setup screenfactor and simulation model
-
-set_screenfactors()
-
-sim = TwoDMixLP()
-sim.open()
-
-start = sim.get_configuration()  # store the start configuration
-
-
-# %% runt the simulation
-"""
-Calculate the output pulse energy for different crystal loss values.
-"""
-
-
-# Parameters
-absorption = np.linspace(0, 41e-1, 30)
-
-# Loop
-results = []
-for a in absorption:
-    r = sim.configure_run_read({"Crystal loss (1/mm)": [a, None, None]})
-    results.append(r["Output pulse energy (mJ)"][0])
-    print(a, results[-1])
-
-print(results)
-
-# %%
-
-results = []
-
-
-# %%
-
-sim.open()
-
-for data in (ags_o, ags_e):
-    results.append(sim.configure_run_read(data))
-
-
-# %%
-
-pe = np.linspace(0, 0.20, 40)
-for v in pe:
-    results.append(
-        sim.configure_run_read({"Energy/power (J or W)": [None, None, v]})[
-            "Output pulse energy (mJ)"
-        ]
-    )
-
-
-# %% Plot
-
-plt.figure()
-ax = plt.gca()
-ax.plot(pe, [r[0] for r in results], label="losless")
-ax.plot(
-    pe,
-    [
-        0.00103,
-        0.00331,
-        0.00869,
-        0.0196,
-        0.0397,
-        0.074,
-        0.129,
-        0.213,
-        0.333,
-        0.494,
-        0.696,
-        0.934,
-        1.2,
-        1.47,
-        1.74,
-        2.0,
-        2.24,
-        2.46,
-        2.66,
-        2.84,
-        3.0,
-        3.14,
-        3.27,
-        3.4,
-        3.51,
-        3.62,
-        3.72,
-        3.82,
-        3.92,
-        4.03,
-        4.13,
-        4.24,
-        4.36,
-        4.49,
-        4.62,
-        4.77,
-        4.92,
-        5.09,
-        5.27,
-        5.46,
-    ],
-    label="absorption",
-)
-ax.set_xlabel("Pumpenergie in J")
-ax.set_ylabel("MIR-Energie in mJ")
-ax.legend()
diff --git a/examples/plots/.gitkeep b/examples/plots/.gitkeep
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/examples/results/.gitkeep b/examples/results/.gitkeep
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/examples/test.py b/examples/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..efc1d6bb5d810143b2e616074127a30b77a33d89
--- /dev/null
+++ b/examples/test.py
@@ -0,0 +1,9 @@
+from snlohelper import snlo
+
+snlo.utils.set_screenfactors()
+
+sim = snlo.TwoDMixLP()
+sim.open()
+sim.configure({"Wavelengths (nm)": [1064.5, None, None]})
+result = sim.run_and_read()
+print(result)
diff --git a/examples/upconversion.py b/examples/upconversion.py
deleted file mode 100644
index 1a93f7d1359246df84586a9a549ce657e7e98967..0000000000000000000000000000000000000000
--- a/examples/upconversion.py
+++ /dev/null
@@ -1,429 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-
-created on 25.05.2022 by Jan Frederic Kinder
-"""
-
-import pickle
-import pyautogui as gui
-import matplotlib.pyplot as plt
-import analysis.modules_JFK as m  # module from NLOQO/EG-Labor/DataAnalysis, check PYTHONPATH in Spyder
-import numpy as np
-import scipy
-from socket import gethostname
-from datetime import datetime  # get current time
-
-import snlohelper as snlo
-
-# imports all of snlohelper, risk of namespace-confusion...
-from snlohelper import *
-
-"""define some global filepath variables based upon the computer name:"""
-
-
-def get_results_path():
-    pc_name = gethostname()
-    if pc_name == "WhiteShard":
-        return "M:/"
-    elif pc_name == "POPS":
-        return (
-            "C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/simulation/"
-        )
-    elif pc_name == "Myres":
-        return "D:/"
-    else:
-        print("data location not specified, use generic path: M:/")
-        return "M:/"
-
-
-def get_plots_path():
-    pc_name = gethostname()
-    if pc_name == "WhiteShard":
-        return "M:/"
-    elif pc_name == "POPS":
-        return (
-            f"C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/Ausgabe/"
-        )
-    elif pc_name == "Myres":
-        return "D:/"
-    else:
-        print("data location not specified, use generic path: M:/")
-        return "M:/"
-
-
-results_path = (
-    get_results_path()
-)  #'C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/simulation/'
-plots_path = (
-    get_plots_path()
-)  # f'C:/Users/kinder.NLOQO/HESSENBOX-DA/OPA-Paper/analysis (python)/Ausgabe/'
-
-
-# %% parameters for OPA1
-# %%% calc radius of curvature: Red1 (horizontal)
-wavelength_nm = 1064
-ref_index = snlo.n_bbo(wavelength_nm, axis="o")
-fwhm_mm = 1.11
-focus_pos_mm = 6  # focus center of crystal
-result = snlo.focus(wavelength_nm, ref_index, fwhm_mm, focus_pos_mm)
-print(f"Red1 ({wavelength_nm}nm): radius of curvature: {result[2]:.3f} mm")
-
-# %%% calc radius of curvature: Red1 (vertical)
-wavelength_nm = 1064
-ref_index = snlo.n_bbo(wavelength_nm, axis="o")
-fwhm_mm = 1.48
-focus_pos_mm = 6  # focus center of crystal
-result = snlo.focus(wavelength_nm, ref_index, fwhm_mm, focus_pos_mm)
-print(f"Red1 ({wavelength_nm}nm): radius of curvature: {result[2]:.3f} mm")
-
-# %%% calc radius of curvature: Red2 (horizontal)
-wavelength_nm = 1417
-ref_index = snlo.n_bbo(wavelength_nm, axis="o")
-fwhm_mm = 0.153
-focus_pos_mm = 6  # focus center of crystal
-result = snlo.focus(wavelength_nm, ref_index, fwhm_mm, focus_pos_mm)
-print(f"Red2 ({wavelength_nm}nm): radius of curvature: {result[2]:.3f} mm")
-
-# %%% calc radius of curvature: Red2 (vertical)
-wavelength_nm = 1417
-ref_index = snlo.n_bbo(wavelength_nm, axis="o")
-fwhm_mm = 0.151
-focus_pos_mm = 6  # focus center of crystal
-result = snlo.focus(wavelength_nm, ref_index, fwhm_mm, focus_pos_mm)
-print(f"Red2 ({wavelength_nm}nm): radius of curvature: {result[2]:.3f} mm")
-
-# %%% calc radius of curvature: Blue
-wavelength_nm = 608
-ref_index = snlo.n_bbo(wavelength_nm, axis="o")
-fwhm_mm = 0.15
-focus_pos_mm = 6  # focus center of crystal
-result = snlo.focus(wavelength_nm, ref_index, fwhm_mm, focus_pos_mm)
-print(f"Blue ({wavelength_nm}nm): radius of curvature: {result[2]:.3f} mm")
-
-# %%% parameters 2D mix LP for OPA1
-red1_nm = 1064
-red2_nm = 1417
-blue_nm = 1 / (1 / red1_nm + 1 / red2_nm)
-theta = 28.2  # from QMIX, SFG pm calculation currently broken...
-seedpower_w = 70e-6
-pump_j = 9.16e-3
-pumpduration_ns = 8
-
-values = [
-    [red1_nm, red2_nm, blue_nm],
-    [
-        snlo.n_bbo(red1_nm, axis="o"),
-        snlo.n(theta, red2_nm, crystal="BBO"),
-        snlo.n(theta, blue_nm, crystal="BBO"),
-    ],
-    [0, 0, 0],  # phases at input
-    [0, 0, 0],  # coating specs input
-    [0, 0, 0],  # coating specs output
-    [0, 0, 0],  # crystal loss 1/mm
-    [pump_j, seedpower_w, 0],
-    [8, 0, 0],
-    [[1.11, 1.48], [0.153, 0.151], 0.15],
-    [1, 1, 1],  # supergaussian coefficient
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 0, 0],
-    [0, 62.92, 63.64],  # walkoff form QMIX
-    [0, 0, 0],  # offset
-    [[1898700, 6001000], [388.73, 368.99], 1959.4],  # radius of curvature
-    [30, 32, 32],  # integration points
-    [12, 5.55, 5.92],  # crystal length, grid, 0: auto: 2.4485x1.9588mm
-    [1.55],  # via QPM, LNB_M
-    [0],  # delta k
-    [0],  # distance to image
-    [20],  # time steps
-]
-
-# %%% init 2D-mix-LP parameters
-snlo.open_function("2D-mix-LP")
-for index in range(len(values)):
-    if len(values[index]) == 1:
-        pos = list(snlo.dict_2dmixlp.values())[index][0]
-        snlo.set_value(pos, values[index][0])
-    else:
-        for element in range(len(values[index])):
-            # if there are different values for horizontal and vertical
-            if type(values[index][element]) == list:
-                value = (
-                    str(values[index][element][0])
-                    + " "
-                    + str(values[index][element][1])
-                )
-                gui.moveTo(*coords(*list(snlo.dict_2dmixlp.values())[index][element]))
-                gui.doubleClick()
-                gui.press("delete")
-                gui.doubleClick()
-                gui.write(value)
-            else:
-                snlo.set_value(
-                    list(snlo.dict_2dmixlp.values())[index][element],
-                    values[index][element],
-                )
-
-
-# %% OPA1 pump energy dependence
-# start with 1e-5
-energies = np.arange(1e-6, 0.7e-3, 1e-5)
-print(energies)
-print(len(energies))
-
-
-# %%% run OPA1 pump energy dependence
-results = []
-for energy in energies:
-    set_value(snlo.dict_2dmixlp["Energy/power (J or W)"][2], energy)
-    snlo.moveto(*dict_2dmixlp["Accept"])
-    # snlo.moveto(780, 800)
-    gui.click()
-    dict = run_2dmixlp()
-    m.set_key(dict, "pump pulse energy (J)", energy)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict, dict2]))
-    snlo.moveto(*dict_2dmixlp["Change Inputs"])
-    # (600, 250)
-    gui.click()
-
-# %% save OPA1 pump dependence
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "OPA1_pump-dependence.pkl"
-pickle.dump(results, open(results_path + file_name, "wb"))
-
-
-# %%% plot results
-file_name = "2022-06-09_OPA1_pump-dependence.pkl"
-# results = snlo.merge_dict(pickle.load(open('simulation/' + file_name, 'rb')))
-results = snlo.merge_dict(pickle.load(open(results_path + file_name, "rb")))
-
-x = np.array(results["pump pulse energy (J)"]).T[0] * 1e6
-y = np.array(results["Output pulse energy (mJ)"]).T[0][0] * 1e3
-y2 = np.array(results["duration_idl_ns"]).T[0]
-y3 = np.array(results["duration_sig_ns"]).T[0]
-y4 = np.array(results["duration_pmp_ns"]).T[0]
-
-fig, [ax1, ax2] = plt.subplots(1, 2)
-m.plot_options(
-    fig,
-    ax1,
-    image_width=15,
-    aspect_ratio=2,
-    xlabel="pump pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y, "r-", label="idler pulse energy")
-ax1.legend(loc="upper left")
-ax12 = ax1.twinx()
-m.plot_options(fig, ax12, image_width=15, aspect_ratio=2)
-ax12.plot(
-    x, 1e-6 * y / (1e-9 * y2), "b--", label="single pass gain"
-)  # divided by 1W...
-ax12.set_ylabel("single pass gain", color="blue")
-ax12.legend(loc="lower right")
-
-m.plot_options(
-    fig,
-    ax2,
-    ticks=["auto", 2],
-    image_width=15,
-    aspect_ratio=2,
-    xlabel="pump pulse energy ($\mathrm{\mu J}$)",
-    ylabel="idler pulse duration (ns)",
-)
-ax2.plot(x, y2, "r-", label="idler")
-ax2.plot(x, y3, "g--", label="sig")
-ax2.plot(x[y4 < 100], y4[y4 < 100], "b-", label="pump")
-ax2.set_ylim([0, max(y4[y4 < 100] + 0.5)])
-ax2.legend(loc="lower center", ncol=3, frameon=True, handlelength=1.5, mode="expand")
-plt.tight_layout()
-# m.save_plot('OPA1_pump_sim')
-plt.show()
-
-# %% conversion efficiency OPA1
-x = np.array(results["pump pulse energy (J)"]).T[0] * 1e6
-y = np.array(results["Output pulse energy (mJ)"]).T * 1e3
-eff = ((y[0] + y[1]) / x)[0]
-
-fig, ax1 = plt.subplots()
-m.plot_options(
-    fig,
-    ax1,
-    image_width=15,
-    xlabel="pump pulse energy ($\mathrm{\mu J}$)",
-    ylabel="pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y[0][0], "r-", label="idler pulse energy")
-ax1.plot(x, y[1][0], "g-", label="signal pulse energy")
-ax1.plot(x, y[2][0], "b-", label="residual pump pulse energy")
-ax1.legend(loc="upper left")
-
-ax12 = ax1.twinx()
-m.plot_options(fig, ax12, image_width=15, aspect_ratio=2)
-ax12.plot(x[eff < 1], eff[eff < 1], "k--", label="conversion efficiency")
-ax12.set_ylabel("conversion efficiency")
-
-ax12.legend(loc="lower right")
-
-plt.tight_layout()
-# m.save_plot('OPA1_pump_sim')
-plt.show()
-
-# %% seed power dependence
-powers1 = np.arange(10e-6, 100e-6, 5e-6)
-powers2 = np.array(
-    [
-        1e-19,
-        1e-18,
-        1e-17,
-        1e-16,
-        1e-15,
-        1e-14,
-        1e-13,
-        1e-12,
-        1e-11,
-        1e-10,
-        1e-9,
-        1e-8,
-        1e-7,
-        1e-6,
-    ]
-)
-powers = m.flatten([powers1, powers2])
-
-# %%% run seed dependence
-set_value(dict_2dmixlp["Energy/power (J or W)"][0], pump_j)
-results = []
-for power in powers:
-    set_value(dict_2dmixlp["Energy/power (J or W)"][1], power)
-    gui.moveTo(*coords(780, 800))
-    gui.click()
-    dict1 = run_2dmixlp()
-    m.set_key(dict1, "seed power (W)", power)
-    dict2 = get_spectr()
-    results.append(merge_dict([dict1, dict2]))
-    gui.moveTo(*coords(600, 250))
-    gui.click()
-
-# save OPA1 seed dependence
-# %%
-now = datetime.now()
-file_name = now.strftime("%Y-%m-%d") + "_" + "upconversion_seed-dependence.pkl"
-pickle.dump(results, open("results/" + file_name, "wb"))
-
-# %%% import and plot seed dependence
-file_name = "2022-08-02_upconversion_seed-dependence.pkl"
-results = merge_dict(pickle.load(open("results/" + file_name, "rb")))
-
-x = np.array(results["seed power (W)"]).T[0] * 1e6
-y1 = np.array(results["Output pulse energy (mJ)"]).T[1][0] * 1e6
-y2 = np.array(results["Output pulse energy (mJ)"]).T[2][0] * 1e6
-
-fig, ax1 = plt.subplots()
-m.plot_options(
-    fig,
-    ax1,
-    xlabel="(cw) seed power ($\mu$W)",
-    ylabel="idler pulse energy ($\mathrm{\mu J}$)",
-)
-ax1.plot(x, y1, "-", label="seed")
-ax1.plot(x, y2, "-", label="SFM")
-ax1.legend()
-# m.plot_options(fig, ax2, xlabel='(cw) idler power (W)',ylabel='idler pulse duration (ns)')
-# ax2.plot(x, y2, '-')
-plt.tight_layout()
-# m.save_plot('OPA1_seed_sim')
-plt.show()
-
-# %% import and plot seed dependence
-file_name = "2022-08-02_upconversion_seed-dependence.pkl"
-results = merge_dict(pickle.load(open("results/" + file_name, "rb")))
-h = scipy.constants.h
-c = scipy.constants.c
-seed_nm = 1417
-pump_nm = 1064
-sfm_nm = 1 / (1 / seed_nm + 1 / pump_nm)
-x = seed_nm * 1e-3 * np.array(results["Output pulse energy (mJ)"]).T[1][0] / (h * c)
-y1 = seed_nm * 1e-3 * np.array(results["Output pulse energy (mJ)"]).T[1][0] / (h * c)
-y2 = sfm_nm * 1e-3 * np.array(results["Output pulse energy (mJ)"]).T[2][0] / (h * c)
-
-fig, ax1 = plt.subplots()
-m.plot_options(
-    fig, ax1, xlabel="(cw) seed photons", ylabel="idler pulse energy ($\mathrm{\mu J}$)"
-)
-
-ax1.plot(x, y1, ".", label="seed")
-ax1.plot(x, y2, "^", label="SFM")
-# ax1.set_xscale('log')
-# ax1.set_yscale('log')
-ax1.loglog()
-ax1.xaxis.set_major_formatter(FormatStrFormatter("%.0f"))
-# ax1.set_xlim(left=1e10)
-# ax1.set_ylim(bottom=1e10)
-ax1.legend()
-# m.plot_options(fig, ax2, xlabel='(cw) idler power (W)',ylabel='idler pulse duration (ns)')
-# ax2.plot(x, y2, '-')
-plt.tight_layout()
-plt.ticklabel_format(axis="both", useOffset=False, style="plain")
-# m.save_plot('OPA1_seed_sim')
-plt.show()
-
-# %%
-# %% plot conversion efficiency
-fig, ax1 = plt.subplots()
-m.plot_options(fig, ax1, xlabel="(cw) seed photons", ylabel="conversion efficiency (%)")
-h = scipy.constants.h
-c = scipy.constants.c
-seed_nm = 1417
-pump_nm = 1064
-sfm_nm = 1 / (1 / seed_nm + 1 / pump_nm)
-x = 1e-3 * np.array(results["Output pulse energy (mJ)"]).T[1][0]
-y1 = (
-    100
-    * sfm_nm
-    * np.array(results["Output pulse energy (mJ)"]).T[2][0]
-    / (seed_nm * np.array(results["Output pulse energy (mJ)"]).T[1][0])
-)
-
-ax1.plot(x[y1 < 50], y1[y1 < 50], "-")
-# ax1.set_xscale('log')
-# ax1.set_yscale('log')
-# ax1.set_xlim(left=1e10)
-# ax1.set_ylim(bottom=1e10)
-# ax1.legend()
-# m.plot_options(fig, ax2, xlabel='(cw) idler power (W)',ylabel='idler pulse duration (ns)')
-# ax2.plot(x, y2, '-')
-plt.tight_layout()
-# m.save_plot('OPA1_seed_sim')
-plt.show()
-
-# %% get result
-pump_energy = 0.00916
-seed_power = 7e-05
-set_value(dict_2dmixlp["Energy/power (J or W)"][2], seed_power)
-set_value(dict_2dmixlp["Energy/power (J or W)"][0], pump_energy)
-moveto(dict_2dmixlp["Accept"])
-gui.click()
-dict1 = run_2dmixlp()
-m.set_key(dict1, "seed power (W)", seed_power)
-m.set_key(dict1, "pump pulse energy (J)", pump_energy)
-dict2 = get_spectr()
-results = merge_dict([dict1, dict2])
-# %% gather OPA1 values
-duration_idl_ns = results["duration_idl_ns"][0]
-fwhm_idl_MHz = results["fwhm_idl_MHz"][0]
-energy_sfm_mJ = results["Output pulse energy (mJ)"][0][2]
-y = np.array(results["Output pulse energy (mJ)"]) * 1e3
-input_energy_mJ = results["seed power (W)"][0] * 8e-9 * 1e3
-efficiency = results["Output pulse energy (mJ)"][0][2] / input_energy_mJ
-# gain = (1e-3*energy_idl_mJ)/(1e-9 * duration_idl_ns)/results['seed power (W)'][0]
-
-print(f"idler pulse energy: {energy_idl_mJ * 1e3:.3f}uJ")
-print(f"conversion efficiency: {efficiency*1e2:.3f}%")
-print(f"single pass gain: {gain:.1f}")
-print(f"idler duration:{duration_idl_ns:.3f}ns")
-print(f"idler bandwidth: {fwhm_idl_MHz:.3f}MHz")
diff --git a/pyproject.toml b/pyproject.toml
index 5b55a7bda3be9ba12df4a13243703e12975ee78f..101a57f7b9fe7d1232decf021c2c2e6617f96668 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,16 +1,20 @@
 [project]
 name = "snlo-helper"
 dynamic = ["version"]
+description = "An autoclicker to automatically configure and read SNLO."
 
 requires-python = ">=3.9"
 dependencies = [
-    "matplotlib",
-    "numpy",
     "pyautogui",
     "pyperclip",
 ]
 readme = "README.md"
 
+
+[project.optional-dependencies]
+full = ["numpy"]
+
+
 [build-system]
 requires = ["setuptools>=61.0", "wheel", "setuptools_scm>=7.0"]
 build-backend = "setuptools.build_meta"
diff --git a/snlohelper/__init__.py b/snlohelper/__init__.py
index 54b4c077cf974b5546f8facef41ae25d89549a29..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/snlohelper/__init__.py
+++ b/snlohelper/__init__.py
@@ -1 +0,0 @@
-from .snlohelper import *  # noqa: F403,F401
diff --git a/snlohelper/focus.py b/snlohelper/focus.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4ab1ab0ed1208f0b645396bf2db3ffea76a3413
--- /dev/null
+++ b/snlohelper/focus.py
@@ -0,0 +1,43 @@
+from .main_window import open_function, Functions
+from .utils import Position, gui, scale, set_value, get_value_complete
+
+
+# coordinates of the Focus-function (in FHD standard)
+_dict_focus: dict[str, Position] = {
+    "Wavelength (nm)": (366, 220),
+    "Refractive Index": (366, 240),
+    "Waist size (mm)": (366, 260),
+    "Face to focus (mm)": (366, 290),
+    "Dist. to focus (mm)": (366, 300),
+    "Rayleigh z in xtal (mm)": (366, 353),
+    "Beam size (mm)": (366, 380),
+    "Radius of curv. (mm)": (366, 393),
+    "Far field ang air (mrad)": (366, 413),
+    "fwhm": (219, 216),
+    "1e^2": (166, 216),
+}
+
+
+def focus(
+    wavelength_nm: float, ref_index: float, fwhm_mm: float, focus_pos_mm: float
+) -> tuple[float, float, float, float]:
+    """Call 'focus' function to determine parameters for the radius of curvature.
+
+    inputs: wavelength_nm, ref_index, waist_size_mm (FWHM), focus_pos_mm
+    return: zr, diameter (FWHM), radcurv, angle
+    """
+    open_function(Functions.FOCUS)
+    gui.leftClick(*scale(*_dict_focus["fwhm"]))
+    set_value(_dict_focus["Wavelength (nm)"], wavelength_nm)
+    set_value(_dict_focus["Refractive Index"], ref_index)
+    set_value(_dict_focus["Waist size (mm)"], fwhm_mm)
+    set_value(_dict_focus["Face to focus (mm)"], focus_pos_mm)
+    # Setting 'Dist. to focus (mm)' equal to 'Face to focus (mm)' gives parameters in air at the
+    # input face
+    set_value(_dict_focus["Dist. to focus (mm)"], focus_pos_mm)
+    # readout
+    zr = get_value_complete(_dict_focus["Rayleigh z in xtal (mm)"])
+    diameter = get_value_complete(_dict_focus["Beam size (mm)"])
+    radcurv = get_value_complete(_dict_focus["Radius of curv. (mm)"])
+    angle = get_value_complete(_dict_focus["Far field ang air (mrad)"])
+    return zr, diameter, radcurv, angle
diff --git a/snlohelper/import_snlo_file.py b/snlohelper/import_snlo_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..b726fbacf7154bccfb00dedff5d9addaea06a361
--- /dev/null
+++ b/snlohelper/import_snlo_file.py
@@ -0,0 +1,30 @@
+import re
+
+import numpy as np
+
+
+def import_snlo_file(file_name: str, file_path: str = "C:/SNLO/"):
+    """import a file generated by SNLO, specified by filename and return array (no header)"""
+    with open(file_path + file_name, "r") as f:
+        input = f.readlines()
+    data = []
+    e_numbers = re.compile(r"-*[\d.]*?E-*[+]*\d*")
+    r""" Regex
+        -*        "-" or nothing
+        [\d.]*?   decimal number (0 or more characters)
+        -*        "-" or nothing
+        [+]*      "+" or nothing
+        \d*       decimal number (0 or more characters)
+    """
+    for i in input:
+        temp = []
+        # old implementation, suffers from strings without whitespace, e.g.:
+        #    '9.206897E-100-1.616264E-2'
+        #    element = i.split()
+        #    for item in element:
+        #        temp.append(float(item))
+        new_element = e_numbers.findall(i)
+        for item in new_element:
+            temp.append(float(item))
+        data.append(temp)
+    return np.array(data.copy())
diff --git a/snlohelper/main_window.py b/snlohelper/main_window.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2a6bfa4f588cd4d4576b48abdb3e42f0ca7fae5
--- /dev/null
+++ b/snlohelper/main_window.py
@@ -0,0 +1,57 @@
+"""
+The SNLO main window
+"""
+
+from enum import StrEnum
+
+from .utils import Position, gui, scale
+
+# coordinates of the functions (in FHD standard)
+_functions_coord: dict[str, Position] = {
+    "Ref. Ind.": (66, 46),
+    "Qmix": (66, 66),
+    "Bmix": (66, 93),
+    "QPM": (66, 120),
+    "Opoangles": (66, 146),
+    "Ncpm": (66, 173),
+    "GVM": (66, 200),
+    "PW-mix-LP": (66, 233),
+    "PW-mix-SP": (66, 260),
+    "PW-mix-BB": (66, 286),
+    "2D-mix-LP": (66, 313),
+    "2D-mix-SP": (66, 340),
+    "PW-cav-LP": (66, 366),
+    "PW-OPO-SP": (66, 393),
+    "PW-OPO-BB": (66, 420),
+    "2D-cav-LP": (66, 446),
+    "Focus": (66, 473),
+    "Cavity": (66, 500),
+}
+
+
+class Functions(StrEnum):
+    """Enum for the functions."""
+
+    REF_INDEX = "Ref. Ind."
+    QMIX = "Qmix"
+    BMIX = "Bmix"
+    QPM = "QPM"
+    OPO_ANGLES = "Opoangles"
+    NCPM = "Ncpm"
+    GVM = "GVM"
+    PW_MIX_LP = "PW-mix-LP"
+    PW_MIX_SP = "PW-mix-SP"
+    PW_MIX_BB = "PW-mix-BB"
+    TWOD_MIX_LP = "2D-mix-LP"
+    TWOD_MIX_SP = "2D-mix-SP"
+    PW_CAV_LP = "PW-cav-LP"
+    PW_OPO_SP = "PW-OPO-SP"
+    PW_OPO_BB = "PW-OPO-BB"
+    TWOD_CAV_LP = "2D-cav-LP"
+    FOCUS = "Focus"
+    CAVITY = "Cavity"
+
+
+def open_function(key: str | Functions) -> None:
+    """opens function according to key"""
+    gui.click(*scale(*_functions_coord[key]))
diff --git a/snlohelper/mix_methods.py b/snlohelper/mix_methods.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd44c306b1ac28cc12702d78fa265b88ae1247bb
--- /dev/null
+++ b/snlohelper/mix_methods.py
@@ -0,0 +1,129 @@
+import time
+from typing import Any, Optional
+
+from .utils import Position, gui, scale, set_value, get_content_complete
+from .main_window import Functions, open_function
+
+
+class MixMethods:
+    """Parent class for mix methods.
+
+    Subclass it for specific methods. You should define the positions and the result interpretation.
+    """
+
+    _function: Functions
+    # Positions
+    _accept_pos: Position
+    _run_pos: Position
+    _change_inputs_pos: Position
+    _result_pos: Position  # of the results field
+    _configuration_pos: dict[str, list[Position]]  # of the configuration fields
+
+    def open(self) -> None:
+        """Open the function."""
+        open_function(self._function)
+
+    def accept(self) -> None:
+        """Click 'Accept'."""
+        gui.click(*scale(*self._accept_pos))
+
+    def run(self) -> None:
+        """Click 'Run'."""
+        gui.click(*scale(*self._run_pos))
+
+    def change_inputs(self) -> None:
+        """Click 'Change Inputs'."""
+        gui.click(*scale(*self._change_inputs_pos))
+
+    def configure(self, data: Optional[dict[str, Any]] = None) -> None:
+        """Configure the values and leave the config window open.
+
+        If any value is "None", that field will not be changed. This is useful, if you want to
+        change a single value in a row.
+        For example `data={'Wavelengths (nm)': [1064.5, None, None]}` will set the first wavelength
+        to 1064.5 nm while leaving the other wavelengths untouched.
+        """
+        self.open()
+        if data is None:
+            return
+        for key, value in data.items():
+            positions = self._configuration_pos[key]
+            for i, val in enumerate(value):
+                if val is not None:
+                    set_value(positions[i], val)
+
+    def get_configuration(self) -> dict[str, Any]:
+        """Read the current configuration."""
+        self.open()
+        data = {}
+        for key, positions in self._configuration_pos.items():
+            d = []
+            for pos in positions:
+                val = get_content_complete(pos)
+                try:
+                    d.append(float(val))
+                except ValueError:
+                    d.append(val)
+            data[key] = d
+        return data
+
+    def interpret_results(self, rows: list[str]) -> dict[str, Any]:
+        """Interpret the results and return them as a dictionary."""
+        data = {}
+        for row in rows:
+            text, values = row.split("=")
+            data[text.strip()] = [float(i) for i in values.split()]
+        return data
+
+    def read_results(self) -> list[str]:
+        return get_content_complete(self._result_pos).split("\r\n")
+
+    def run_and_read(
+        self,
+        waiting_time: float = 1,
+        max_tries: int = 10,
+        interval: float = 0.5,
+        successful_line_count: int = 3,
+    ) -> dict[str, Any]:
+        """Run an analysis and return the result."""
+        self.run()
+        time.sleep(waiting_time)
+        for _ in range(max_tries):
+            rows = self.read_results()
+            if len(rows) > successful_line_count:
+                break
+            time.sleep(interval)
+
+        # interpret results and save as dictionary:
+        return self.interpret_results(rows)
+
+    def configure_run_read(
+        self, data: Optional[dict[str, Any]] = None, **kwargs
+    ) -> dict[str, float | list[float]]:
+        """Configure and run an analysis and return the result."""
+        self.configure(data)
+        self.accept()
+        return self.run_and_read(**kwargs)
+
+
+def generate_position_dict(
+    first_configuration_field: Position, configuration_names: list[str]
+) -> dict[str, list[Position]]:
+    """Generate a position dictionary for mix methods.
+
+    This utility makes it easier to generate a position matrix with three fields per entry.
+    Adjust it afterwards according to the number of entries.
+
+    :param first_configuration_field: Position (tuple) of the top left configuration field.
+    :param configuration_names: List of all the names (in order) of the configuration.
+    """
+    # Notes regarding positions: horizontal distance is 60 px, vertical distance 16 pixels
+    positions = {}
+    i = 0
+    for name in configuration_names:
+        positions[name] = [
+            (first_configuration_field[0], first_configuration_field[1] + 16 * i),
+            (first_configuration_field[0] + 60, first_configuration_field[1] + 16 * i),
+            (first_configuration_field[0] + 120, first_configuration_field[1] + 16 * i),
+        ]
+    return positions
diff --git a/snlohelper/snlo.py b/snlohelper/snlo.py
new file mode 100644
index 0000000000000000000000000000000000000000..b95561278b14454b0aa27711b61adf035b226f50
--- /dev/null
+++ b/snlohelper/snlo.py
@@ -0,0 +1,24 @@
+import logging
+
+from . import utils
+
+from .main_window import open_function, Functions  # noqa: F401
+from .two_d_mix_lp import TwoDMixLP  # noqa: F401
+from .two_d_mix_sp import TwoDMixSP  # noqa: F401
+from .focus import focus  # noqa: F401
+
+log = logging.getLogger(__name__)
+log.addHandler(logging.NullHandler())
+
+
+def main() -> None:
+    """Initialize the SNLO helper."""
+    if len(log.handlers) < 2:
+        log.addHandler(logging.StreamHandler())
+    log.setLevel(logging.INFO)
+    utils.set_screenfactors()
+    log.info(f"Setting screenfactors to {utils.factors}.")
+
+
+if __name__ == "__main__":  # pragma: nocover
+    main()
diff --git a/snlohelper/snlohelper.py b/snlohelper/snlohelper.py
deleted file mode 100644
index 88cf406955f309729e8c0b449f888a4c4eed8d71..0000000000000000000000000000000000000000
--- a/snlohelper/snlohelper.py
+++ /dev/null
@@ -1,429 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Helper for SNLO simulations ("autoclicker")
-===========================================
-
-
-This file clicks automatically on buttons of SNLO program.
-
-
-Before you can do anything else, you need to set the factors for your display as the positions of
-the buttons changes with resolution and zoom factor. For that, you should
-execute :meth:`set_screenfactors`.
-
-example:
-
-.. code::
-
-    from snlohelper import *  # import everything
-
-    set_screenfactors()  # set the screenfactors.
-
-    # you should open SNLO now.
-    sim = TwoDMixLP()  # choose the function
-    sim.open()  # open the function
-    config = sim.get_configuration()  # read the current configuration
-
-
-created on 25.05.2022 by Jan Frederic Kinder
-"""
-
-from enum import StrEnum
-import logging
-import time
-import re
-from typing import Any, Optional
-
-import pyautogui as gui
-import numpy as np
-from pyperclip import paste
-
-
-log = logging.getLogger(__name__)
-log.addHandler(logging.NullHandler())
-
-
-Position = tuple[float, float]
-
-
-"""
-Autoclicker setup
-=================
-
-General methods for the autoclicker
-
-
-Setup of the screen and scaling
--------------------------------
-
-All positions in code are given on a Full HD (1920 * 1080) screen and dynamically adjusted to the
-screen resolution.
-"""
-
-
-def get_screenfactors(standard: Position = (1920, 1080)) -> Position:
-    """Get the scaling factor from Full HD to the current display resolution."""
-    width, height = gui.size()
-    return standard[0] / width, standard[1] / height
-
-
-def set_screenfactors(new_factors: Optional[tuple[float, float]] = None) -> None:
-    """Set the screenfactors to factors or detect them automatically."""
-    global factors
-    factors = get_screenfactors() if new_factors is None else new_factors
-
-
-def scale(x: float | Position, y: float | None = None) -> Position:
-    """Scale coordinates from the definition standard to the current screen."""
-    global factors
-    if isinstance(x, (list, tuple)):
-        if y is None:
-            x, y = x
-        else:
-            raise ValueError("You cannot specify x as a tuple and y.")
-    elif y is None:
-        raise ValueError("You have to specify two coordinatres.")
-    return x / factors[0], y / factors[1]
-
-
-def standard_position() -> Position:
-    """Get the mouse position in standard coordinates (x, y)."""
-    point = gui.position()
-    global factors
-    return point.x * factors[0], point.y * factors[1]
-
-
-"""
-Helper functions
-----------------
-
-GUI functions to get/set content from/into data fields.
-"""
-
-
-def get_content(position: Position) -> str:
-    """Get the content of the field at position via double click.
-
-    If there is a "-" in the text, the extraction fails!
-    """
-    gui.doubleClick(*scale(*position))
-    gui.hotkey("ctrl", "c")
-    return paste()
-
-
-def get_value(position: Position) -> float:
-    """move to position, retrieve value and return float"""
-    return float(get_content(position))
-
-
-def get_content_complete(position: Position) -> str:
-    """Go to position and retrieve the content there, marking all."""
-    gui.click(*scale(*position))
-    gui.hotkey("ctrl", "home")
-    gui.hotkey(
-        "ctrl", "shiftleft", "shiftright", "end"
-    )  # both shift keys necessary if keylock on.
-    gui.hotkey("ctrl", "c")
-    return paste()
-
-
-def get_value_complete(position: Position) -> float:
-    """moves to position, retrieves value via context menu (slower) and returns float"""
-    return float(get_content_complete(position))
-
-
-def set_value(position: Position, value: Any) -> None:
-    """move to position, insert value as string"""
-    gui.doubleClick(*scale(*position))
-    gui.press("delete")
-    gui.doubleClick()
-    gui.write(str(value))
-
-
-"""
-SNLO configuration
-==================
-
-
-Dictionaries of button positions
---------------------------------
-"""
-
-
-# coordinates of the functions (in FHD standard)
-_functions_coord: dict[str, Position] = {
-    "Ref. Ind.": (66, 46),
-    "Qmix": (66, 66),
-    "Bmix": (66, 93),
-    "QPM": (66, 120),
-    "Opoangles": (66, 146),
-    "Ncpm": (66, 173),
-    "GVM": (66, 200),
-    "PW-mix-LP": (66, 233),
-    "PW-mix-SP": (66, 260),
-    "PW-mix-BB": (66, 286),
-    "2D-mix-LP": (66, 313),
-    "2D-mix-SP": (66, 340),
-    "PW-cav-LP": (66, 366),
-    "PW-OPO-SP": (66, 393),
-    "PW-OPO-BB": (66, 420),
-    "2D-cav-LP": (66, 446),
-    "Focus": (66, 473),
-    "Cavity": (66, 500),
-}
-
-
-class Functions(StrEnum):
-    """Enum for the functions."""
-
-    REF_INDEX = "Ref. Ind."
-    QMIX = "Qmix"
-    BMIX = "Bmix"
-    QPM = "QPM"
-    OPO_ANGLES = "Opoangles"
-    NCPM = "Ncpm"
-    GVM = "GVM"
-    PW_MIX_LP = "PW-mix-LP"
-    PW_MIX_SP = "PW-mix-SP"
-    PW_MIX_BB = "PW-mix-BB"
-    TWOD_MIX_LP = "2D-mix-LP"
-    TWOD_MIX_SP = "2D-mix-SP"
-    PW_CAV_LP = "PW-cav-LP"
-    PW_OPO_SP = "PW-OPO-SP"
-    PW_OPO_BB = "PW-OPO-BB"
-    TWOD_CAV_LP = "2D-cav-LP"
-    FOCUS = "Focus"
-    CAVITY = "Cavity"
-
-
-# coordinates of the Focus-function (in FHD standard)
-_off: Position = 200, 200  # offset
-_dict_focus: dict[str, Position] = {
-    "Wavelength (nm)": (366, _off[1] + 20),
-    "Refractive Index": (366, _off[1] + 40),
-    "Waist size (mm)": (366, _off[1] + 60),
-    "Face to focus (mm)": (366, _off[1] + 90),
-    "Dist. to focus (mm)": (366, 300),
-    "Rayleigh z in xtal (mm)": (366, 353),
-    "Beam size (mm)": (366, 380),
-    "Radius of curv. (mm)": (366, 393),
-    "Far field ang air (mrad)": (366, 413),
-    "fwhm": (219, 216),
-    "1e^2": (166, 216),
-}
-
-
-"""
-SNLO functions
---------------
-
-Functions to access SNLO buttons/configurations.
-"""
-
-
-def open_function(key: str | Functions) -> None:
-    """opens function according to key"""
-    gui.click(*scale(*_functions_coord[key]))
-
-
-def focus(
-    wavelength_nm: float, ref_index: float, fwhm_mm: float, focus_pos_mm: float
-) -> tuple[float, float, float, float]:
-    """calls focus function to determine parameters for the radius of curvature.
-    inputs: wavelength_nm, ref_index, waist_size_mm (FWHM), focus_pos_mm
-    returns: zr, diameter (FWHM), radcurv, angle
-    """
-    open_function(Functions.FOCUS)
-    gui.leftClick(*scale(*_dict_focus["fwhm"]))
-    set_value(_dict_focus["Wavelength (nm)"], wavelength_nm)
-    set_value(_dict_focus["Refractive Index"], ref_index)
-    set_value(_dict_focus["Waist size (mm)"], fwhm_mm)
-    set_value(_dict_focus["Face to focus (mm)"], focus_pos_mm)
-    # Setting 'Dist. to focus (mm)' equal to 'Face to focus (mm)' gives parameters in air at the
-    # input face
-    set_value(_dict_focus["Dist. to focus (mm)"], focus_pos_mm)
-    # readout
-    zr = get_value_complete(_dict_focus["Rayleigh z in xtal (mm)"])
-    diameter = get_value_complete(_dict_focus["Beam size (mm)"])
-    radcurv = get_value_complete(_dict_focus["Radius of curv. (mm)"])
-    angle = get_value_complete(_dict_focus["Far field ang air (mrad)"])
-    return zr, diameter, radcurv, angle
-
-
-class MixMethods:
-    """Parent class for mix methods.
-
-    Subclass it for specific methods. You should define the positions and the result interpretation.
-    """
-
-    _function: Functions
-    # Positions
-    _accept_pos: Position
-    _run_pos: Position
-    _change_inputs_pos: Position
-    _result_pos: Position  # of the results field
-    _configuration_pos: dict[str, list[Position]]  # of the configuration fields
-
-    def open(self):
-        """Open the function."""
-        open_function(self._function)
-
-    def accept(self):
-        """Click 'Accept'."""
-        gui.click(*scale(*self._accept_pos))
-
-    def run(self):
-        """Click 'Run'."""
-        gui.click(*scale(*self._run_pos))
-
-    def change_inputs(self):
-        """Click 'Change Inputs'."""
-        gui.click(*scale(*self._change_inputs_pos))
-
-    def configure(self, data: Optional[dict[str, Any]] = None) -> None:
-        """Configure the values and leave the config window open.
-
-        If any value is "None", that field will not be changed. This is useful, if you want to
-        change a single value in a row.
-        For example `data={'Wavelengths (nm)': [1064.5, None, None]}` will set the first wavelength
-        to 1064.5 nm while leaving the other wavelengths untouched.
-        """
-        self.open()
-        if data is None:
-            return
-        for key, value in data.items():
-            positions = self._configuration_pos[key]
-            for i, val in enumerate(value):
-                if val is not None:
-                    set_value(positions[i], val)
-
-    def get_configuration(self) -> dict[str, Any]:
-        """Read the current configuration."""
-        self.open()
-        data = {}
-        for key, positions in self._configuration_pos.items():
-            d = []
-            for pos in positions:
-                val = get_content_complete(pos)
-                try:
-                    d.append(float(val))
-                except ValueError:
-                    d.append(val)
-            data[key] = d
-        return data
-
-    def interpret_results(self, rows: list[str]) -> dict[str, Any]:
-        """Interpret the results and return them as a dictionary."""
-        raise NotImplementedError("Implement in subclass.")
-
-    def run_and_read(
-        self,
-        waiting_time: float = 1,
-        max_tries: int = 10,
-        interval: float = 0.5,
-        successful_line_count: int = 3,
-    ) -> dict[str, Any]:
-        """Run an analysis and return the result."""
-        self.run()
-        time.sleep(waiting_time)
-        for _ in range(max_tries):
-            rows = get_content_complete(self._result_pos).split("\r\n")
-            if len(rows) > successful_line_count:
-                break
-            time.sleep(interval)
-
-        # interpret results and save as dictionary:
-        return self.interpret_results(rows)
-
-    def configure_run_read(
-        self, data: Optional[dict[str, Any]] = None, **kwargs
-    ) -> dict[str, float | list[float]]:
-        """Configure and run an analysis and return the result."""
-        self.configure(data)
-        self.accept()
-        return self.run_and_read(**kwargs)
-
-
-class TwoDMixLP(MixMethods):
-    """The '2D-mix-LP' method."""
-
-    _function = Functions.TWOD_MIX_LP
-    _accept_pos = (506, 536)
-    _run_pos = (140, 220)
-    _change_inputs_pos = (373, 166)
-    _result_pos = (133, 293)
-
-    # coordinates of the 2DmixLP-function (in FHD standard)
-    _configuration_pos = {
-        "Wavelengths (nm)": [(403, 186), (470, 186), (536, 186)],
-        "Indexes of refraction": [(403, 200), (470, 200), (536, 200)],
-        "Phases at input (rad)": [(403, 213), (470, 213), (536, 213)],
-        "Input face reflectivity (0-1)": [(403, 226), (470, 226), (536, 226)],
-        "Output face reflectivity (0-1)": [(403, 246), (470, 246), (536, 246)],
-        "Crystal loss (1/mm)": [(403, 260), (470, 260), (536, 260)],
-        "Energy/power (J or W)": [(416, 280), (483, 280), (550, 280)],
-        "Pulse duration (fwhm ns)": [(403, 293), (470, 293), (536, 293)],
-        "Beam diam. (fwhm mm)": [(403, 306), (470, 306), (536, 306)],
-        "Supergaussian coeff.": [(403, 326), (470, 326), (536, 326)],
-        "n2 red1 (sq cm/W)": [(403, 340), (470, 340), (536, 340)],
-        "n2 red2 (sq cm/W)": [(403, 360), (470, 360), (536, 360)],
-        "n2 blue (sq cm/W)": [(403, 373), (470, 373), (536, 373)],
-        "beta red1 (cm/W)": [(403, 393), (470, 393), (536, 393)],
-        "beta red2 (cm/W)": [(403, 406), (470, 406), (536, 406)],
-        "beta blue (cm/W)": [(403, 420), (470, 420), (536, 420)],
-        "Walkoff angles (mrad)": [(403, 440), (470, 440), (536, 440)],
-        "Offset in wo dir. (mm)": [(403, 453), (470, 453), (536, 453)],
-        "Rad. curv. (mm/air“)": [(403, 473), (470, 473), (536, 473)],
-        "# of integ/grid points": [(403, 486), (470, 486), (536, 486)],
-        "Crystal/grid sizes (mm)": [(403, 500), (470, 500), (536, 500)],
-        "Deff (pm/V)": [(403, 520)],
-        "Delta k (1/mm)": [(403, 533)],
-        "Dist. to image (mm)": [(403, 546)],
-        "# time steps": [(403, 566)],
-    }
-
-    def interpret_results(self, rows: list[str]) -> dict[str, float | list[float]]:
-        """Interpret the results."""
-        return {
-            "Input peak irradiance (W/sq cm)": [float(i) for i in rows[0].split()[5:]],
-            "Input peak fluence (J/sq cm)": [float(i) for i in rows[1].split()[6:]],
-            "Input peak powers (W)": [float(i) for i in rows[2].split()[5:]],
-            "Output peak fluence (J/sq cm)": [float(i) for i in rows[3].split()[6:]],
-            "Output pulse energy (mJ)": [float(i) for i in rows[4].split()[5:]],
-            "So (W/sq cm)": float(rows[5].split()[4]),
-        }
-
-
-def import_snlo_file(file_name: str, file_path: str = "C:/SNLO/"):
-    """import a file generated by SNLO, specified by filename and return array (no header)"""
-    with open(file_path + file_name, "r") as f:
-        input = f.readlines()
-    data = []
-    e_numbers = re.compile(r"-*[\d.]*?E-*[+]*\d*")
-    r""" Regex
-        -*        "-" or nothing
-        [\d.]*?   decimal number (0 or more characters)
-        -*        "-" or nothing
-        [+]*      "+" or nothing
-        \d*       decimal number (0 or more characters)
-    """
-    for i in input:
-        temp = []
-        # old implementation, suffers from strings without whitespace, e.g.:
-        #    '9.206897E-100-1.616264E-2'
-        #    element = i.split()
-        #    for item in element:
-        #        temp.append(float(item))
-        new_element = e_numbers.findall(i)
-        for item in new_element:
-            temp.append(float(item))
-        data.append(temp)
-    return np.array(data.copy())
-
-
-if __name__ == "__main__":
-    if len(log.handlers) < 2:
-        log.addHandler(logging.StreamHandler())
-    log.setLevel(logging.INFO)
-    set_screenfactors()
-    log.info(f"Setting screenfactors to {factors}.")
diff --git a/snlohelper/two_d_mix_lp.py b/snlohelper/two_d_mix_lp.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1abf34df3a5c6ee0d4eaf3a1b7a106aec3477e2
--- /dev/null
+++ b/snlohelper/two_d_mix_lp.py
@@ -0,0 +1,52 @@
+from .main_window import Functions
+from .mix_methods import MixMethods
+
+
+class TwoDMixLP(MixMethods):
+    """The '2D-mix-LP' method."""
+
+    _function = Functions.TWOD_MIX_LP
+    _accept_pos = (506, 520)
+    _run_pos = (140, 220)
+    _change_inputs_pos = (373, 166)
+    _result_pos = (133, 293)
+
+    # coordinates of the 2DmixLP-function (in FHD standard)
+    _configuration_pos = {
+        "Wavelengths (nm)": [(400, 186), (460, 186), (520, 186)],
+        "Indexes of refraction": [(400, 200), (460, 200), (520, 200)],
+        "Phases at input (rad)": [(400, 213), (460, 213), (520, 213)],
+        "Input face reflectivity (0-1)": [(400, 226), (460, 226), (520, 226)],
+        "Output face reflectivity (0-1)": [(400, 246), (460, 246), (520, 246)],
+        "Crystal loss (1/mm)": [(400, 260), (460, 260), (520, 260)],
+        "Energy/power (J or W)": [(416, 280), (483, 280), (550, 280)],
+        "Pulse duration (fwhm ns)": [(400, 293), (460, 293), (520, 293)],
+        "Beam diam. (fwhm mm)": [(400, 306), (460, 306), (520, 306)],
+        "Supergaussian coeff.": [(400, 326), (460, 326), (520, 326)],
+        "n2 red1 (sq cm/W)": [(400, 340), (460, 340), (520, 340)],
+        "n2 red2 (sq cm/W)": [(400, 360), (460, 360), (520, 360)],
+        "n2 blue (sq cm/W)": [(400, 373), (460, 373), (520, 373)],
+        "beta red1 (cm/W)": [(400, 393), (460, 393), (520, 393)],
+        "beta red2 (cm/W)": [(400, 406), (460, 406), (520, 406)],
+        "beta blue (cm/W)": [(400, 420), (460, 420), (520, 420)],
+        "Walkoff angles (mrad)": [(400, 440), (460, 440), (520, 440)],
+        "Offset in wo dir. (mm)": [(400, 453), (460, 453), (520, 453)],
+        "Rad. curv. (mm/air“)": [(400, 473), (460, 473), (520, 473)],
+        "# of integ/grid points": [(400, 486), (460, 486), (520, 486)],
+        "Crystal/grid sizes (mm)": [(400, 500), (460, 500), (520, 500)],
+        "Deff (pm/V)": [(400, 520)],
+        "Delta k (1/mm)": [(400, 533)],
+        "Dist. to image (mm)": [(400, 546)],
+        "# time steps": [(400, 566)],
+    }
+
+    def interpret_results(self, rows: list[str]) -> dict[str, float | list[float]]:
+        """Interpret the results."""
+        return {
+            "Input peak irradiance (W/sq cm)": [float(i) for i in rows[0].split()[5:]],
+            "Input peak fluence (J/sq cm)": [float(i) for i in rows[1].split()[6:]],
+            "Input peak powers (W)": [float(i) for i in rows[2].split()[5:]],
+            "Output peak fluence (J/sq cm)": [float(i) for i in rows[3].split()[6:]],
+            "Output pulse energy (mJ)": [float(i) for i in rows[4].split()[5:]],
+            "So (W/sq cm)": float(rows[5].split()[4]),
+        }
diff --git a/snlohelper/two_d_mix_sp.py b/snlohelper/two_d_mix_sp.py
new file mode 100644
index 0000000000000000000000000000000000000000..b15561404e5c1a540a0801c4d6026cb012eb1fa2
--- /dev/null
+++ b/snlohelper/two_d_mix_sp.py
@@ -0,0 +1,45 @@
+from .main_window import Functions
+from .mix_methods import MixMethods
+
+
+class TwoDMixSP(MixMethods):
+    """The '2D-mix-SP' method."""
+
+    _function = Functions.TWOD_MIX_SP
+    _accept_pos = (506, 600)
+    _run_pos = (140, 200)
+    _change_inputs_pos = (150, 266)
+    _result_pos = (133, 293)
+
+    # coordinates of the 2DmixLP-function (in FHD standard)
+    _configuration_pos = {
+        "Wavelengths (nm)": [(400, 186), (460, 186), (520, 186)],
+        "Indexes of refraction": [(400, 200), (460, 200), (520, 200)],
+        "Group velocity index": [(400, 213), (460, 213), (520, 213)],
+        "Group delay dispersion": [(400, 226), (460, 226), (520, 226)],
+        "Phase (radians)": [(400, 246), (460, 246), (520, 246)],
+        "Input face reflectivity": [(400, 260), (460, 260), (520, 260)],
+        "Output face reflectivity": [(416, 280), (483, 280), (550, 280)],
+        "Crystal absorption (per mm)": [(400, 293), (460, 293), (520, 293)],
+        "n2 red1 (sq cm/W)": [(400, 306), (460, 306), (520, 306)],
+        "n2 red2 (sq cm/W)": [(400, 326), (460, 326), (520, 326)],
+        "n2 blue (sq cm/W)": [(400, 340), (460, 340), (520, 340)],
+        "beta red1 (cm/W)": [(400, 360), (460, 360), (520, 360)],
+        "beta red2 (cm/W)": [(400, 373), (460, 373), (520, 373)],
+        "beta blue (cm/W)": [(400, 393), (460, 393), (520, 393)],
+        "Pulse energy (Joules)": [(400, 406), (460, 406), (520, 406)],
+        "Pulse duration (ps)": [(400, 420), (460, 420), (520, 420)],
+        "Pulse Delay (ps)": [(400, 440), (460, 440), (520, 440)],
+        "Pulse chirp (THz/ps)": [(400, 453), (460, 453), (520, 453)],
+        "Beam diameter (mm)": [(400, 473), (460, 473), (520, 473)],
+        "Supergaussian coefficient": [(400, 486), (460, 486), (520, 486)],
+        "Walkoff angle (mrad)": [(400, 500), (460, 500), (520, 500)],
+        "Beam position (mm)": [(400, 520), (460, 520), (520, 520)],
+        "Radius of curvature (mm)": [(400, 533), (460, 533), (460, 533)],
+        "Number t,x,y points": [(400, 546), (460, 546), (520, 546)],
+        "Size of crystal/grid (mm)": [(400, 566), (460, 566), (520, 566)],
+        "deff (pm/V)": [(400, 580)],
+        "delta k (1/mm)": [(400, 596)],
+        "Number of z steps": [(400, 612)],
+        "Dist. to detector (mm)": [(400, 628)]
+    }
diff --git a/snlohelper/utils.py b/snlohelper/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..583e915f0e61e820006b0be0f9d821b915146073
--- /dev/null
+++ b/snlohelper/utils.py
@@ -0,0 +1,112 @@
+"""
+Autoclicker setup
+=================
+
+General methods for the autoclicker
+"""
+
+import logging
+from typing import Any, Optional
+
+import pyautogui as gui
+from pyperclip import paste
+
+
+log = logging.getLogger(__name__)
+log.addHandler(logging.NullHandler())
+
+Position = tuple[float, float]
+
+
+"""
+
+Setup of the screen and scaling
+-------------------------------
+
+All positions in code are given on a Full HD (1920 * 1080) screen and dynamically adjusted to the
+screen resolution.
+"""
+
+
+def get_screenfactors(standard: Position = (1920, 1080)) -> Position:
+    """Get the scaling factor from Full HD to the current display resolution."""
+    width, height = gui.size()
+    return standard[0] / width, standard[1] / height
+
+
+def set_screenfactors(new_factors: Optional[tuple[float, float]] = None) -> tuple[float, float]:
+    """Set the screenfactors to `new_factors` or detect them automatically."""
+    global factors
+    factors = get_screenfactors() if new_factors is None else new_factors
+    return factors
+
+
+def scale(x: float | Position, y: float | None = None) -> Position:
+    """Scale coordinates from the definition standard to the current screen."""
+    global factors
+    if isinstance(x, (list, tuple)):
+        if y is None:
+            x, y = x
+        else:
+            raise ValueError("You cannot specify x as a tuple and y.")
+    elif y is None:
+        raise ValueError("You have to specify two coordinatres.")
+    try:
+        return x / factors[0], y / factors[1]
+    except NameError:
+        log.warning("Factors was not set, running `set_screenfactors` to get values.")
+        set_screenfactors()
+        return x / factors[0], y / factors[1]
+
+
+def standard_position() -> Position:
+    """Get the mouse position in standard coordinates (x, y)."""
+    point = gui.position()
+    global factors
+    return point.x * factors[0], point.y * factors[1]
+
+
+"""
+Helper functions
+----------------
+
+GUI functions to get/set content from/into data fields.
+"""
+
+
+def get_content(position: Position) -> str:
+    """Get the content of the field at position via double click.
+
+    If there is a "-" in the text, the extraction fails!
+    """
+    gui.doubleClick(*scale(*position))
+    gui.hotkey("ctrl", "c")
+    return paste()
+
+
+def get_value(position: Position) -> float:
+    """Move to position, retrieve value and return float."""
+    return float(get_content(position))
+
+
+def get_content_complete(position: Position) -> str:
+    """Go to position and retrieve the content there, marking all."""
+    gui.click(*scale(*position))
+    gui.hotkey("ctrl", "home")
+    # both shift keys are necessary if keylock is on
+    gui.hotkey("ctrl", "shiftleft", "shiftright", "end")
+    gui.hotkey("ctrl", "c")
+    return paste()
+
+
+def get_value_complete(position: Position) -> float:
+    """Move to position, retrieve value via context menu (slower) and return float."""
+    return float(get_content_complete(position))
+
+
+def set_value(position: Position, value: Any) -> None:
+    """Move to position, insert value as string."""
+    gui.doubleClick(*scale(*position))
+    gui.press("delete")
+    gui.doubleClick()
+    gui.write(str(value))