diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..a0cf709bc0991b5340080f944d02894dc1596d46
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1 @@
+# CHANGELOG
diff --git a/README.md b/README.md
index cc3d50de977c4d59d2ea5ae33fa81275f330350f..4f50b59abf78d6ed48f281e7d745ee563e8e5adf 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ The autoclicker can be interrupted by moving the mouse into the top right corner
 
 ## Installation
 
-Install all the requirements in requirements.txt, for example with pip via `pip install -r requirements.txt` (in this folder).
+Install it in this folder via `pip install -e .`
 
 
 ## Usage
@@ -18,4 +18,4 @@ Install all the requirements in requirements.txt, for example with pip via `pip
 - 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.
 
-Use the methods as conventient.
+Use the methods as convenient.
diff --git a/snlo-helper/OPA-simulation.py b/examples/OPA-simulation.py
similarity index 99%
rename from snlo-helper/OPA-simulation.py
rename to examples/OPA-simulation.py
index 63e553b1cde3a7e40a8393a7efa1c6b2fc41b739..6ff02c750c744d7d0ee0157398464f3cdc99f72c 100644
--- a/snlo-helper/OPA-simulation.py
+++ b/examples/OPA-simulation.py
@@ -7,7 +7,7 @@ created on 25.05.2022 by Jan Frederic Kinder
 import pickle
 import pyautogui as gui
 import matplotlib.pyplot as plt
-import modules_JFK as m  # module from NLOQO/EG-Labor/DataAnalysis, check PYTHONPATH in Spyder
+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
diff --git a/snlo-helper/lnbVsAgs-example.py b/examples/lnbVsAgs-example.py
similarity index 100%
rename from snlo-helper/lnbVsAgs-example.py
rename to examples/lnbVsAgs-example.py
diff --git a/snlo-helper/plots/.gitkeep b/examples/plots/.gitkeep
similarity index 100%
rename from snlo-helper/plots/.gitkeep
rename to examples/plots/.gitkeep
diff --git a/snlo-helper/results/.gitkeep b/examples/results/.gitkeep
similarity index 100%
rename from snlo-helper/results/.gitkeep
rename to examples/results/.gitkeep
diff --git a/snlo-helper/upconversion.py b/examples/upconversion.py
similarity index 99%
rename from snlo-helper/upconversion.py
rename to examples/upconversion.py
index f7ac998d77d61a8453def9b2558d3703a2dad7fc..1a93f7d1359246df84586a9a549ce657e7e98967 100644
--- a/snlo-helper/upconversion.py
+++ b/examples/upconversion.py
@@ -7,7 +7,7 @@ created on 25.05.2022 by Jan Frederic Kinder
 import pickle
 import pyautogui as gui
 import matplotlib.pyplot as plt
-import modules_JFK as m  # module from NLOQO/EG-Labor/DataAnalysis, check PYTHONPATH in Spyder
+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
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..5b55a7bda3be9ba12df4a13243703e12975ee78f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,30 @@
+[project]
+name = "snlo-helper"
+dynamic = ["version"]
+
+requires-python = ">=3.9"
+dependencies = [
+    "matplotlib",
+    "numpy",
+    "pyautogui",
+    "pyperclip",
+]
+readme = "README.md"
+
+[build-system]
+requires = ["setuptools>=61.0", "wheel", "setuptools_scm>=7.0"]
+build-backend = "setuptools.build_meta"
+
+
+[tool.ruff]
+select = ["E", "F", "W"]
+line-length = 100
+exclude = [
+    ".git",
+    ".venv",
+    "__pypackages__",
+    "__pycache__",
+    "build",
+    "dist",
+    "docs/conf.py",
+]
diff --git a/snlo-helper/__init__.py b/snlo-helper/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/snlo_helper.egg-info/PKG-INFO b/snlo_helper.egg-info/PKG-INFO
new file mode 100644
index 0000000000000000000000000000000000000000..f5462b47189f9a55bf3ad253cdeaca692be5fac8
--- /dev/null
+++ b/snlo_helper.egg-info/PKG-INFO
@@ -0,0 +1,31 @@
+Metadata-Version: 2.1
+Name: snlo-helper
+Version: 0.0.0
+Requires-Python: >=3.9
+Description-Content-Type: text/markdown
+Requires-Dist: matplotlib
+Requires-Dist: numpy
+Requires-Dist: pyautogui
+Requires-Dist: pyperclip
+
+# SNLO Helper
+
+Utilises SNLO software for simulation of nonlinear processes in crystals.
+
+An autoclicker clicks different buttons/fills fields in order to automate SNLO simulations.
+
+Beware, that the script does its work with your mouse and keyboard, so you should not interact with the computer in the meanwhile.
+The autoclicker can be interrupted by moving the mouse into the top right corner of the screen.
+
+
+## Installation
+
+Install all the requirements in requirements.txt, for example with pip via `pip install -r requirements.txt` (in this folder).
+
+
+## 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.
+
+Use the methods as conventient.
diff --git a/snlo_helper.egg-info/SOURCES.txt b/snlo_helper.egg-info/SOURCES.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6277ebb103c5b4e7541e29ef0e007294aaf083f9
--- /dev/null
+++ b/snlo_helper.egg-info/SOURCES.txt
@@ -0,0 +1,14 @@
+.gitignore
+README.md
+pyproject.toml
+requirements.txt
+snlo_helper.egg-info/PKG-INFO
+snlo_helper.egg-info/SOURCES.txt
+snlo_helper.egg-info/dependency_links.txt
+snlo_helper.egg-info/requires.txt
+snlo_helper.egg-info/top_level.txt
+snlohelper/OPA-simulation.py
+snlohelper/__init__.py
+snlohelper/lnbVsAgs-example.py
+snlohelper/snlohelper.py
+snlohelper/upconversion.py
\ No newline at end of file
diff --git a/snlo_helper.egg-info/dependency_links.txt b/snlo_helper.egg-info/dependency_links.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/snlo_helper.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/snlo_helper.egg-info/requires.txt b/snlo_helper.egg-info/requires.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d63086331dc87ad24457e5aaba55c21cccdfac69
--- /dev/null
+++ b/snlo_helper.egg-info/requires.txt
@@ -0,0 +1,4 @@
+matplotlib
+numpy
+pyautogui
+pyperclip
diff --git a/snlo_helper.egg-info/top_level.txt b/snlo_helper.egg-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..14151c0eb0c874c34bc0040f000941ebaadcd837
--- /dev/null
+++ b/snlo_helper.egg-info/top_level.txt
@@ -0,0 +1 @@
+snlohelper
diff --git a/snlohelper/__init__.py b/snlohelper/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..54b4c077cf974b5546f8facef41ae25d89549a29
--- /dev/null
+++ b/snlohelper/__init__.py
@@ -0,0 +1 @@
+from .snlohelper import *  # noqa: F403,F401
diff --git a/snlohelper/get_spectr.py b/snlohelper/get_spectr.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f5e28b80d90752a00a48f6975a917b7ce81ea57
--- /dev/null
+++ b/snlohelper/get_spectr.py
@@ -0,0 +1,137 @@
+
+import numpy as np
+import time
+from typing import cast
+
+import matplotlib.pyplot as plt
+import pyautogui as gui
+
+import analysis.modules_JFK as m
+from snlohelper import scale, import_snlo_file
+
+
+def get_spectr(
+    call_function: bool = True, display_results: bool = False
+) -> dict[str, float]:
+    """imports spectra and power vs. time data for all three fields of the mixing process.
+    call_function: for 2D-mix-LP you have to click on 'Spectra' to calculate the spectra.
+    display_results: gives plots for spectra and powers
+    returns dictionary with bandwidths and durations (given as FWHMs)"""
+    if call_function:
+        gui.click(*scale(400, 260))
+        time.sleep(0.5)
+
+    # detuning [MHz], Red1, Red2, Blue
+    spectr = import_snlo_file("OPA2D_SP.dat")
+    # time, power, phase, Mx^2, My^2, Rad Curv c, Rad Curv y, X-tilt, w_x^2, w_y^2
+    id_beam = import_snlo_file("ID_BEAM.dat")
+    sig_beam = import_snlo_file("SIG_BEAM.dat")
+    pmp_beam = import_snlo_file("PMP_BEAM.dat")
+
+    # fit idler duration
+    x = 1e9 * id_beam.T[0]  # ns
+    y = 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]
+    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]
+    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)
+    fits = idl, sig, pmp
+    if display_results:
+        # plot pulse durations
+        fig, ax = plt.subplots(1, 3)
+        fwhms = fwhm_ns_idl, fwhm_ns_sig, fwhm_ns_pmp
+        titles = "idler (Red1)", "signal (Red2)", "pump (Blue)"
+        colors = "red", "green", "blue"
+        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])
+
+            x_fit = np.arange(min(x_lists[i]), max(x_lists[i]), 0.1)
+
+            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])
+            # ax[i].set_xlim([min(x_lists[i]), max(x_lists[i])])
+        m.plot_options(
+            fig,
+            ax[0],
+            image_width=15,
+            aspect_ratio=3,
+            xlabel="time (ns)",
+            ylabel="normalized power",
+        )
+        plt.tight_layout()
+        plt.show()
+
+    # fit spectra
+    x = spectr.T[0]  # in MHz
+    y_idl = spectr.T[1]
+    y_sig = spectr.T[2]
+    y_pmp = spectr.T[3]
+
+    idl = cast(list[float], m.gaussian_fit(np.array([x, y_idl]).T, False)[0])
+    sig = cast(list[float], m.gaussian_fit(np.array([x, y_sig]).T, False)[0])
+    pmp = cast(list[float], m.gaussian_fit(np.array([x, y_pmp]).T, False)[0])
+
+    fwhm_spec_idl = abs(2 * np.sqrt(2 * np.log(2)) * idl[2])
+    fwhm_spec_sig = abs(2 * np.sqrt(2 * np.log(2)) * sig[2])
+    fwhm_spec_pmp = abs(2 * np.sqrt(2 * np.log(2)) * pmp[2])
+
+    y_lists = y_idl, y_sig, y_pmp
+
+    fits = idl, sig, pmp
+    if display_results:
+        # plot spectra durations
+        fig, ax = plt.subplots(1, 3)
+        fwhms = fwhm_spec_idl, fwhm_spec_sig, fwhm_spec_pmp
+        titles = "idler (Red1)", "signal (Red2)", "pump (Blue)"
+        colors = "red", "green", "blue"
+        for i in range(len(ax)):
+            m.plot_options(
+                fig, ax[i], xlabel="detuning (MHz)", image_width=15, aspect_ratio=3
+            )
+            ax[i].plot(x, y_lists[i], ".", color=colors[i])
+            ax[i].set_title(titles[i], color=colors[i])
+
+            x_fit = np.arange(min(x), max(x), 0.1)
+
+            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}MHz", ha="center", color=colors[i])
+            ax[i].set_xlim([-2 * fwhms[i], 2 * fwhms[i]])
+        m.plot_options(
+            fig,
+            ax[0],
+            image_width=15,
+            aspect_ratio=3,
+            xlabel="time (ns)",
+            ylabel="normalized power",
+        )
+        plt.tight_layout()
+        plt.show()
+
+    return {
+        "fwhm_idl_MHz": fwhm_spec_idl,
+        "fwhm_sig_MHz": fwhm_spec_sig,
+        "fwhm_pmp_MHz": fwhm_spec_pmp,
+        "duration_idl_ns": fwhm_ns_idl,
+        "duration_sig_ns": fwhm_ns_sig,
+        "duration_pmp_ns": fwhm_ns_pmp,
+    }
diff --git a/snlo-helper/snlohelper.py b/snlohelper/snlohelper.py
similarity index 74%
rename from snlo-helper/snlohelper.py
rename to snlohelper/snlohelper.py
index 6ef1a13175e66fd18a91585716fa7f09bcff6f17..88cf406955f309729e8c0b449f888a4c4eed8d71 100644
--- a/snlo-helper/snlohelper.py
+++ b/snlohelper/snlohelper.py
@@ -32,15 +32,12 @@ from enum import StrEnum
 import logging
 import time
 import re
-from typing import Any, cast, Optional
+from typing import Any, Optional
 
 import pyautogui as gui
-import matplotlib.pyplot as plt
 import numpy as np
 from pyperclip import paste
 
-import analysis.modules_JFK as m
-
 
 log = logging.getLogger(__name__)
 log.addHandler(logging.NullHandler())
@@ -424,133 +421,6 @@ def import_snlo_file(file_name: str, file_path: str = "C:/SNLO/"):
     return np.array(data.copy())
 
 
-def get_spectr(
-    call_function: bool = True, display_results: bool = False
-) -> dict[str, float]:
-    """imports spectra and power vs. time data for all three fields of the mixing process.
-    call_function: for 2D-mix-LP you have to click on 'Spectra' to calculate the spectra.
-    display_results: gives plots for spectra and powers
-    returns dictionary with bandwidths and durations (given as FWHMs)"""
-    if call_function:
-        gui.click(*scale(400, 260))
-        time.sleep(0.5)
-
-    # detuning [MHz], Red1, Red2, Blue
-    spectr = import_snlo_file("OPA2D_SP.dat")
-    # time, power, phase, Mx^2, My^2, Rad Curv c, Rad Curv y, X-tilt, w_x^2, w_y^2
-    id_beam = import_snlo_file("ID_BEAM.dat")
-    sig_beam = import_snlo_file("SIG_BEAM.dat")
-    pmp_beam = import_snlo_file("PMP_BEAM.dat")
-
-    # fit idler duration
-    x = 1e9 * id_beam.T[0]  # ns
-    y = 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]
-    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]
-    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)
-    fits = idl, sig, pmp
-    if display_results:
-        # plot pulse durations
-        fig, ax = plt.subplots(1, 3)
-        fwhms = fwhm_ns_idl, fwhm_ns_sig, fwhm_ns_pmp
-        titles = "idler (Red1)", "signal (Red2)", "pump (Blue)"
-        colors = "red", "green", "blue"
-        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])
-
-            x_fit = np.arange(min(x_lists[i]), max(x_lists[i]), 0.1)
-
-            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])
-            # ax[i].set_xlim([min(x_lists[i]), max(x_lists[i])])
-        m.plot_options(
-            fig,
-            ax[0],
-            image_width=15,
-            aspect_ratio=3,
-            xlabel="time (ns)",
-            ylabel="normalized power",
-        )
-        plt.tight_layout()
-        plt.show()
-
-    # fit spectra
-    x = spectr.T[0]  # in MHz
-    y_idl = spectr.T[1]
-    y_sig = spectr.T[2]
-    y_pmp = spectr.T[3]
-
-    idl = cast(list[float], m.gaussian_fit(np.array([x, y_idl]).T, False)[0])
-    sig = cast(list[float], m.gaussian_fit(np.array([x, y_sig]).T, False)[0])
-    pmp = cast(list[float], m.gaussian_fit(np.array([x, y_pmp]).T, False)[0])
-
-    fwhm_spec_idl = abs(2 * np.sqrt(2 * np.log(2)) * idl[2])
-    fwhm_spec_sig = abs(2 * np.sqrt(2 * np.log(2)) * sig[2])
-    fwhm_spec_pmp = abs(2 * np.sqrt(2 * np.log(2)) * pmp[2])
-
-    y_lists = y_idl, y_sig, y_pmp
-
-    fits = idl, sig, pmp
-    if display_results:
-        # plot spectra durations
-        fig, ax = plt.subplots(1, 3)
-        fwhms = fwhm_spec_idl, fwhm_spec_sig, fwhm_spec_pmp
-        titles = "idler (Red1)", "signal (Red2)", "pump (Blue)"
-        colors = "red", "green", "blue"
-        for i in range(len(ax)):
-            m.plot_options(
-                fig, ax[i], xlabel="detuning (MHz)", image_width=15, aspect_ratio=3
-            )
-            ax[i].plot(x, y_lists[i], ".", color=colors[i])
-            ax[i].set_title(titles[i], color=colors[i])
-
-            x_fit = np.arange(min(x), max(x), 0.1)
-
-            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}MHz", ha="center", color=colors[i])
-            ax[i].set_xlim([-2 * fwhms[i], 2 * fwhms[i]])
-        m.plot_options(
-            fig,
-            ax[0],
-            image_width=15,
-            aspect_ratio=3,
-            xlabel="time (ns)",
-            ylabel="normalized power",
-        )
-        plt.tight_layout()
-        plt.show()
-
-    return {
-        "fwhm_idl_MHz": fwhm_spec_idl,
-        "fwhm_sig_MHz": fwhm_spec_sig,
-        "fwhm_pmp_MHz": fwhm_spec_pmp,
-        "duration_idl_ns": fwhm_ns_idl,
-        "duration_sig_ns": fwhm_ns_sig,
-        "duration_pmp_ns": fwhm_ns_pmp,
-    }
-
-
 if __name__ == "__main__":
     if len(log.handlers) < 2:
         log.addHandler(logging.StreamHandler())