Repository: MannLabs/alphapept (language: python)

Issue:
Update develop

Enable specification of matching range
**Is your feature request related to a problem? Please describe.**
With biological samples we want to avoid matching identifications between files, where we might genuinely not have the protein in one of the samples (e.g. KO samples, IPs, fractionation, ...), but still be able to transfer IDs between exact biological replicates to boost the numbers. The same goes for peptide fractions, where you would not want to e.g. match between the 1st and 3rd SDB-RPS fraction, but only 1<->2 and 2<->3. Currently it's all or nothing.

**Describe the solution you'd like**
I personally like the solution in MQ where you only match neighboring fraction numbers very much. It could become even better if you offer two matching 'dimensions' so you can e.g. match same peptide fractions of neighboring biological samples, as well as neighboring peptide fractions of same biological samples. In the example below where it would be biological sample x peptide fraction and sample `*2x2*` would receive IDs from `_4 other raw files_`.

```
-------------------
| 1x1 |_1x2_| 1x3 |
-------------------
|_2x1_|*2x2*|_2x3_|
-------------------
| 3x1 |_3x2_| 3x3 |
-------------------
```



Discussion / hints:

Fix patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 442a1d45..9270337e 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 0.3.26
+current_version = 0.3.27
 commit = True
 tag = False
 parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)(?P<build>\d+))?
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 659dbdad..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# How to contribute
-
-Contributions are welcome, and they are greatly appreciated! Every
-little bit helps, and credit will always be given.
-
-You can contribute in many ways:
-
-## Report Bugs
-
-Report bugs at https://github.com/MannLabs/alphapept/issues.
-
-If you are reporting a bug, please include:
-
-* Your operating system name and version.
-* Any details about your local setup that might be helpful in troubleshooting.
-* Detailed steps to reproduce the bug.
-
-## Fix Bugs
-
-Look through the GitHub issues for bugs. Anything tagged with "bug"
-and "help wanted" is open to whoever wants to implement it.
-
-## Implement Features
-
-Look through the GitHub issues for features. Anything tagged with "enhancement"
-and "help wanted" is open to whoever wants to implement it.
-
-## Write Documentation
-
-AlphaPept could always use more documentation, whether as part of the
-official AlphaPept documentation, in docstrings, or even on the web in blog posts,
-articles, and such.
-
-## Submit Feedback
-
-The best way to send feedback is to file an issue at https://github.com/MannLabs/alphapept/issues.
-
-If you are proposing a feature:
-
-* Explain in detail how it would work.
-* Keep the scope as narrow as possible, to make it easier to implement.
-* Remember that this is a volunteer-driven project, and that contributions
-  are welcome :)
-
-## Get Started!
-
-Ready to contribute? Here's how to set up `alphapept` for local development.
-
-1. Fork the `alphapept` repo on GitHub.
-2. Clone your fork locally:
-
-    $ git clone git@github.com:your_name_here/alphapept.git
-
-3. Follow the installation instructions in the readme to install a alphapept environment.
-
-4. Create a branch for local development::
-
-    $ git checkout -b name-of-your-bugfix-or-feature
-
-   Now you can make your changes locally.
-
-5. See below in the Notes for Programmers about how to use the nbdev environment.
-
-6. Commit your changes and push your branch to GitHub::
-
-    $ git add .
-    $ git commit -m "Your detailed description of your changes."
-    $ git push origin name-of-your-bugfix-or-feature
-
-7. Submit a pull request through the GitHub website.
-
-## Notes for Programmers
-
-### Literal Programming
-A key feature is the use of [nbdev](https://github.com/fastai/nbdev). We like to keep the entrance barrier low to attract new coders to contribute to the AlphaPept package. For this, we see nbedv as an ideal tool to document and modify code. To install nbdev use `pip install nbdev` and then install the git hooks in the folder where your GitHub repository is cloned (`nbdev_install_git_hooks`). The key commands for nbdev are:
-* `nbdev_build_lib`: build the library from the notebooks
-* `nbdev_test_nbs`: test all notebooks
-* `nbdev_clean_nbs`: clean the notebooks (strip from unnecessary metadata)
-
-
-### Testing
-
-In order to make AlphaPept a sustainable package, it is imperative that all functions have tests. This is not only to ensure the proper execution of the function but also for the long run when wanting to keep up-to-date with package updates. For tracking package updates, we rely on [dependabot](https://dependabot.com). For continuous integration, we use GitHub Actions.
-
-##### Unit tests
-
-Within the nbdev notebooks, we try to write small tests for each function. They should be lightweight so that running (and hence testing) the notebooks should not take too long. Each test is defined as `test_function` and then that function is called with `test_function()` in a cell and not exported to the codebase. If, at a later stage, we want to switch to pytest or abandon nbdev this would alleviate reorganizing the code to a test folder.
-
-##### Integration and performance tests (in progress)
-
-A complete run of a proteomics pipeline is computationally intensive and requires large files that exceed the typical workflow in a GitHub Actions runner. For this, we use self-hosted [runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners). Likewise, we aim to benchmark every release of AlphaPept and automatically run each release on a number of test sets.
-
-### Numba - first
-
-We heavily rely on the [Numba](http://numba.pydata.org) package for efficient computation. As writing classes in numba with `@jitclass` requires type specification, in most cases, we prefer functional programming over
-Object-oriented programming for simplicity. Here, adding the decorator `@njit` is mostly enough. Numba also allows alleviates transfering code to GPU.
-
-### Parallelization strategies
-
-Python has some powerful parallelization tools, such as the `multiprocessing` library. `Numba` allows loops to be executed in parallel when flagging with `prange`, which is, from a syntactic point of view, a very elegant solution to parallel processing. It comes with the downside that we cannot easily track the progress of parallel functions that use `prange`. We, therefore, chunk data where possible to be able to have a progress bar. Additionally, currently, it is not possible to set the number of cores that should be used.
-
-From a data analysis point of view, there are several considerations to be taken into account: When processing multiple files in parallel, it would be suitable to launch several processes in parallel, where the multiprocessing library would come in handy. On the other hand, when only wanting to investigate a single file, having the individual functions parallelized would be beneficial.
-
-Hence, the core idea is to write fast single-core functions and also have parallelized versions where applicable. When processing a large number of files, we are relying on the `multiprocessing` module.
-
-### Constants
-
-One good way to handle constants would be to use globals. However, numba is not able to use typed dictionaries/classes as globals. We therefore pass them as variables (such as the mass_dict), which in some cases leads to functions with a lot of arguments. Note that `numba` is not able to handle `kwargs` and `args` at this point.
-
-### Callbacks
-
-As AlphaPept is intended to be the backend of a tool with GUI, we ideally want to be able to get a progress bar out of the major functions. For this, we can pass a `callback`-argument to the major functions. If the argument is passed, it will return the current progress in the range from 0 to 1.
-
-### Version bumping
-
-We are using the python package [`bump2version`](https://github.com/c4urself/bump2version). You can use this to bump the version number. Currently specified is: `bump2version`: (`major`, `minor`, `patch`):
-
-* e.g.: `bump2version patch` for a patch
-
-
-### Data flow
-
-Having access to intermediate results is very beneficial if individual modules need to be optimized. To achieve this, we rely on *.hdf containers and save intermediate results here. For each file that gets processed, we create a *.hdf file with the same name and add the results of each processing step as groups in the container.
-
-
-## PR submission guidelines
-
-* Keep each PR focused. While it's more convenient, do not combine several unrelated fixes together. Create as many branches as needing to keep each PR focused.
-* Do not mix style changes/fixes with "functional" changes. It's very difficult to review such PRs and it most likely get rejected.
-* Do not add/remove vertical whitespace. Preserve the original style of the file you edit as much as you can.
-* Do not turn an already submitted PR into your development playground. If after you submitted PR, you discovered that more work is needed - close the PR, do the required work and then submit a new PR. Otherwise each of your commits requires attention from maintainers of the project.
-* If, however, you submitted a PR and received a request for changes, you should proceed with commits inside that PR, so that the maintainer can see the incremental fixes and won't need to review the whole PR again. In the exception case where you realize it'll take many many commits to complete the requests, then it's probably best to close the PR, do the work and then submit it again. Use common sense where you'd choose one way over another.
diff --git a/README.md b/README.md
index 98bffeae..2cf2ca00 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,7 @@
 ![Windows Installer](https://github.com/MannLabs/alphapept/workflows/Windows%20Installer/badge.svg)
 
 # AlphaPept
-<img src="nbs/images/alphapept_logo.png">
-
+![](https://i.imgur.com/xkFtDff.jpg)
  > A modular, python-based framework to analyze mass spectrometry data. Powered by nbdev. Supercharged with numba.
 
 
@@ -18,7 +17,7 @@
 The documentation is automatically built based on the jupyter notebooks (nbs/index.ipynb) and can be found [here](https://mannlabs.github.io/alphapept/):
 
 ## Windows Quickstart
-<img src="nbs/images/alphapept_screen.png" width="480," style="max-width: 480,px">
+![](https://i.imgur.com/UO64YPx.jpg)
 
 1. Download the latest installer [here](https://github.com/MannLabs/alphapept/releases/latest), install and click the shortcut on the desktop. A browser window with the AlphaPept interface should open. In case Windows Firewall asks for network access for AlphaPept, please allow.
 2. In the `New Experiment` select a folder with raw files and FASTA files.
@@ -113,7 +112,7 @@ AlphaPept is meant to be a framework to implement and test new ideas quickly but
 
 For the [windows installation](https://github.com/MannLabs/alphapept/releases/latest), simply click on the shortcut after installation. The windows installation also installs the command-line tool so that you can call alphapept via `alphapept` in the command line.
 
-<img src="nbs/images/alphapept_cmd.png" width="480," style="max-width: 480,px">
+![](https://i.imgur.com/SQikLHQ.jpg)
 
 ### Python Package
 
@@ -132,7 +131,7 @@ get_frag_dict(parse(peptide), constants.mass_dict)
 
 
     {'b1': 98.06004032687,
-     'b2': 227.10263342686997,
+     'b2': 227.10263342687,
      'b3': 324.15539728686997,
      'y1': 120.06551965033,
      'y2': 217.11828351033,
@@ -150,23 +149,9 @@ To launch the command line interface use:
 This allows us to select different modules. To start the GUI use:
 * `alphapept gui`
 
-Likewise, to start the watcher use:
-* `alphapept watcher`
-> AlphaPept has a watcher module that continuously monitors a target folder and automatically performs file conversion and feature finding on new files.
-
-To run a workflow, use:* `alphapept workflow your_own_workflow.yaml`An example workflow is easily generated by running the GUI once and saving the settings which an be modified on a per-project basis.
-
-## Analyzing an experiment
-This describes the minimal steps to analyze an experiment.
-
-### GUI
-
-1. Open the GUI. Drag and drop experimental files and at least one fasta in the `Experiment` tab. 
-2. Default settings are loaded and can be changed or saved in the `Settings` tab
-3. Navigate to the Run panel and click `Start`
-
-### Investigating the result files
-The experimental results will be stored in the corresponding *.hdf-files.
+To run a workflow, use:
+* `alphapept workflow your_own_workflow.yaml`
+An example workflow is easily generated by running the GUI once and saving the settings which an be modified on a per-project basis.
 
 ### CMD / Python
 1. Create a settings-file. This can be done by changing the `default_settings.yaml` in the repository or using the GUI.
@@ -180,6 +165,18 @@ settings = load_settings('new_settings.yaml')
 r = alphapept.interface.run_complete_workflow(settings)
 ```
 
+## Dockerization
+
+We provide a dockerfile to show how to run AlphaPept in a containerized environment. This allows building powerful pipelines e.g. with AWS Batch. The dockerfile will launch the browser-based GUI. 
+
+1. Install docker
+2. Build the dockerfile with `docker build -t docker-alphapept:latest .` This took approximately ~10 minutes on a 2019 MacBookPro)
+3. Run the container `docker run -p 8501:8501 -v /Users/mstrauss/Desktop/docker:/home/alphapept/ docker-alphapept:latest alphapept gui`
+
+Notes: 
+`-p 8501:8501` forwards the streamlit port to the same port on your machine. You can access AlphaPept via `localhost:8501`
+`-v /Users/mstrauss/Desktop/docker:/shared_folder/` mounts the volume to your docker container. E.g., in this case, the local `docker`-folder on the Desktop can be accessed via `/shared_folder/`.
+
 ## Notebooks
 
 Within the notebooks, we try to cover most aspects of a proteomics workflow:
diff --git a/alphapept/__init__.py b/alphapept/__init__.py
index 90c13ab4..5c0d6020 100644
--- a/alphapept/__init__.py
+++ b/alphapept/__init__.py
@@ -1,1 +1,1 @@
-__version__ = "0.3.26"
+__version__ = "0.3.27"
diff --git a/alphapept/__version__.py b/alphapept/__version__.py
index c219f45f..51f8b8b9 100644
--- a/alphapept/__version__.py
+++ b/alphapept/__version__.py
@@ -33,7 +33,7 @@
 AUTHOR_EMAIL = "straussmaximilian@gmail.com"
 COPYRIGHT = "Mann Labs"
 BRANCH = "master"
-VERSION_NO = "0.3.26"
+VERSION_NO = "0.3.27"
 MIN_PYTHON = "3.6"
 MAX_PYTHON = "4"
 AUDIENCE = "Developers"
diff --git a/alphapept/_nbdev.py b/alphapept/_nbdev.py
index a3111b37..f97ddeaf 100644
--- a/alphapept/_nbdev.py
+++ b/alphapept/_nbdev.py
@@ -132,18 +132,13 @@
          "replace_infs": "04_feature_finding.ipynb",
          "map_ms2": "04_feature_finding.ipynb",
          "compare_frags": "05_search.ipynb",
-         "compare_frags_python": "05_search.ipynb",
          "ppm_to_dalton": "05_search.ipynb",
          "get_idxs": "05_search.ipynb",
-         "compare_spectrum": "05_search.ipynb",
-         "compare_spectrum_parallel_": "05_search.ipynb",
          "compare_spectrum_parallel": "05_search.ipynb",
          "query_data_to_features": "05_search.ipynb",
          "get_psms": "05_search.ipynb",
          "frag_delta": "05_search.ipynb",
          "intensity_fraction": "05_search.ipynb",
-         "intensity_product": "05_search.ipynb",
-         "b_y_hits": "05_search.ipynb",
          "add_column": "05_search.ipynb",
          "remove_column": "05_search.ipynb",
          "get_hits": "05_search.ipynb",
@@ -283,7 +278,8 @@
          "compile_function": "13_performance.ipynb",
          "__copy_func": "13_performance.ipynb",
          "DYNAMIC_COMPILATION_ENABLED": "13_performance.ipynb",
-         "performance_function": "13_performance.ipynb"}
+         "performance_function": "13_performance.ipynb",
+         "sample_function": "contributing.ipynb"}
 
 modules = ["settings.py",
            "chem.py",
@@ -299,7 +295,8 @@
            "interface.py",
            "speed.py",
            "export.py",
-           "performance.py"]
+           "performance.py",
+           "contributing.py"]
 
 doc_url = "https://mannlabs.github.io/alphapept/"
 
diff --git a/alphapept/contributing.py b/alphapept/contributing.py
new file mode 100644
index 00000000..35ae33ac
--- /dev/null
+++ b/alphapept/contributing.py
@@ -0,0 +1,21 @@
+# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/contributing.ipynb (unless otherwise specified).
+
+__all__ = ['sample_function']
+
+# Cell
+def sample_function(a_string: str, a_int: int, a_flag: bool) -> str:
+    """
+    Sample function that performs a sample calculation.
+
+    Args:
+        a_string: A string.
+        a_int: An integer.
+        a_flag: A flag.
+
+    Returns:
+        A string based on the input.
+
+    Raises:
+        ValueError: An error occured when the integer is negative.
+
+    """
\ No newline at end of file
diff --git a/alphapept/default_settings.yaml b/alphapept/default_settings.yaml
index 650e966a..a8a38289 100644
--- a/alphapept/default_settings.yaml
+++ b/alphapept/default_settings.yaml
@@ -15,6 +15,7 @@ experiment:
   shortnames: []
   file_paths: []
   fractions: []
+  matching_groups: []
   database_path: null
   fasta_paths: []
 raw:
diff --git a/alphapept/feature_finding.py b/alphapept/feature_finding.py
index 81082766..25c7f077 100644
--- a/alphapept/feature_finding.py
+++ b/alphapept/feature_finding.py
@@ -442,7 +442,7 @@ def hill_stats(idx, hill_range, hill_ptrs, hill_data, int_data, mass_data, rt_,
     mz_ = mass_data[idx_]
 
     int_sum = np.sum(int_)
-    int_area = np.trapz(rt_[rt_idx[idx_]], int_) #Area
+    int_area = np.abs(np.trapz(rt_[rt_idx[idx_]], int_)) #Area
 
     rt_min = rt_[rt_idx[idx_]].min()
     rt_max = rt_[rt_idx[idx_]].max()
@@ -834,7 +834,7 @@ def truncate(array, intensity_profile, seedpos, iso_split_level):
 
         array = array[minpos:maxpos+1]
 
-    return array, intensity_profile
+    return array
 
 # Cell
 from .chem import mass_to_dist
@@ -935,6 +935,7 @@ def isolate_isotope_pattern(pre_pattern, hill_ptrs, hill_data, int_data, scan_id
     longest_trace = 0
     champion_trace = None
     champion_charge = 0
+    champion_intensity = 0
 
     # Sort patterns by mass
 
@@ -950,22 +951,23 @@ def isolate_isotope_pattern(pre_pattern, hill_ptrs, hill_data, int_data, scan_id
         trails = get_trails(seed, sorted_pattern, stats, charge_range, iso_mass_range, sortindex_, hill_ptrs, hill_data, int_data, scan_idx, cc_cutoff)
 
         for index, trail in enumerate(trails):
-            if len(trail) > longest_trace:  # Needs to be longer than the current champion
-
+            if len(trail) >= longest_trace:  # Needs to be longer than the current champion
                 arr = int_list_to_array(trail)
                 intensity_profile = stats[arr][:,2]
                 seedpos = np.nonzero(arr==seed_global)[0][0]
 
                 # truncate around the seed...
-                arr, intensity_profile = truncate(arr, intensity_profile, seedpos, iso_split_level)
+                arr = truncate(arr, intensity_profile, seedpos, iso_split_level)
+                intensity_profile = stats[arr][:,2]
 
                 # Remove lower masses:
                 # Take the index of the maximum and remove all masses on the left side
                 if charge_range[index] * stats[seed_global, 0] < 1000:
                     maxpos = np.argmax(intensity_profile)
                     arr = arr[maxpos:]
+                    intensity_profile = stats[arr][:,2]
 
-                if len(arr) > longest_trace:
+                if (len(arr) > longest_trace) | ((len(arr) == longest_trace) & (intensity_profile.sum() > champion_intensity)):
                     # Averagine check
                     cc = check_averagine(stats, arr, charge_range[index], averagine_aa, isotopes)
                     if cc > 0.6:
@@ -973,6 +975,7 @@ def isolate_isotope_pattern(pre_pattern, hill_ptrs, hill_data, int_data, scan_id
                         champion_trace = arr
                         champion_charge = charge_range[index]
                         longest_trace = len(arr)
+                        champion_intensity = intensity_profile.sum()
 
     return champion_trace, champion_charge
 
@@ -1055,8 +1058,6 @@ def report_(idx, isotope_charges, isotope_patterns, iso_idx, stats, sortindex_,
     rt_start = isotope_data[int_max_idx, 4] # This is the start of the most abundant trace
     rt_end = isotope_data[int_max_idx, 5]
 
-    int_sum = np.sum(isotope_data[:, 2])
-
     # better measurement of the peak with interpolation
 
     rt_min_ = min(isotope_data[:, 4])
@@ -1126,7 +1127,7 @@ def report_(idx, isotope_charges, isotope_patterns, iso_idx, stats, sortindex_,
     rt_start = rt_range[rt_min_idx]
     rt_end = rt_range[rt_max_idx]
 
-    int_area = np.trapz(trace_sum[rt_min_idx:rt_max_idx], rt_range[rt_min_idx:rt_max_idx])
+    int_area = np.abs(np.trapz(trace_sum[rt_min_idx:rt_max_idx], rt_range[rt_min_idx:rt_max_idx]))
     int_sum = trace_sum.sum()
 
     results[idx,:] = np.array([mz, mz_std, mz_most_abundant, charge, rt_start, rt_apex, rt_end, fwhm, n_isotopes, mass, int_apex, int_area, int_sum])
diff --git a/alphapept/gui/experiment.py b/alphapept/gui/experiment.py
index fd76bb81..fd2a4158 100644
--- a/alphapept/gui/experiment.py
+++ b/alphapept/gui/experiment.py
@@ -2,10 +2,12 @@
 import os
 import pandas as pd
 import datetime
+import time
 from alphapept.paths import SETTINGS_TEMPLATE_PATH, QUEUE_PATH, DEFAULT_SETTINGS_PATH, FASTA_PATH
 from alphapept.settings import load_settings_as_template, save_settings, load_settings
 from alphapept.gui.utils import escape_markdown, files_in_folder
-import yaml 
+from st_aggrid import GridOptionsBuilder, AgGrid, GridUpdateMode, DataReturnMode, JsCode
+import yaml
 
 # Dict to match workflow
 WORKFLOW_DICT = {}
@@ -69,6 +71,8 @@ def widget_from_setting(recorder, key, group, element, override=None, indent=Fal
         recorder[key][element] = c2.multiselect(label = element, options = opts, default = value, help = help)
     elif _['type'] == 'combobox':
         recorder[key][element] = c2.selectbox(label = element, options = _['value'], index = _['value'].index(value),  help = help)
+    elif _['type'] == 'string':
+        recorder[key][element] = c2.text_input(label=element, default = value, help = help)
     else:
         st.write(f"Not understood {_}")
 
@@ -129,7 +133,21 @@ def customize_settings(recorder, uploaded_settings, loaded):
 
     return recorder
 
+def file_df_from_files(raw_files, file_folder):
+    """
+    From a list of files, get creation data and filesize and convert to a pandas dataframe.
+    """
+    raw_files.sort()
+    sizes = [round(os.stat(os.path.join(file_folder, _)).st_size/1024**3,2) for _ in raw_files]
+    created = [datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(file_folder, _))) for _ in raw_files]
+    file_df = pd.DataFrame(list(zip(range(1, len(raw_files)+1), raw_files, created, sizes)), columns =['#', 'Filename', 'Creation date', 'Size (GB)'])
+    file_df['Shortname'] = [os.path.splitext(_)[0] for _ in raw_files]
+
+    return file_df
+
+
 def experiment():
+    error = 0
     st.write("# New experiment")
     st.write('## Files')
 
@@ -157,8 +175,39 @@ def experiment():
                 st.warning('No raw files in folder.')
 
             else:
-                with st.beta_expander(f"Raw files ({len(raw_files)})"):
-                    st.table(pd.DataFrame(raw_files, columns=['File']))
+                exclude = st.multiselect('Exclude files', raw_files)
+                raw_files = [_ for _ in raw_files if _ not in exclude]
+
+                file_df = file_df_from_files(raw_files, file_folder)
+                file_df['Fraction'] = ''
+                file_df['Matching group'] = ''
+
+                gb = GridOptionsBuilder.from_dataframe(file_df)
+                gb.configure_default_column(groupable=True, value=True, enableRowGroup=True, aggFunc='sum', editable=True)
+                gb.configure_grid_options(domLayout='normal')
+                gridOptions = gb.build()
+
+                grid_response = AgGrid(
+                    file_df,
+                    height=300,
+                    width='100%',
+                    gridOptions=gridOptions,
+                    )
+
+                file_df_selected = grid_response['data']
+
+                with st.beta_expander('Additional info'):
+                    st.write('- Filename: Name of the file.'
+                            ' \n- Creation date of file.'
+                            ' \n- Size (GB): Size in GB of the file.'
+                            ' \n- Shortname: Unique shortname for each file.'
+                            ' \n- Fraction: Fraction of each file.'
+                            ' \n- Matching Group: Match-between-runs only among members of this group.')
+
+                shortnames = file_df_selected['Shortname'].values.tolist()
+                if len(shortnames) != len(set(shortnames)):
+                    st.warning(f'Warning: Shortnames are not unique.')
+                    error+=1
 
                 fasta_files_home_dir = files_in_folder(FASTA_PATH, '.fasta')
                 fasta_files_home_dir = [os.path.join(FASTA_PATH, _) for _ in fasta_files_home_dir]
@@ -168,24 +217,10 @@ def experiment():
                 selection = st.multiselect(f'Select FASTA files', options=fasta_files_home_dir, default = fasta_files)
                 recorder['experiment']['fasta_paths'] = selection
 
-                #TODO: Include databse files
-                #if len(fasta_files) > 0:
-                #    with st.beta_expander(f"FASTA files ({len(fasta_files)})"):
-                #        st.table(pd.DataFrame(fasta_files, columns=['File']))
-
-                with st.beta_expander("Fractions"):
-                    st.write('Fractions are currently not supported.')
-                    if False:
-                        st.write('Fractions can be automatically assigned based on the filename.',
-                                'Enter the string that preceds the fraction identifier and the string that comes after.')
-                        prec = st.text_input('Preceding')
-                        after = st.text_input('After')
-
-                        if st.button('Apply'):
-                            with st.spinner('Parsing folder'):
-                                files = pd.DataFrame(raw_files, columns=['File'])
-                                files['Fraction'] = files['File'].apply(lambda x: x.split(prec)[1].split(after)[0])
-                                st.table(files)
+                recorder['experiment']['shortnames'] = shortnames
+                recorder['experiment']['file_paths'] = [os.path.join(file_folder, _) for _ in file_df_selected['Filename'].values.tolist()]
+                recorder['experiment']['fractions'] = file_df_selected['Fraction'].values.tolist()
+                recorder['experiment']['matching_groups'] = file_df_selected['Matching group'].values.tolist()
 
                 st.write(f"## Workflow")
 
@@ -205,9 +240,11 @@ def experiment():
                     if uploaded_file is not None:
                         uploaded_settings =  yaml.load(uploaded_file, Loader=yaml.FullLoader)
                         loaded=True
-
-
+                        
                 recorder = customize_settings(recorder, uploaded_settings, loaded)
 
                 st.write("## Submit experiment")
-                submit_experiment(recorder)
+                if error != 0:
+                    st.warning('Some warnings exist. Please check settings.')
+                else:
+                    submit_experiment(recorder)
diff --git a/alphapept/gui/fasta.py b/alphapept/gui/fasta.py
index 1f498444..9b7c3b1c 100644
--- a/alphapept/gui/fasta.py
+++ b/alphapept/gui/fasta.py
@@ -9,6 +9,7 @@
 proteomes['Homo_sapiens_UP000005640'] = 'UP000005640'
 proteomes['Escherichia_coli_UP000000625'] = 'UP000000625'
 proteomes['Saccharomyces_cerevisiae_UP000002311'] = 'UP000002311'
+proteomes['Arabidopsis_thaliana_UP000006548'] = 'UP000006548'
 
 def fasta():
     st.write('# FASTA')
diff --git a/alphapept/gui/results.py b/alphapept/gui/results.py
index 638dac7a..0b75aa6a 100644
--- a/alphapept/gui/results.py
+++ b/alphapept/gui/results.py
@@ -1,5 +1,5 @@
 import streamlit as st
-from alphapept.gui.utils import files_in_folder, read_log
+from alphapept.gui.utils import files_in_folder, read_log, escape_markdown
 from alphapept.paths import PROCESSED_PATH
 from alphapept.settings import load_settings
 import os
@@ -79,6 +79,12 @@ def ion_plot(ms_file, options):
 
                 st.write(fig)
 
+def multiple_file_check(param):
+    if len(param) > 1:
+        return True
+    else:
+        st.info("Only one file present, can't perform operation.")
+        return False
 
 
 def correlation_heatmap(file, options):
@@ -90,15 +96,16 @@ def correlation_heatmap(file, options):
             if len(cols) == 0:
                 cols = df.columns
 
-            df = np.log(df[cols])
-            corr = df.corr()
+            if multiple_file_check(cols):
+                df = np.log(df[cols])
+                corr = df.corr()
 
-            fig = make_subplots(rows=1, cols=1)
-            fig.add_trace(trace = go.Heatmap(z=corr.values,
-                              x=corr.index.values,
-                              y=corr.columns.values, colorscale='Greys'))
-            fig.update_layout(height=600, width=600)
-            st.write(fig)
+                fig = make_subplots(rows=1, cols=1)
+                fig.add_trace(trace = go.Heatmap(z=corr.values,
+                                  x=corr.index.values,
+                                  y=corr.columns.values, colorscale='Greys'))
+                fig.update_layout(height=600, width=600)
+                st.write(fig)
 
 def pca_plot(file, options):
     if '/protein_table' in options:
@@ -109,15 +116,16 @@ def pca_plot(file, options):
             if len(cols) == 0:
                 cols = df.columns
 
-            pca = PCA(n_components=2)
-            components = pca.fit_transform(df[cols].fillna(0).T)
+            if multiple_file_check(cols):
+                pca = PCA(n_components=2)
+                components = pca.fit_transform(df[cols].fillna(0).T)
 
-            plot_df = pd.DataFrame(components, columns = ['Component 1', 'Component 2'])
-            plot_df['Filename'] = cols
-            fig = px.scatter(plot_df, x='Component 1', y='Component 2', hover_data=['Filename'], title='PCA')
-            fig.update_layout(height=600, width=600)
-            fig.update_traces(marker=dict(color='#18212b'))
-            st.write(fig)
+                plot_df = pd.DataFrame(components, columns = ['Component 1', 'Component 2'])
+                plot_df['Filename'] = cols
+                fig = px.scatter(plot_df, x='Component 1', y='Component 2', hover_data=['Filename'], title='PCA')
+                fig.update_layout(height=600, width=600)
+                fig.update_traces(marker=dict(color='#18212b'))
+                st.write(fig)
 
 
 
@@ -129,60 +137,62 @@ def volcano_plot(file, options):
             df_log = np.log(df.copy())
             col1, col2 = st.beta_columns(2)
 
-            group_1 = col1.multiselect('Group1', df.columns)
-            group_2 = col2.multiselect('Group2', df.columns)
-
-            show_proteins = st.multiselect('Highlight proteins', df.index)
-
-            if (len(group_1) > 0) and (len(group_2) > 0):
-
-
-                with st.spinner('Creating plot..'):
-                    test = stats.ttest_ind(df_log[group_1].values, df_log[group_2].values, nan_policy='omit', axis=1)
-
-                    t_diff = np.nanmean(df_log[group_1].values, axis = 1) - np.nanmean(df_log[group_2].values, axis = 1)
-                    plot_df = pd.DataFrame()
-
-                    plot_df['t_test_diff'] = t_diff
-                    plot_df['-log(pvalue)'] = -np.log(test.pvalue.data)
-                    plot_df['id'] = df.index
-                    plot_df.index = df.index
-
-                    fig = make_subplots()
-
-                    fig.add_trace(
-                        go.Scatter(
-                                    x= plot_df['t_test_diff'],
-                                    y=plot_df['-log(pvalue)'],
-                                    hovertemplate ='<b>%{text}</b>' +
-                                    '<br>t_test diff: %{y:.3f}'+
-                                    '<br>-log(pvalue): %{x:.3f}',
-                                    text = plot_df.index,
-                                    opacity=0.8,
-                                    mode='markers',
-                                    marker = dict(color='#3dc5ef')))
-
-                    if len(show_proteins)> 0:
-                        fig.add_trace(go.Scatter(
-                            x=plot_df.loc[show_proteins]['t_test_diff'],
-                            y=plot_df.loc[show_proteins]['-log(pvalue)'],
-                            hovertemplate ='<b>%{text}</b>' +
-                            '<br>t_test diff: %{y:.3f}'+
-                            '<br>-log(pvalue): %{x:.3f}',
-                            text = show_proteins,
-                            mode="markers+text",
-                            textposition="top center",
-                            marker_color='#18212b',
-                            textfont=dict(
-                                family="Courier New, monospace",
-                                size=16,
-                                color="#18212b"
+            if multiple_file_check(df.columns):
+
+                group_1 = col1.multiselect('Group1', df.columns)
+                group_2 = col2.multiselect('Group2', df.columns)
+
+                show_proteins = st.multiselect('Highlight proteins', df.index)
+
+                if (len(group_1) > 0) and (len(group_2) > 0):
+
+
+                    with st.spinner('Creating plot..'):
+                        test = stats.ttest_ind(df_log[group_1].values, df_log[group_2].values, nan_policy='omit', axis=1)
+
+                        t_diff = np.nanmean(df_log[group_1].values, axis = 1) - np.nanmean(df_log[group_2].values, axis = 1)
+                        plot_df = pd.DataFrame()
+
+                        plot_df['t_test_diff'] = t_diff
+                        plot_df['-log(pvalue)'] = -np.log(test.pvalue.data)
+                        plot_df['id'] = df.index
+                        plot_df.index = df.index
+
+                        fig = make_subplots()
+
+                        fig.add_trace(
+                            go.Scatter(
+                                        x= plot_df['t_test_diff'],
+                                        y=plot_df['-log(pvalue)'],
+                                        hovertemplate ='<b>%{text}</b>' +
+                                        '<br>t_test diff: %{y:.3f}'+
+                                        '<br>-log(pvalue): %{x:.3f}',
+                                        text = plot_df.index,
+                                        opacity=0.8,
+                                        mode='markers',
+                                        marker = dict(color='#3dc5ef')))
+
+                        if len(show_proteins)> 0:
+                            fig.add_trace(go.Scatter(
+                                x=plot_df.loc[show_proteins]['t_test_diff'],
+                                y=plot_df.loc[show_proteins]['-log(pvalue)'],
+                                hovertemplate ='<b>%{text}</b>' +
+                                '<br>t_test diff: %{y:.3f}'+
+                                '<br>-log(pvalue): %{x:.3f}',
+                                text = show_proteins,
+                                mode="markers+text",
+                                textposition="top center",
+                                marker_color='#18212b',
+                                textfont=dict(
+                                    family="Courier New, monospace",
+                                    size=16,
+                                    color="#18212b"
+                                    )
                                 )
-                            )
-                            )
-                    fig.update_layout(height=600, width=600)
-                    fig.update_layout(showlegend=False)
-                    st.write(fig)
+                                )
+                        fig.update_layout(height=600, width=600)
+                        fig.update_layout(showlegend=False)
+                        st.write(fig)
 
 def scatter_plot(file, options):
     if '/protein_table' in options:
@@ -194,19 +204,21 @@ def scatter_plot(file, options):
 
             all_cols = df.columns
 
-            group_1 = col1.selectbox('Group1', df.columns)
-            group_2 = col2.selectbox('Group2', df.columns)
+            if multiple_file_check(all_cols):
+                group_1 = col1.selectbox('Group1', all_cols)
+                group_2 = col2.selectbox('Group2', all_cols)
 
-            with st.spinner('Creating plot..'):
-                df_log['id'] = df_log.index
-                fig = px.scatter(df_log, x=group_1, y=group_2, hover_data=['id'], title='Scatterplot', opacity=0.2, trendline="ols")
-                fig.update_layout(height=600, width=600)
-                fig.update_traces(marker=dict(color='#18212b'))
+                with st.spinner('Creating plot..'):
+                    df_log['id'] = df_log.index
+                    fig = px.scatter(df_log, x=group_1, y=group_2, hover_data=['id'], title='Scatterplot', opacity=0.2, trendline="ols")
+                    fig.update_layout(height=600, width=600)
+                    fig.update_traces(marker=dict(color='#18212b'))
 
-                results = px.get_trendline_results(fig)
+                    results = px.get_trendline_results(fig)
+
+                    st.write(fig)
+                    st.code(results.px_fit_results.iloc[0].summary())
 
-                st.write(fig)
-                st.code(results.px_fit_results.iloc[0].summary())
 
 def parse_file_and_display(file):
     """
@@ -268,7 +280,7 @@ def plot_summary(results_yaml, selection):
     median_peptides = int(data_df['sequence (protein_fdr, n unique)'].median())
     median_protein_groups = int(data_df['protein_group (protein_fdr, n unique)'].median())
 
-    st.write(f"### {selection}")
+    st.write(f"### {escape_markdown(selection)}")
     st.write(f"### {median_features:,} features | {median_peptides:,} peptides | {median_protein_groups:,}  protein groups (median)")
 
     fig = make_subplots(rows=1, cols=3, subplot_titles=("Features", "Peptides", "Protein Groups", ))
diff --git a/alphapept/interface.py b/alphapept/interface.py
index c10a463f..28548857 100644
--- a/alphapept/interface.py
+++ b/alphapept/interface.py
@@ -1134,7 +1134,7 @@ def cli_gui():
     theme.append("--theme.backgroundColor=#FFFFFF")
     theme.append("--theme.secondaryBackgroundColor=#f0f2f6")
     theme.append("--theme.textColor=#262730")
-    theme.append("--theme.font='sans serif'")
+    theme.append("--theme.font=sans serif")
     theme.append("--theme.primaryColor=#18212b")
 
     args = ["streamlit", "run", file_path, "--global.developmentMode=false", "--server.port=8501", "--browser.gatherUsageStats=False"]
diff --git a/alphapept/recalibration.py b/alphapept/recalibration.py
index 31ba40f0..3b2f31df 100644
--- a/alphapept/recalibration.py
+++ b/alphapept/recalibration.py
@@ -47,10 +47,9 @@ def get_calibration(df, features, outlier_std = 3, calib_n_neighbors = 100, cali
 
         # Remove outliers for calibration
         o_mass_std = np.abs(df['o_mass_ppm'].std())
-        o_mass_mean = df['o_mass_ppm'].mean()
+        o_mass_median = df['o_mass_ppm'].median()
 
-        #df_sub = df[~df['feature_idx'].isna()].copy() #Remove unmatched features
-        df_sub = df.query('o_mass_ppm < @o_mass_mean+@outlier_std*@o_mass_std and o_mass_ppm > @o_mass_mean-@outlier_std*@o_mass_std').copy()
+        df_sub = df.query('o_mass_ppm < @o_mass_median+@outlier_std*@o_mass_std and o_mass_ppm > @o_mass_median-@outlier_std*@o_mass_std').copy()
 
         tree_points = df_sub[cols].values
 
@@ -172,7 +171,6 @@ def calibrate_hdf(to_process, callback = None, parallel=False):
 
 # Cell
 
-
 import alphapept.io
 import pandas as pd
 import numpy as np
diff --git a/alphapept/search.py b/alphapept/search.py
index b5406316..0e018e8f 100644
--- a/alphapept/search.py
+++ b/alphapept/search.py
@@ -1,10 +1,9 @@
 # AUTOGENERATED! DO NOT EDIT! File to edit: nbs/05_search.ipynb (unless otherwise specified).
 
-__all__ = ['compare_frags', 'compare_frags_python', 'ppm_to_dalton', 'get_idxs', 'compare_spectrum',
-           'compare_spectrum_parallel_', 'compare_spectrum_parallel', 'query_data_to_features', 'get_psms',
-           'frag_delta', 'intensity_fraction', 'intensity_product', 'b_y_hits', 'add_column', 'remove_column',
-           'get_hits', 'score', 'get_sequences', 'get_score_columns', 'plot_hit', 'plot_psms', 'perform_search',
-           'store_hdf', 'search_db', 'search_fasta_block', 'filter_top_n', 'search_parallel', 'mass_dict']
+__all__ = ['compare_frags', 'ppm_to_dalton', 'get_idxs', 'compare_spectrum_parallel', 'query_data_to_features',
+           'get_psms', 'frag_delta', 'intensity_fraction', 'add_column', 'remove_column', 'get_hits', 'score',
+           'get_sequences', 'get_score_columns', 'plot_hit', 'plot_psms', 'perform_search', 'store_hdf', 'search_db',
+           'search_fasta_block', 'filter_top_n', 'search_parallel', 'mass_dict']
 
 # Cell
 import logging
@@ -12,7 +11,7 @@
 import numpy as np
 
 @njit
-def compare_frags(query_frag, db_frag, mtol, ppm=False):
+def compare_frags(query_frag, db_frag, frag_tol, ppm=False):
     """
     Compare query and database frags and find hits
     """
@@ -31,7 +30,7 @@ def compare_frags(query_frag, db_frag, mtol, ppm=False):
         else:
             mass_difference = delta_mass
 
-        if abs(mass_difference) <= mtol:
+        if abs(mass_difference) <= frag_tol:
             hits[d] = q + 1  # Save query position +1 (zero-indexing)
             d += 1
             q += 1  # Only one query for each db element
@@ -42,44 +41,6 @@ def compare_frags(query_frag, db_frag, mtol, ppm=False):
 
     return hits
 
-def compare_frags_python(query_frag, db_frag, mtol, ppm=False):
-    """
-    Compare query and database frags and find hits (python function for benchmarking)
-    """
-    q_max = len(query_frag)
-    d_max = len(db_frag)
-    hits = np.zeros(d_max, dtype=np.int16)
-    q, d = 0, 0  # q > query, d > database
-    while q < q_max and d < d_max:
-        mass1 = query_frag[q]
-        mass2 = db_frag[d]
-        delta_mass = mass1 - mass2
-
-        if ppm:
-            sum_mass = mass1 + mass2
-            mass_difference = 2 * delta_mass / sum_mass * 1e6
-        else:
-            mass_difference = delta_mass
-
-        if abs(mass_difference) <= mtol:
-            hits[d] = q + 1  # Save query position +1 (zero-indexing)
-            d += 1
-            q += 1  # Only one query for each db element
-        elif delta_mass < 0:
-            q += 1
-        elif delta_mass > 0:
-            d += 1
-
-    return hits
-
-
-from numba import cuda
-
-try:
-    import cupy
-except ModuleNotFoundError:
-    pass
-
 # Cell
 from numba import prange
 @njit
@@ -105,79 +66,8 @@ def get_idxs(db_masses, query_masses, prec_tol, ppm):
 
     return idxs_lower, idxs_higher
 
-@njit
-def compare_spectrum(query_idx, idxs_lower, idxs_higher, query_indices, query_frags, query_ints, db_indices, db_frags, best_hits, score, frag_tol, ppm):
-    """
-    Compares a spectrum and writes to best_his, score array
-    """
-    idx_low = idxs_lower[query_idx]
-    idx_high = idxs_higher[query_idx]
-
-    query_idx_start = query_indices[query_idx]
-    query_idx_end = query_indices[query_idx + 1]
-    query_frag = query_frags[query_idx_start:query_idx_end]
-    query_int = query_ints[query_idx_start:query_idx_end]
-
-    query_int_sum = np.sum(query_int)
-
-    matches = []
-
-    for db_idx in range(idx_low, idx_high):
-        db_idx_start = db_indices[db_idx]
-        db_idx_end = db_indices[db_idx + 1]
-        db_frag = db_frags[db_idx_start:db_idx_end]
-        hits = compare_frags(query_frag, db_frag, frag_tol, ppm)
-        n_hits = np.sum(hits>0)
-        if n_hits > 0:
-            matched_int = np.sum(query_int[hits[hits>0]-1]) / query_int_sum
-            matches.append((n_hits + matched_int, db_idx))
-
-    matches.sort()
-    matches = matches[::-1]
-
-    len_ = min((len(matches), best_hits.shape[1]))
-    best_hits[query_idx, 0:len_] = [_[1] for _ in matches[:len_]]
-    score[query_idx, 0:len_] = [_[0] for _ in matches[:len_]]
-
-
 import alphapept.performance
 
-#@alphapept.performance.performance_function(compilation_mode="numba-multithread")
-def compare_spectrum_parallel_(query_idx, query_masses, idxs_lower, idxs_higher, query_indices, query_frags, query_ints, db_indices, db_frags, best_hits, score, frag_tol, ppm):
-    """
-    Compares a spectrum and writes to best_his, score array
-    """
-
-    idx_low = idxs_lower[query_idx]
-    idx_high = idxs_higher[query_idx]
-
-    query_idx_start = query_indices[query_idx]
-    query_idx_end = query_indices[query_idx + 1]
-    query_frag = query_frags[query_idx_start:query_idx_end]
-    query_int = query_ints[query_idx_start:query_idx_end]
-
-    query_int_sum = query_int.sum()
-
-    matches = []
-
-    for db_idx in range(idx_low, idx_high):
-        db_idx_start = db_indices[db_idx]
-        db_idx_end = db_indices[db_idx + 1]
-        db_frag = db_frags[db_idx_start:db_idx_end]
-
-        hits = compare_frags(query_frag, db_frag, frag_tol, ppm)
-
-        n_hits = (hits>0).sum()
-        if n_hits > 0:
-            matched_int = (query_int[hits[hits>0]-1]).sum() / query_int_sum
-            matches.append((n_hits + matched_int, db_idx))
-
-    matches.sort()
-    matches = matches[::-1]
-
-    len_ = min((len(matches), best_hits.shape[1]))
-    best_hits[query_idx, 0:len_] = [_[1] for _ in matches[:len_]]
-    score[query_idx, 0:len_] = [_[0] for _ in matches[:len_]]
 
 @alphapept.performance.performance_function
 def compare_spectrum_parallel(query_idx, query_masses, idxs_lower, idxs_higher, query_indices, query_frags, query_ints, db_indices, db_frags, best_hits, score, frag_tol, ppm):
@@ -448,38 +338,6 @@ def intensity_fraction(query_int, hits):
 
     return i_frac
 
-# Cell
-@njit
-def intensity_product(query_int, hits, db_int=None):
-    """
-    Calculate the dot product of matched query intensity to db intensity
-    """
-
-    matched_query_int = query_int[hits[hits > 0] - 1]
-    if db_int is None:
-        matched_intensity = np.sum(matched_query_int)
-    else:
-        matched_db_int = db_int[hits > 0]
-        matched_intensity = np.sum(matched_query_int*matched_db_int)
-
-    return matched_intensity
-
-# Cell
-@njit
-def b_y_hits(frag_type, hits):
-    """
-    Count the number of b and y hits
-    hits usually start with b-ions > 0, then y-ions < 1
-    """
-    hits_index = hits > 0
-
-    hit_types = frag_type[hits_index]
-
-    b_hits = np.sum(hit_types > 0)
-    y_hits = np.sum(hit_types < 0)
-
-    return b_hits, y_hits
-
 # Cell
 from numpy.lib.recfunctions import append_fields, drop_fields
 
diff --git a/alphapept/settings_template.yaml b/alphapept/settings_template.yaml
index 2a47509d..484decd6 100644
--- a/alphapept/settings_template.yaml
+++ b/alphapept/settings_template.yaml
@@ -64,6 +64,11 @@ experiment:
     type: list
     default: []
     description: List of fractions for the raw files.
+  matching_groups:
+    type: list
+    default: []
+    description: List of macthing groups for the raw files. This only allows match-between-runs
+      of files within the same groups.
   database_path:
     type: path
     default: null
diff --git a/docs/_data/sidebars/home_sidebar.yml b/docs/_data/sidebars/home_sidebar.yml
index fb704afd..999e6995 100644
--- a/docs/_data/sidebars/home_sidebar.yml
+++ b/docs/_data/sidebars/home_sidebar.yml
@@ -54,6 +54,9 @@ entries:
     - output: web,pdf
       title: Performance
       url: performance.html
+    - output: web,pdf
+      title: How to contribute
+      url: contributing.html
     - output: web,pdf
       title: AlphaPept workflow and files
       url: file_formats.html
diff --git a/docs/contributing.html b/docs/contributing.html
new file mode 100644
index 00000000..f92efae4
--- /dev/null
+++ b/docs/contributing.html
@@ -0,0 +1,262 @@
+---
+
+title: How to contribute
+
+
+keywords: fastai
+sidebar: home_sidebar
+
+
+
+nb_path: "nbs/contributing.ipynb"
+---
+<!--
+
+#################################################
+### THIS FILE WAS AUTOGENERATED! DO NOT EDIT! ###
+#################################################
+# file to edit: nbs/contributing.ipynb
+# command to build the docs after a change: nbdev_build_docs
+
+-->
+
+<div class="container" id="notebook-container">
+        
+    {% raw %}
+    
+<div class="cell border-box-sizing code_cell rendered">
+
+</div>
+    {% endraw %}
+
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Report-Bugs">Report Bugs<a class="anchor-link" href="#Report-Bugs"> </a></h2><p>Report bugs at <a href="https://github.com/MannLabs/alphapept/issues">https://github.com/MannLabs/alphapept/issues</a>.</p>
+<p>If you are reporting a bug, please include:</p>
+<ul>
+<li>Your operating system name and version.</li>
+<li>Any details about your local setup that might be helpful in troubleshooting.</li>
+<li>Detailed steps to reproduce the bug.</li>
+</ul>
+<h2 id="Fix-Bugs">Fix Bugs<a class="anchor-link" href="#Fix-Bugs"> </a></h2><p>Look through the GitHub issues for bugs. Anything tagged with "bug"
+and "help wanted" is open to whoever wants to implement it.</p>
+<h2 id="Implement-Features">Implement Features<a class="anchor-link" href="#Implement-Features"> </a></h2><p>Look through the GitHub issues for features. Anything tagged with "enhancement"
+and "help wanted" is open to whoever wants to implement it.</p>
+<h2 id="Write-Documentation">Write Documentation<a class="anchor-link" href="#Write-Documentation"> </a></h2><p>AlphaPept could always use more documentation, whether as part of the
+official AlphaPept documentation, in docstrings, or even on the web in blog posts,
+articles, and such. See below for our docstring convention.</p>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Submit-Feedback">Submit Feedback<a class="anchor-link" href="#Submit-Feedback"> </a></h2><p>The best way to send feedback is to file an issue at <a href="https://github.com/MannLabs/alphapept/issues">https://github.com/MannLabs/alphapept/issues</a>.</p>
+<p>If you are proposing a feature:</p>
+<ul>
+<li>Explain in detail how it would work.</li>
+<li>Keep the scope as narrow as possible, to make it easier to implement.</li>
+<li>Remember that this is a volunteer-driven project, and that contributions
+are welcome :)</li>
+</ul>
+<h2 id="Get-Started!">Get Started!<a class="anchor-link" href="#Get-Started!"> </a></h2><p>Ready to contribute? Here's how to set up <code>alphapept</code> for local development.</p>
+<ol>
+<li>Fork the <code>alphapept</code> repo on GitHub.</li>
+<li><p>Clone your fork locally:</p>
+
+<pre><code> git clone git@github.com:your_name_here/alphapept.git</code></pre>
+</li>
+<li><p>Follow the installation instructions in the readme to install a alphapept environment.</p>
+</li>
+<li><p>Create a branch for local development::</p>
+
+<pre><code> git checkout -b name-of-your-bugfix-or-feature</code></pre>
+<p>Now you can make your changes locally.</p>
+</li>
+<li><p>See below in the Notes for Programmers about how to use the nbdev environment.</p>
+</li>
+<li><p>Commit your changes and push your branch to GitHub::</p>
+
+<pre><code> git add .
+ git commit -m "Your detailed description of your changes."
+ git push origin name-of-your-bugfix-or-feature</code></pre>
+</li>
+<li><p>Submit a pull request through the GitHub website.</p>
+</li>
+</ol>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="PR-submission-guidelines">PR submission guidelines<a class="anchor-link" href="#PR-submission-guidelines"> </a></h2><ul>
+<li>Keep each PR focused. While it's more convenient, do not combine several unrelated fixes together. Create as many branches as needed to keep each PR focused.</li>
+<li>Do not mix style changes/fixes with "functional" changes. It's very difficult to review such PRs, and it most likely gets rejected.</li>
+<li>Do not turn an already submitted PR into your development playground. If after you submitted PR, you discovered that more work is needed - close the PR, do the required work and then submit a new PR. Otherwise, each of your commits requires attention from maintainers of the project.</li>
+<li>If, however, you submitted a PR and received a request for changes, you should proceed with commits inside that PR, so that the maintainer can see the incremental fixes and won't need to review the whole PR again. In the exceptional case where you realize it'll take many many commits to complete the requests, then it's probably best to close the PR, do the work and then submit it again. Again, use common sense where you'd choose one way over another.</li>
+</ul>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Notes-for-Programmers">Notes for Programmers<a class="anchor-link" href="#Notes-for-Programmers"> </a></h2><h3 id="Literal-Programming">Literal Programming<a class="anchor-link" href="#Literal-Programming"> </a></h3><p>A key feature is the use of <a href="https://github.com/fastai/nbdev">nbdev</a>. We like to keep the entrance barrier low to attract new coders to contribute to the AlphaPept package. For this, we see nbedv as an ideal tool to document and modify code. To install nbdev use <code>pip install nbdev</code> and then install the git hooks in the folder where your GitHub repository is cloned (<code>nbdev_install_git_hooks</code>). The key commands for nbdev are:</p>
+<ul>
+<li><code>nbdev_build_lib</code>: build the library from the notebooks</li>
+<li><code>nbdev_test_nbs</code>: test all notebooks</li>
+<li><code>nbdev_clean_nbs</code>: clean the notebooks (strip from unnecessary metadata)</li>
+</ul>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h3 id="Docstring-convention">Docstring convention<a class="anchor-link" href="#Docstring-convention"> </a></h3><p>The docstring convention we adhere to is from the <a href="https://google.github.io/styleguide/pyguide.html">Google stylegyuide</a>.</p>
+<p>Below a sample docstring (Note the empty space after Args/Returns/Raises):</p>
+
+<pre><code>def sample_function(a_string: str, a_int: int, a_flag: bool) -&gt; str:
+    """
+    Sample function that performs a sample calculation.
+
+    Args:
+        a_string: A string.
+        a_int: An integer.
+        a_flag: A flag.
+
+    Returns:
+        A string based on the input.  
+
+    Raises:
+        ValueError: An error occured when the integer is negative.
+
+    """</code></pre>
+<blockquote><p>Note that type hints are highly recommended but not mandatory (see also <a href="https://www.python.org/dev/peps/pep-0484/">PEP484</a>). As the Legacy codebase had no type hints we are slowly moving towards more type hinting coverage.
+Note that adding additional elements to the docstring could break how the documentation is displayed. Below is the function from above but exported to show how it would appear in the documentation.</p>
+</blockquote>
+
+</div>
+</div>
+</div>
+    {% raw %}
+    
+<div class="cell border-box-sizing code_cell rendered">
+
+<div class="output_wrapper">
+<div class="output">
+
+<div class="output_area">
+
+
+<div class="output_markdown rendered_html output_subarea ">
+<h4 id="sample_function" class="doc_header"><code>sample_function</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/contributing.py#L6" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>sample_function</code>(<strong><code>a_string</code></strong>:<code>str</code>, <strong><code>a_int</code></strong>:<code>int</code>, <strong><code>a_flag</code></strong>:<code>bool</code>)</p>
+</blockquote>
+<p>Sample function that performs a sample calculation.</p>
+<p>Args:
+    a_string: A string.
+    a_int: An integer.
+    a_flag: A flag.</p>
+<p>Returns:
+    A string based on the input.</p>
+<p>Raises:
+    ValueError: An error occured when the integer is negative.</p>
+
+</div>
+
+</div>
+
+</div>
+</div>
+
+</div>
+    {% endraw %}
+
+    {% raw %}
+    
+<div class="cell border-box-sizing code_cell rendered">
+
+</div>
+    {% endraw %}
+
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Tests">Tests<a class="anchor-link" href="#Tests"> </a></h2><p>In order to make AlphaPept a sustainable package, it is imperative that all functions have tests. This is not only to ensure the proper execution of the function but also for the long run when wanting to keep up-to-date with package updates. For tracking package updates, we rely on <a href="https://dependabot.com">dependabot</a>. For continuous integration, we use GitHub Actions.</p>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h3 id="Unit-Tests">Unit Tests<a class="anchor-link" href="#Unit-Tests"> </a></h3><p>Within the nbdev notebooks, we try to write small tests for each function. They should be lightweight so that running (and hence testing) the notebooks should not take too long. You define your code in a cell with the <code>#export</code>-flag and you can write a text in the following cell. To prevent the cell from showing up in the documentation, you can add a <code>#hide</code>-flag. To be flexible and maybe migrate from the notebook tests to <code>pytest</code> we write the tests with leading <code>test_</code> and the function name and execute it in the cell.</p>
+<h4 id="Example">Example<a class="anchor-link" href="#Example"> </a></h4><p>Cell that defines the code:</p>
+
+<pre><code>#export
+
+def function_a():
+    return 5</code></pre>
+<p>Next cell with the test function and calling the test.</p>
+
+<pre><code>def test_function_a():
+    assert function_a() == 5
+
+test_function_a()</code></pre>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h3 id="GitHub-Actions">GitHub Actions<a class="anchor-link" href="#GitHub-Actions"> </a></h3><p>We use GitHub Actions as part of the continuous integration framework.</p>
+<p>Within the repository, the actions are defined as yml files in <code>./.github/workflows/</code>.</p>
+<p>Currently, the following actions are present:</p>
+<ul>
+<li>cla_assistant: Action that runs a check if a contributor has signed the CLA</li>
+<li>gpu_test: Helper to run GPU tests. This is not part of the automated pipeline</li>
+<li>main: Main CI/CD from nbdev. This installs the library and runs all notebooks</li>
+<li>performance_test: This action freezes the environment and runs multiple test cases specified in <code>test_ci.py</code>. Used to benchmark new versions.</li>
+<li>quick_test: This action runs the whole AlphaPept pipeline on small test files</li>
+<li>release: This action triggers the installer compilation and creates a release draft</li>
+</ul>
+<p>The <code>quick_test</code> and <code>performance_test</code> are hooked to the MongoDB, so results are reported here.</p>
+<p>Some actions are executed on each push (e.g. <code>quick_test</code>, others need to be triggered manually (e.g. <code>release</code>).</p>
+<h3 id="Adding-an-integration-test">Adding an integration test<a class="anchor-link" href="#Adding-an-integration-test"> </a></h3><p>The best way to add an integration test is by specifying a new test in <code>test_ci.py</code> and then adding a job to the <code>performance_test</code> action. <code>test_ci.py</code> contains a file dictionary with links to files. The idea here is as follows: On the local machine, there should be a <code>BASE_DIR</code> that contains all files used for running the test cases. If the files are not present, the script will download them with the URL from the file dictionary. To define a test case, one needs to initiate a <code>TestRun</code>-class and provide the files to be used. Running a test will always take the current <code>default_settings.yaml</code> and modifying the respective files. You can add subsequent analysis (e.g., such as calculating the false discovery FDR for a mixed species experiment by adding a function to the test class.</p>
+<p>To test an integration test locally, simply call the <code>test_ci.py</code> with the respective test (e.g. thermo_irt) like so: <code>python test_ci.py thermo_irt</code>.</p>
+<h3 id="Integrating-Actions">Integrating Actions<a class="anchor-link" href="#Integrating-Actions"> </a></h3><p>{% include note.html content='Unless you are a core developer, there should not be the need to add actions.' %}
+New actions can be added by creating a <code>.yml</code> file and placing it in the workflows folder.
+We can distinguish local actions, which run on our self-hosted <a href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners">runners</a>  and cloud actions that run on GitHub servers.</p>
+<p>The local runners are meant to run computationally intensive tasks such as the <code>performance_test</code> and the <code>quick_test</code>, whereas the cloud actions are meant for unit tests.
+{% include note.html content='If you fork the repository, you will not be able to execute tests on the local runners. They are restricted to the <code>MannLabs</code> repository. ' %}</p>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Test-Coverage">Test Coverage<a class="anchor-link" href="#Test-Coverage"> </a></h2><p>Ideally, we would like to track test coverage. However, this isn't easy when having numba-compiled functions. Any solutions to this are greatly appreciated.</p>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Linting">Linting<a class="anchor-link" href="#Linting"> </a></h2><p>It is planned to include a GitHub action that automatically runs a linter on the code.</p>
+
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h3 id="Callbacks">Callbacks<a class="anchor-link" href="#Callbacks"> </a></h3><p>As AlphaPept is intended to be the backend of a tool with GUI, we ideally want to be able to get a progress bar out of the major functions. For this, we can pass a <code>callback</code>-argument to the major functions. If the argument is passed, it will return the current progress in the range from 0 to 1.</p>
+<h3 id="Version-bumping">Version bumping<a class="anchor-link" href="#Version-bumping"> </a></h3><p>We are using the python package <code>bump2version</code>. You can use this to bump the version number (install via <code>pip install bump2version</code>). Currently specified is: <code>bump2version</code>: (<code>major</code>, <code>minor</code>, <code>patch</code>):</p>
+<ul>
+<li>e.g.: <code>bump2version patch</code> for a patch</li>
+</ul>
+
+</div>
+</div>
+</div>
+</div>
+ 
+
diff --git a/docs/feature_finding.html b/docs/feature_finding.html
index cb8206e2..ca62cf25 100644
--- a/docs/feature_finding.html
+++ b/docs/feature_finding.html
@@ -281,9 +281,7 @@ <h5 id="Foward-and-Backward-Search">Foward and Backward Search<a class="anchor-l
 <ul>
 <li>c-&gt; d</li>
 </ul>
-<p><img src="/images/undirected.png" alt="undirected"></p>
 <p>The resulting hill would have a branch to the c centroid, which is obviously intended. To circumvent this, we additionally perform the search backwards. (Note the functions <code>connect_centroids_forward</code> and <code>connect_centroids_backward</code>).  Now, as we only store, the best connection between two centroids  <code>d</code> will only be connected to <code>b</code>. To, therefore, remove branches, we remove all connections that are not double.</p>
-<p><img src="/images/directed.png" alt="directed"></p>
 <p>We define a wrapper function named <a href="/feature_finding.html#connect_centroids"><code>connect_centroids</code></a> that allows to search with a gaps ize and returns lists of indices of the connected centroids.</p>
 
 </div>
@@ -737,21 +735,6 @@ <h4 id="check_isotope_pattern" class="doc_header"><code>check_isotope_pattern</c
 </div>
 </div>
 
-<div class="output_wrapper">
-<div class="output">
-
-<div class="output_area">
-
-<div class="output_subarea output_stream output_stdout output_text">
-<pre>True
-False
-</pre>
-</div>
-</div>
-
-</div>
-</div>
-
 </div>
     {% endraw %}
 
diff --git a/docs/index.html b/docs/index.html
index 39b679c3..d4e07cf4 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -214,26 +214,17 @@ <h3 id="Using-as-a-tool">Using as a tool<a class="anchor-link" href="#Using-as-a
 <ul>
 <li><code>alphapept gui</code></li>
 </ul>
-<p>Likewise, to start the watcher use:</p>
+<p>To run a workflow, use:</p>
 <ul>
-<li><code>alphapept watcher</code><blockquote><p>AlphaPept has a watcher module that continuously monitors a target folder and automatically performs file conversion and feature finding on new files.</p>
-</blockquote>
-</li>
+<li><code>alphapept workflow your_own_workflow.yaml</code>
+An example workflow is easily generated by running the GUI once and saving the settings which an be modified on a per-project basis.</li>
 </ul>
-<p>To run a workflow, use:* <code>alphapept workflow your_own_workflow.yaml</code>An example workflow is easily generated by running the GUI once and saving the settings which an be modified on a per-project basis.</p>
 
 </div>
 </div>
 </div>
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Analyzing-an-experiment">Analyzing an experiment<a class="anchor-link" href="#Analyzing-an-experiment"> </a></h2><p>This describes the minimal steps to analyze an experiment.</p>
-<h3 id="GUI">GUI<a class="anchor-link" href="#GUI"> </a></h3><ol>
-<li>Open the GUI. Drag and drop experimental files and at least one fasta in the <code>Experiment</code> tab. </li>
-<li>Default settings are loaded and can be changed or saved in the <code>Settings</code> tab</li>
-<li>Navigate to the Run panel and click <code>Start</code></li>
-</ol>
-<h3 id="Investigating-the-result-files">Investigating the result files<a class="anchor-link" href="#Investigating-the-result-files"> </a></h3><p>The experimental results will be stored in the corresponding *.hdf-files.</p>
 <h3 id="CMD-/-Python">CMD / Python<a class="anchor-link" href="#CMD-/-Python"> </a></h3><ol>
 <li>Create a settings-file. This can be done by changing the <code>default_settings.yaml</code> in the repository or using the GUI.</li>
 <li>Run the analysis with the new settings file. <code>alphapept run new_settings.yaml</code></li>
@@ -245,6 +236,21 @@ <h3 id="CMD-/-Python">CMD / Python<a class="anchor-link" href="#CMD-/-Python"> <
 settings = load_settings('new_settings.yaml')
 r = alphapept.interface.run_complete_workflow(settings)</code></pre>
 
+</div>
+</div>
+</div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Dockerization">Dockerization<a class="anchor-link" href="#Dockerization"> </a></h2><p>We provide a dockerfile to show how to run AlphaPept in a containerized environment. This allows building powerful pipelines e.g. with AWS Batch. The dockerfile will launch the browser-based GUI.</p>
+<ol>
+<li>Install docker</li>
+<li>Build the dockerfile with <code>docker build -t docker-alphapept:latest .</code> This took approximately ~10 minutes on a 2019 MacBookPro)</li>
+<li>Run the container <code>docker run -p 8501:8501 -v /Users/mstrauss/Desktop/docker:/home/alphapept/ docker-alphapept:latest alphapept gui</code></li>
+</ol>
+<p>Notes: 
+<code>-p 8501:8501</code> forwards the streamlit port to the same port on your machine. You can access AlphaPept via <code>localhost:8501</code>
+<code>-v /Users/mstrauss/Desktop/docker:/shared_folder/</code> mounts the volume to your docker container. E.g., in this case, the local <code>docker</code>-folder on the Desktop can be accessed via <code>/shared_folder/</code>.</p>
+
 </div>
 </div>
 </div>
diff --git a/docs/io.html b/docs/io.html
index 536711ae..86af06fd 100644
--- a/docs/io.html
+++ b/docs/io.html
@@ -62,6 +62,7 @@ <h2 id="Reading-raw-input">Reading raw input<a class="anchor-link" href="#Readin
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
 <h3 id="Thermo">Thermo<a class="anchor-link" href="#Thermo"> </a></h3><p>To read Thermo files, AlphaPept uses the <code>pyrawfilereader</code> package, a Python implementation of the commonly used <code>rawfilereader</code> tool. By using the custom python version, Thermo files can be read without having to install <code>MSFileReader</code>.</p>
+<p>The user can pass an additional flag <code>use_profile_ms1</code>. This will then use the profile data which is not centroided already an peform centroiding. Note that this will lead to slightly different intensities, as the centroided data uses the apex and the centroid algorithm the summed intensity.</p>
 
 </div>
 </div>
diff --git a/docs/recalibration.html b/docs/recalibration.html
index ddb3b225..9c64cd31 100644
--- a/docs/recalibration.html
+++ b/docs/recalibration.html
@@ -31,34 +31,28 @@
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<p>This notebook contains everything to perform a recalibration.</p>
-<p>Current ToDo here:</p>
-<ul>
-<li>Write introduction / comment more on functions</li>
-<li>Describe approach</li>
-<li>Cite relevant papers</li>
-</ul>
+<p>This notebook contains evertyhing related to recalibration of data.</p>
 
 </div>
 </div>
 </div>
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-<div class="input">
-
-<div class="inner_cell">
-    <div class="input_area">
-<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
-</pre></div>
+<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
+<div class="text_cell_render border-box-sizing rendered_html">
+<h2 id="Recalibration-after-search">Recalibration after search<a class="anchor-link" href="#Recalibration-after-search"> </a></h2><h3 id="Precursor-mass-calibration">Precursor mass calibration<a class="anchor-link" href="#Precursor-mass-calibration"> </a></h3><p>Recalibration refers to the computational step where masses are recalibrated after a first search. The identified peptides are used to calculate the deviations of experimental masses to their theoretical masses. After recalibration, a second search with decreased precursor tolerance is performed.</p>
+<p>The recalibration is largely motivated by the software lock mass paper:</p>
+<p><a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3231580/">Cox J, Michalski A, Mann M. Software lock mass by two-dimensional minimization of peptide mass errors. J Am Soc Mass Spectrom. 2011;22(8):1373-1380. doi:10.1007/s13361-011-0142-8</a></p>
+<p>Here, mass offsets are piecewise linearly approximated. The positions for approximation need to fulfill a number of criteria (e.g., a minimum number of samples and a minimum distance). The AlphaPept implementation is slightly modified by employing a more general <code>KNeighborsRegressor</code>-approach. In brief, the calibration is calculated for each point individually by estimating the deviation from its identified neighbors in n-dimensional space (e.g., retention time, mass, mobility).</p>
+<p>Mor specifically, the algorithm consists of the following steps:</p>
+<ol>
+<li>Outlier removal: We remove outliers from the identified peptides by only accepting identifications with a mass offset that is within three standard deviations to the mean.</li>
+<li>For each point, we perform a neighbors lookup of the next n (default 100) neighbors.</li>
+<li>Next, we perform a regression based on the neighbors to determine the mass offset. The contribution of each neighbor is weighted by their distance.</li>
+</ol>
+<h3 id="Fragment-mass-calibration">Fragment mass calibration<a class="anchor-link" href="#Fragment-mass-calibration"> </a></h3><p>The fragment mass calibration is based on the identified ions (i.e., b-hits and y-hits). For each hit, we calculate the offset to its theoretical mass. The correction is then applied by taking the median offset in ppm and applying it globally.</p>
 
-    </div>
 </div>
 </div>
-
 </div>
-    {% endraw %}
-
     {% raw %}
     
 <div class="cell border-box-sizing code_cell rendered">
@@ -119,7 +113,7 @@ <h4 id="get_calibration" class="doc_header"><code>get_calibration</code><a href=
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="calibrate_hdf" class="doc_header"><code>calibrate_hdf</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L80" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>calibrate_hdf</code>(<strong><code>to_process</code></strong>, <strong><code>callback</code></strong>=<em><code>None</code></em>, <strong><code>parallel</code></strong>=<em><code>False</code></em>)</p>
+<h4 id="calibrate_hdf" class="doc_header"><code>calibrate_hdf</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L79" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>calibrate_hdf</code>(<strong><code>to_process</code></strong>, <strong><code>callback</code></strong>=<em><code>None</code></em>, <strong><code>parallel</code></strong>=<em><code>False</code></em>)</p>
 </blockquote>
 
 </div>
@@ -141,7 +135,7 @@ <h4 id="calibrate_hdf" class="doc_header"><code>calibrate_hdf</code><a href="htt
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<p>Calbration with regards to database</p>
+<h4 id="Database-calibration">Database calibration<a class="anchor-link" href="#Database-calibration"> </a></h4><p>Another way to calibrate the fragment and precursor masses is by directly comparing them to a previously generated theoretical mass database. Here, peaks in the distribution of databases are used to align the experimental masses.</p>
 
 </div>
 </div>
@@ -157,7 +151,7 @@ <h4 id="calibrate_hdf" class="doc_header"><code>calibrate_hdf</code><a href="htt
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="get_db_targets" class="doc_header"><code>get_db_targets</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L186" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_db_targets</code>(<strong><code>db_file_name</code></strong>, <strong><code>max_ppm</code></strong>=<em><code>100</code></em>, <strong><code>min_distance</code></strong>=<em><code>0.5</code></em>, <strong><code>ms_level</code></strong>=<em><code>2</code></em>)</p>
+<h4 id="get_db_targets" class="doc_header"><code>get_db_targets</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L184" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_db_targets</code>(<strong><code>db_file_name</code></strong>, <strong><code>max_ppm</code></strong>=<em><code>100</code></em>, <strong><code>min_distance</code></strong>=<em><code>0.5</code></em>, <strong><code>ms_level</code></strong>=<em><code>2</code></em>)</p>
 </blockquote>
 
 </div>
@@ -188,7 +182,7 @@ <h4 id="get_db_targets" class="doc_header"><code>get_db_targets</code><a href="h
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="align_run_to_db" class="doc_header"><code>align_run_to_db</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L230" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>align_run_to_db</code>(<strong><code>ms_data_file_name</code></strong>, <strong><code>db_array</code></strong>, <strong><code>max_ppm_distance</code></strong>=<em><code>1000000</code></em>, <strong><code>rt_step_size</code></strong>=<em><code>0.1</code></em>, <strong><code>plot_ppms</code></strong>=<em><code>False</code></em>, <strong><code>ms_level</code></strong>=<em><code>2</code></em>)</p>
+<h4 id="align_run_to_db" class="doc_header"><code>align_run_to_db</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L228" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>align_run_to_db</code>(<strong><code>ms_data_file_name</code></strong>, <strong><code>db_array</code></strong>, <strong><code>max_ppm_distance</code></strong>=<em><code>1000000</code></em>, <strong><code>rt_step_size</code></strong>=<em><code>0.1</code></em>, <strong><code>plot_ppms</code></strong>=<em><code>False</code></em>, <strong><code>ms_level</code></strong>=<em><code>2</code></em>)</p>
 </blockquote>
 
 </div>
@@ -219,7 +213,7 @@ <h4 id="align_run_to_db" class="doc_header"><code>align_run_to_db</code><a href=
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="calibrate_fragments" class="doc_header"><code>calibrate_fragments</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L317" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>calibrate_fragments</code>(<strong><code>db_file_name</code></strong>, <strong><code>ms_data_file_name</code></strong>, <strong><code>ms_level</code></strong>=<em><code>2</code></em>)</p>
+<h4 id="calibrate_fragments" class="doc_header"><code>calibrate_fragments</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/recalibration.py#L315" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>calibrate_fragments</code>(<strong><code>db_file_name</code></strong>, <strong><code>ms_data_file_name</code></strong>, <strong><code>ms_level</code></strong>=<em><code>2</code></em>)</p>
 </blockquote>
 
 </div>
diff --git a/docs/search.html b/docs/search.html
index a9f85269..f9263b60 100644
--- a/docs/search.html
+++ b/docs/search.html
@@ -31,14 +31,14 @@
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<p>This notebook contains all functions related to searching and getting peptide-spectrum-matches (PSMs). When searching, we compare how similar an experimental spectrum is to a theoretical spectrum. As described in the FASTA notebook, we can calculate theoretical fragment masses for a given peptide sequence and get theoretical spectra. Typically, we calculate a database with all possible spectra and compare our experimental data. It could be that the database is too large to be saved on disc; here we keep the database in memory.</p>
+<p>This notebook contains all functions related to searching and getting peptide-spectrum-matches (PSMs). When searching, we compare how similar an experimental spectrum is to a theoretical spectrum. As described in the FASTA notebook, we can calculate theoretical fragment masses for a given peptide sequence and get theoretical spectra. Typically, we calculate a database with all possible spectra, save it to disk, and then compare our experimental data. This allows re-using the database and saving time for this computational step. It could be that the database is too large to be saved on disc; in this case, generate the database on the fly, referring to only have a subset of all FASTA entries in memory and processing them.</p>
 
 </div>
 </div>
 </div>
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Comparing-Fragments">Comparing Fragments<a class="anchor-link" href="#Comparing-Fragments"> </a></h2><p>To efficiently compare two fragments, we use a pointer based approach. We start with two sorted arrays, the <code>query_frag</code> that contains the m/z positions of the query spectrum and the <code>db_frag</code> which contains the database fragment that is compared against to. The two pointers compare each m/z position with each other and check wheter they are within a certain tolerance <code>mtol</code>. Depending on their delta, either of the pointers is advanced. The function returns an arrray named <code>hits</code> that is the same length as the database spectrum and encodes the hit positions.</p>
+<h2 id="Comparing-spectra">Comparing spectra<a class="anchor-link" href="#Comparing-spectra"> </a></h2><p>To efficiently compare two spectra, we use a pointer based approach. We start with two sorted arrays, the <code>query_frag</code> that contains the m/z positions of the experimental query spectrum and the <code>db_frag</code> which contains the database fragment that is compared against to. The two pointers compare each m/z position with each other and check wheter they are within a certain tolerance <code>frag_tol</code>. Depending on their delta, either of the pointers is advanced. The function returns an arrray named <code>hits</code> that is the same length as the database spectrum and encodes the hit positions.</p>
 
 </div>
 </div>
@@ -54,7 +54,7 @@ <h2 id="Comparing-Fragments">Comparing Fragments<a class="anchor-link" href="#Co
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="compare_frags" class="doc_header"><code>compare_frags</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L14" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_frags</code>(<strong><code>query_frag</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>mtol</code></strong>, <strong><code>ppm</code></strong>=<em><code>False</code></em>)</p>
+<h4 id="compare_frags" class="doc_header"><code>compare_frags</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L13" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_frags</code>(<strong><code>query_frag</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>ppm</code></strong>=<em><code>False</code></em>)</p>
 </blockquote>
 <p>Compare query and database frags and find hits</p>
 
@@ -72,31 +72,6 @@ <h4 id="compare_frags" class="doc_header"><code>compare_frags</code><a href="htt
     
 <div class="cell border-box-sizing code_cell rendered">
 
-<div class="output_wrapper">
-<div class="output">
-
-<div class="output_area">
-
-
-<div class="output_markdown rendered_html output_subarea ">
-<h4 id="compare_frags_python" class="doc_header"><code>compare_frags_python</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L45" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_frags_python</code>(<strong><code>query_frag</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>mtol</code></strong>, <strong><code>ppm</code></strong>=<em><code>False</code></em>)</p>
-</blockquote>
-<p>Compare query and database frags and find hits (python function for benchmarking)</p>
-
-</div>
-
-</div>
-
-</div>
-</div>
-
-</div>
-    {% endraw %}
-
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-
 </div>
     {% endraw %}
 
@@ -113,7 +88,7 @@ <h4 id="compare_frags_python" class="doc_header"><code>compare_frags_python</cod
 
 <span class="c1"># Hits: Query 2 -&gt; Db 2 and Query 3 -&gt; Db 3</span>
 
-<span class="n">compare_frags</span><span class="p">(</span><span class="n">query_frag</span><span class="p">,</span> <span class="n">db_frag</span><span class="p">,</span> <span class="n">mtol</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
+<span class="n">compare_frags</span><span class="p">(</span><span class="n">query_frag</span><span class="p">,</span> <span class="n">db_frag</span><span class="p">,</span> <span class="n">frag_tol</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
 </pre></div>
 
     </div>
@@ -141,7 +116,7 @@ <h4 id="compare_frags_python" class="doc_header"><code>compare_frags_python</cod
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<p>This function allows us to easily compare a query spectrum against a theoretical database.</p>
+<p>This function allows us to easily compare a query spectrum against a spectrum from a theoretical database.</p>
 
 </div>
 </div>
@@ -175,7 +150,7 @@ <h4 id="compare_frags_python" class="doc_header"><code>compare_frags_python</cod
 <span class="n">query_frag</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="mf">98.06</span><span class="p">,</span> <span class="mf">227.10</span><span class="p">,</span> <span class="mf">263.08</span><span class="p">,</span> <span class="mf">548.06</span><span class="p">,</span> <span class="mf">653.31</span><span class="p">])</span>
 <span class="n">query_int</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="mi">20</span><span class="p">,</span> <span class="mi">80</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">50</span><span class="p">])</span>
 
-<span class="n">hits</span> <span class="o">=</span> <span class="n">compare_frags</span><span class="p">(</span><span class="n">query_frag</span><span class="p">,</span> <span class="n">db_frag</span><span class="p">,</span> <span class="n">mtol</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
+<span class="n">hits</span> <span class="o">=</span> <span class="n">compare_frags</span><span class="p">(</span><span class="n">query_frag</span><span class="p">,</span> <span class="n">db_frag</span><span class="p">,</span> <span class="n">frag_tol</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
 
 <span class="n">hitpos</span> <span class="o">=</span> <span class="n">hits</span><span class="p">[</span><span class="n">hits</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mi">1</span>
 <span class="n">hit_x</span> <span class="o">=</span> <span class="n">query_frag</span><span class="p">[</span><span class="n">hitpos</span><span class="p">]</span>
@@ -226,9 +201,9 @@ <h4 id="compare_frags_python" class="doc_header"><code>compare_frags_python</cod
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Comparing-Spectra">Comparing Spectra<a class="anchor-link" href="#Comparing-Spectra"> </a></h2><p>To compare multiple spectra against a database, we first need some helper functions. Per default, AlphaPept calculates in Dalton. To use ppm boundaries, we need the function <a href="/search.html#ppm_to_dalton"><code>ppm_to_dalton</code></a> for conversion.</p>
-<p>To minimize the search space, we typically only compare spectra with precursors in the same mass range as defined by <code>prec_tol</code>. To look up the limits for search, we define the function <a href="/search.html#get_idxs"><code>get_idxs</code></a>, which is a wrapper to the fast <code>searchsorted</code> method from NumPy.</p>
-<p>The actual search takes place in <code>compare_specs_single</code> and <code>compare_specs_parallel</code>, a single-core and multicore method for comparing spectra.</p>
+<h2 id="Comparing-Spectra">Comparing Spectra<a class="anchor-link" href="#Comparing-Spectra"> </a></h2><p>To compare multiple spectra against a database, we first need some helper functions. First, we need a conversion function to convert from Dalton masses to ppm, which is implemented in the <a href="/search.html#ppm_to_dalton"><code>ppm_to_dalton</code></a> function.</p>
+<p>To minimize the search space, we typically only compare spectra with precursors in the same mass range as defined by <code>prec_tol</code>. To look up the limits for search, we define the function <a href="/search.html#get_idxs"><code>get_idxs</code></a>, which is a wrapper to the fast <code>searchsorted</code> method from <code>NumPy</code>.</p>
+<p>The actual search takes place in <a href="/search.html#compare_spectrum_parallel"><code>compare_spectrum_parallel</code></a>, which utilizes the performance decorator from the performance notebook. Here we save the top matching spectra for each query spectrum. Note that for code compilation reasons, the code of the previously defined function <a href="/search.html#compare_frags"><code>compare_frags</code></a> is duplicated in here.</p>
 
 </div>
 </div>
@@ -244,7 +219,7 @@ <h2 id="Comparing-Spectra">Comparing Spectra<a class="anchor-link" href="#Compar
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="ppm_to_dalton" class="doc_header"><code>ppm_to_dalton</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L85" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>ppm_to_dalton</code>(<strong><code>mass</code></strong>, <strong><code>prec_tol</code></strong>)</p>
+<h4 id="ppm_to_dalton" class="doc_header"><code>ppm_to_dalton</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L46" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>ppm_to_dalton</code>(<strong><code>mass</code></strong>, <strong><code>prec_tol</code></strong>)</p>
 </blockquote>
 
 </div>
@@ -268,7 +243,7 @@ <h4 id="ppm_to_dalton" class="doc_header"><code>ppm_to_dalton</code><a href="htt
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="get_idxs" class="doc_header"><code>get_idxs</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L93" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_idxs</code>(<strong><code>db_masses</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>prec_tol</code></strong>, <strong><code>ppm</code></strong>)</p>
+<h4 id="get_idxs" class="doc_header"><code>get_idxs</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L54" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_idxs</code>(<strong><code>db_masses</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>prec_tol</code></strong>, <strong><code>ppm</code></strong>)</p>
 </blockquote>
 <p>Function to get upper and lower limits to define search range.</p>
 
@@ -293,57 +268,7 @@ <h4 id="get_idxs" class="doc_header"><code>get_idxs</code><a href="https://githu
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="compare_spectrum" class="doc_header"><code>compare_spectrum</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L108" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_spectrum</code>(<strong><code>query_idx</code></strong>, <strong><code>idxs_lower</code></strong>, <strong><code>idxs_higher</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>best_hits</code></strong>, <strong><code>score</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>ppm</code></strong>)</p>
-</blockquote>
-<p>Compares a spectrum and writes to best_his, score array</p>
-
-</div>
-
-</div>
-
-</div>
-</div>
-
-</div>
-    {% endraw %}
-
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-
-<div class="output_wrapper">
-<div class="output">
-
-<div class="output_area">
-
-
-<div class="output_markdown rendered_html output_subarea ">
-<h4 id="compare_spectrum_parallel_" class="doc_header"><code>compare_spectrum_parallel_</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L146" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_spectrum_parallel_</code>(<strong><code>query_idx</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>idxs_lower</code></strong>, <strong><code>idxs_higher</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>best_hits</code></strong>, <strong><code>score</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>ppm</code></strong>)</p>
-</blockquote>
-<p>Compares a spectrum and writes to best_his, score array</p>
-
-</div>
-
-</div>
-
-</div>
-</div>
-
-</div>
-    {% endraw %}
-
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-
-<div class="output_wrapper">
-<div class="output">
-
-<div class="output_area">
-
-
-<div class="output_markdown rendered_html output_subarea ">
-<h4 id="compare_spectrum_parallel" class="doc_header"><code>compare_spectrum_parallel</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L182" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_spectrum_parallel</code>(<strong><code>query_idx</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>idxs_lower</code></strong>, <strong><code>idxs_higher</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>best_hits</code></strong>, <strong><code>score</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>ppm</code></strong>)</p>
+<h4 id="compare_spectrum_parallel" class="doc_header"><code>compare_spectrum_parallel</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L72" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>compare_spectrum_parallel</code>(<strong><code>query_idx</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>idxs_lower</code></strong>, <strong><code>idxs_higher</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>best_hits</code></strong>, <strong><code>score</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>ppm</code></strong>)</p>
 </blockquote>
 <p>Compares a spectrum and writes to best_his, score array</p>
 
@@ -366,7 +291,8 @@ <h4 id="compare_spectrum_parallel" class="doc_header"><code>compare_spectrum_par
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Wrapper">Wrapper<a class="anchor-link" href="#Wrapper"> </a></h2>
+<h2 id="Wrapper">Wrapper<a class="anchor-link" href="#Wrapper"> </a></h2><p>To conveniently perform peptide-spectrum matches on multiple datasets we define a wrapper <a href="/search.html#get_psms"><code>get_psms</code></a> that returns the PSMS when handing over <code>query_data</code> and <code>db_data</code>.</p>
+
 </div>
 </div>
 </div>
@@ -381,7 +307,7 @@ <h2 id="Wrapper">Wrapper<a class="anchor-link" href="#Wrapper"> </a></h2>
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="query_data_to_features" class="doc_header"><code>query_data_to_features</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L266" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>query_data_to_features</code>(<strong><code>query_data</code></strong>)</p>
+<h4 id="query_data_to_features" class="doc_header"><code>query_data_to_features</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L156" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>query_data_to_features</code>(<strong><code>query_data</code></strong>)</p>
 </blockquote>
 
 </div>
@@ -405,7 +331,7 @@ <h4 id="query_data_to_features" class="doc_header"><code>query_data_to_features<
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="get_psms" class="doc_header"><code>get_psms</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L283" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_psms</code>(<strong><code>query_data</code></strong>, <strong><code>db_data</code></strong>, <strong><code>features</code></strong>, <strong><code>parallel</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>prec_tol</code></strong>, <strong><code>ppm</code></strong>, <strong><code>min_frag_hits</code></strong>, <strong><code>callback</code></strong>=<em><code>None</code></em>, <strong><code>m_offset_calibrated</code></strong>=<em><code>None</code></em>, <strong>**<code>kwargs</code></strong>)</p>
+<h4 id="get_psms" class="doc_header"><code>get_psms</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L173" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_psms</code>(<strong><code>query_data</code></strong>, <strong><code>db_data</code></strong>, <strong><code>features</code></strong>, <strong><code>parallel</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>prec_tol</code></strong>, <strong><code>ppm</code></strong>, <strong><code>min_frag_hits</code></strong>, <strong><code>callback</code></strong>=<em><code>None</code></em>, <strong><code>m_offset_calibrated</code></strong>=<em><code>None</code></em>, <strong>**<code>kwargs</code></strong>)</p>
 </blockquote>
 <p>Wrapper function to extract psms from dataset</p>
 <p>Args:
@@ -438,14 +364,14 @@ <h4 id="get_psms" class="doc_header"><code>get_psms</code><a href="https://githu
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Extracting-columns-for-scoring">Extracting columns for scoring<a class="anchor-link" href="#Extracting-columns-for-scoring"> </a></h2><p>The basic fragment comparison only counts the number of hits when comparing a theoretical spectrum to an experimental one. Based on the number of hits, we can drastically reduce the number of candidates one wants to analyze for an in-depth comparison, which requires additional features. The following section describes several functions which extract parameters to compare spectrum matches better.</p>
+<h2 id="Extracting-columns-for-scoring">Extracting columns for scoring<a class="anchor-link" href="#Extracting-columns-for-scoring"> </a></h2><p>The basic fragment comparison only counts the number of hits and matched intensity fraction when comparing a theoretical spectrum to an experimental one. Based on this metric, we can drastically reduce the number of candidates one wants to analyze for an in-depth comparison, which requires additional features. The following section describes several functions which extract parameters to compare spectrum matches better.</p>
 
 </div>
 </div>
 </div>
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h3 id="Frag-Delta">Frag Delta<a class="anchor-link" href="#Frag-Delta"> </a></h3><p><a href="/search.html#frag_delta"><code>frag_delta</code></a> calculates the the substracts the experimental fragment masses from the theoretical fragment masses for each hit.</p>
+<h3 id="Frag-Delta">Frag Delta<a class="anchor-link" href="#Frag-Delta"> </a></h3><p><a href="/search.html#frag_delta"><code>frag_delta</code></a> substracts the experimental fragment masses from the theoretical fragment masses for each hit.</p>
 
 </div>
 </div>
@@ -461,7 +387,7 @@ <h3 id="Frag-Delta">Frag Delta<a class="anchor-link" href="#Frag-Delta"> </a></h
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="frag_delta" class="doc_header"><code>frag_delta</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L423" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>frag_delta</code>(<strong><code>query_frag</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>hits</code></strong>)</p>
+<h4 id="frag_delta" class="doc_header"><code>frag_delta</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L313" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>frag_delta</code>(<strong><code>query_frag</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>hits</code></strong>)</p>
 </blockquote>
 <p>Calculate the mass difference for a given array of hits in Dalton and ppm</p>
 
@@ -484,45 +410,8 @@ <h4 id="frag_delta" class="doc_header"><code>frag_delta</code><a href="https://g
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h3 id="Intensity-Fraction">Intensity Fraction<a class="anchor-link" href="#Intensity-Fraction"> </a></h3>
-</div>
-</div>
-</div>
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-
-<div class="output_wrapper">
-<div class="output">
-
-<div class="output_area">
-
-
-<div class="output_markdown rendered_html output_subarea ">
-<h4 id="intensity_fraction" class="doc_header"><code>intensity_fraction</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L437" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>intensity_fraction</code>(<strong><code>query_int</code></strong>, <strong><code>hits</code></strong>)</p>
-</blockquote>
-<p>Calculate the fraction of matched intensity</p>
-
-</div>
-
-</div>
+<h3 id="Intensity-Fraction">Intensity Fraction<a class="anchor-link" href="#Intensity-Fraction"> </a></h3><p><a href="/search.html#intensity_fraction"><code>intensity_fraction</code></a> calculates the fraction of matched intensity. This refers to the intensity of all hits compared to the intensity of all peaks in the query spectrum.</p>
 
-</div>
-</div>
-
-</div>
-    {% endraw %}
-
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-
-</div>
-    {% endraw %}
-
-<div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
-<div class="text_cell_render border-box-sizing rendered_html">
-<h3 id="Intensity-Product">Intensity Product<a class="anchor-link" href="#Intensity-Product"> </a></h3>
 </div>
 </div>
 </div>
@@ -537,9 +426,9 @@ <h3 id="Intensity-Product">Intensity Product<a class="anchor-link" href="#Intens
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="intensity_product" class="doc_header"><code>intensity_product</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L452" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>intensity_product</code>(<strong><code>query_int</code></strong>, <strong><code>hits</code></strong>, <strong><code>db_int</code></strong>=<em><code>None</code></em>)</p>
+<h4 id="intensity_fraction" class="doc_header"><code>intensity_fraction</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L327" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>intensity_fraction</code>(<strong><code>query_int</code></strong>, <strong><code>hits</code></strong>)</p>
 </blockquote>
-<p>Calculate the dot product of matched query intensity to db intensity</p>
+<p>Calculate the fraction of matched intensity</p>
 
 </div>
 
@@ -560,60 +449,11 @@ <h4 id="intensity_product" class="doc_header"><code>intensity_product</code><a h
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h3 id="B-&amp;-Y---Hits">B &amp; Y - Hits<a class="anchor-link" href="#B-&amp;-Y---Hits"> </a></h3>
-</div>
-</div>
-</div>
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-
-<div class="output_wrapper">
-<div class="output">
-
-<div class="output_area">
-
-
-<div class="output_markdown rendered_html output_subarea ">
-<h4 id="b_y_hits" class="doc_header"><code>b_y_hits</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L468" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>b_y_hits</code>(<strong><code>frag_type</code></strong>, <strong><code>hits</code></strong>)</p>
-</blockquote>
-<p>Count the number of b and y hits
-hits usually start with b-ions &gt; 0, then y-ions &lt; 1</p>
-
-</div>
-
-</div>
-
-</div>
-</div>
-
-</div>
-    {% endraw %}
-
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
+<h3 id="File-Format">File Format<a class="anchor-link" href="#File-Format"> </a></h3><p>To have an efficient data format to store PSMs in the search. We use <code>numpy</code>-recarrays and define the utility functions <a href="/search.html#add_column"><code>add_column</code></a> and <a href="/search.html#remove_column"><code>remove_column</code></a> to append and remove data.</p>
 
 </div>
-    {% endraw %}
-
-    {% raw %}
-    
-<div class="cell border-box-sizing code_cell rendered">
-<div class="input">
-
-<div class="inner_cell">
-    <div class="input_area">
-<div class=" highlight hl-ipython3"><pre><span></span> 
-</pre></div>
-
-    </div>
 </div>
 </div>
-
-</div>
-    {% endraw %}
-
     {% raw %}
     
 <div class="cell border-box-sizing code_cell rendered">
@@ -625,7 +465,7 @@ <h4 id="b_y_hits" class="doc_header"><code>b_y_hits</code><a href="https://githu
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="add_column" class="doc_header"><code>add_column</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L487" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>add_column</code>(<strong><code>recarray</code></strong>, <strong><code>column</code></strong>, <strong><code>name</code></strong>)</p>
+<h4 id="add_column" class="doc_header"><code>add_column</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L345" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>add_column</code>(<strong><code>recarray</code></strong>, <strong><code>column</code></strong>, <strong><code>name</code></strong>)</p>
 </blockquote>
 <p>Function to add a column with given name to recarray</p>
 
@@ -650,7 +490,7 @@ <h4 id="add_column" class="doc_header"><code>add_column</code><a href="https://g
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="remove_column" class="doc_header"><code>remove_column</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L499" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>remove_column</code>(<strong><code>recarray</code></strong>, <strong><code>name</code></strong>)</p>
+<h4 id="remove_column" class="doc_header"><code>remove_column</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L357" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>remove_column</code>(<strong><code>recarray</code></strong>, <strong><code>name</code></strong>)</p>
 </blockquote>
 <p>Function to remove a column from recarray</p>
 
@@ -673,16 +513,17 @@ <h4 id="remove_column" class="doc_header"><code>remove_column</code><a href="htt
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Reporting">Reporting<a class="anchor-link" href="#Reporting"> </a></h2><p>When performing a database search, we need to know what experimental spectrum we are comparing with which database entry. 
+<h2 id="Extracting-features-for-scoring">Extracting features for scoring<a class="anchor-link" href="#Extracting-features-for-scoring"> </a></h2><h3 id="Indices">Indices<a class="anchor-link" href="#Indices"> </a></h3><p>When performing a database search, we need to know which experimental spectrum we compare with what database entry. 
 We distinguish three indices:</p>
 <ul>
 <li>query_idx</li>
 <li>raw_idx</li>
-<li>feature_idx
-Initially, the get_psms functions accepts experimental data in the form of <code>query_data</code>. Here, the <code>query_idx</code> refers to the index to <code>query_data</code>. However, this might not be the same index as of the raw data. This is due to the implementation of the matching of MS1-features to MS2 spectra. Here we allow multiple matches and implement this by repeating the respective spectra. </li>
+<li>feature_idx</li>
 </ul>
-<p>We then add the two columns <code>feature_idx</code> and <code>raw_idx</code> to the psms to later be able to distinguish where the match originated from. Here, <code>raw_idx</code> refers to the original spectrum.</p>
+<p>Initially, the get_psms function accepts experimental data in the form of <code>query_data</code>. Here, the <code>query_idx</code> refers to the index to <code>query_data</code>. However, this might not be the same index as the raw data. This is due to the implementation of the matching of MS1-features to MS2 spectra. Here we allow multiple matches and implement this by repeating the respective spectrum.</p>
+<p>We then add the two columns <code>feature_idx</code> and <code>raw_idx</code> to the PSMs to later be able to distinguish where the match originated. In this case, <code>raw_idx</code> refers to the original spectrum.</p>
 <p>When not applying feature finding, <code>raw_idx</code> and <code>query_idx</code> are equivalent.</p>
+<h3 id="Features">Features<a class="anchor-link" href="#Features"> </a></h3><p>In the <a href="/score.html"><code>score</code></a>-function we use the pre-filtered PSMs to extract additional columns for scoring such as the offset from theoretical to experimental precursor or the number of b- and y-ion hits.</p>
 
 </div>
 </div>
@@ -698,7 +539,7 @@ <h2 id="Reporting">Reporting<a class="anchor-link" href="#Reporting"> </a></h2><
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="get_hits" class="doc_header"><code>get_hits</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L509" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_hits</code>(<strong><code>query_frag</code></strong>, <strong><code>query_int</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>db_int</code></strong>, <strong><code>frag_type</code></strong>, <strong><code>mtol</code></strong>, <strong><code>ppm</code></strong>, <strong><code>losses</code></strong>)</p>
+<h4 id="get_hits" class="doc_header"><code>get_hits</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L367" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_hits</code>(<strong><code>query_frag</code></strong>, <strong><code>query_int</code></strong>, <strong><code>db_frag</code></strong>, <strong><code>db_int</code></strong>, <strong><code>frag_type</code></strong>, <strong><code>mtol</code></strong>, <strong><code>ppm</code></strong>, <strong><code>losses</code></strong>)</p>
 </blockquote>
 
 </div>
@@ -722,7 +563,7 @@ <h4 id="get_hits" class="doc_header"><code>get_hits</code><a href="https://githu
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="score" class="doc_header"><code>score</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L546" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>score</code>(<strong><code>psms</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>query_masses_raw</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>db_masses</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>frag_types</code></strong>, <strong><code>mtol</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>ppm</code></strong>, <strong><code>psms_dtype</code></strong>, <strong><code>db_ints</code></strong>=<em><code>None</code></em>, <strong><code>parallel</code></strong>=<em><code>False</code></em>)</p>
+<h4 id="score" class="doc_header"><code>score</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L404" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>score</code>(<strong><code>psms</code></strong>, <strong><code>query_masses</code></strong>, <strong><code>query_masses_raw</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>db_masses</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>frag_types</code></strong>, <strong><code>mtol</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>ppm</code></strong>, <strong><code>psms_dtype</code></strong>, <strong><code>db_ints</code></strong>=<em><code>None</code></em>, <strong><code>parallel</code></strong>=<em><code>False</code></em>)</p>
 </blockquote>
 
 </div>
@@ -753,7 +594,7 @@ <h4 id="score" class="doc_header"><code>score</code><a href="https://github.com/
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="get_sequences" class="doc_header"><code>get_sequences</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L627" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_sequences</code>(<strong><code>psms</code></strong>, <strong><code>db_seqs</code></strong>)</p>
+<h4 id="get_sequences" class="doc_header"><code>get_sequences</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L485" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_sequences</code>(<strong><code>psms</code></strong>, <strong><code>db_seqs</code></strong>)</p>
 </blockquote>
 <p>Get sequences to add them to a recarray</p>
 
@@ -778,7 +619,7 @@ <h4 id="get_sequences" class="doc_header"><code>get_sequences</code><a href="htt
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="get_score_columns" class="doc_header"><code>get_score_columns</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L636" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_score_columns</code>(<strong><code>psms</code></strong>, <strong><code>query_data</code></strong>, <strong><code>db_data</code></strong>, <strong><code>features</code></strong>, <strong><code>parallel</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>prec_tol</code></strong>, <strong><code>ppm</code></strong>, <strong><code>m_offset_calibrated</code></strong>=<em><code>None</code></em>, <strong>**<code>kwargs</code></strong>)</p>
+<h4 id="get_score_columns" class="doc_header"><code>get_score_columns</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L494" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>get_score_columns</code>(<strong><code>psms</code></strong>, <strong><code>query_data</code></strong>, <strong><code>db_data</code></strong>, <strong><code>features</code></strong>, <strong><code>parallel</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>prec_tol</code></strong>, <strong><code>ppm</code></strong>, <strong><code>m_offset_calibrated</code></strong>=<em><code>None</code></em>, <strong>**<code>kwargs</code></strong>)</p>
 </blockquote>
 
 </div>
@@ -815,7 +656,7 @@ <h2 id="Plot">Plot<a class="anchor-link" href="#Plot"> </a></h2>
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="plot_hit" class="doc_header"><code>plot_hit</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L802" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>plot_hit</code>(<strong><code>df</code></strong>, <strong><code>index</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>frag_types</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>ppm</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>db_ints</code></strong>=<em><code>None</code></em>, <strong>**<code>kwargs</code></strong>)</p>
+<h4 id="plot_hit" class="doc_header"><code>plot_hit</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L660" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>plot_hit</code>(<strong><code>df</code></strong>, <strong><code>index</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>frag_types</code></strong>, <strong><code>query_frags</code></strong>, <strong><code>query_ints</code></strong>, <strong><code>query_indices</code></strong>, <strong><code>ppm</code></strong>, <strong><code>frag_tol</code></strong>, <strong><code>db_ints</code></strong>=<em><code>None</code></em>, <strong>**<code>kwargs</code></strong>)</p>
 </blockquote>
 
 </div>
@@ -839,7 +680,7 @@ <h4 id="plot_hit" class="doc_header"><code>plot_hit</code><a href="https://githu
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="plot_psms" class="doc_header"><code>plot_psms</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L876" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>plot_psms</code>(<strong><code>query_data</code></strong>, <strong><code>df</code></strong>, <strong><code>index</code></strong>, <strong><code>mass_dict</code></strong>, <strong><code>ppm</code></strong>=<em><code>True</code></em>, <strong><code>frag_tol</code></strong>=<em><code>20</code></em>)</p>
+<h4 id="plot_psms" class="doc_header"><code>plot_psms</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L734" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>plot_psms</code>(<strong><code>query_data</code></strong>, <strong><code>df</code></strong>, <strong><code>index</code></strong>, <strong><code>mass_dict</code></strong>, <strong><code>ppm</code></strong>=<em><code>True</code></em>, <strong><code>frag_tol</code></strong>=<em><code>20</code></em>)</p>
 </blockquote>
 <p>Plot a psms</p>
 
@@ -877,7 +718,7 @@ <h2 id="Wrapper">Wrapper<a class="anchor-link" href="#Wrapper"> </a></h2>
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="perform_search" class="doc_header"><code>perform_search</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L957" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>perform_search</code>(<strong><code>query_files</code></strong>, <strong><code>db_masses</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_seqs</code></strong>, <strong><code>frag_types</code></strong>, <strong><code>plot</code></strong>, <strong>**<code>kwargs</code></strong>)</p>
+<h4 id="perform_search" class="doc_header"><code>perform_search</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L815" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>perform_search</code>(<strong><code>query_files</code></strong>, <strong><code>db_masses</code></strong>, <strong><code>db_frags</code></strong>, <strong><code>db_indices</code></strong>, <strong><code>db_seqs</code></strong>, <strong><code>frag_types</code></strong>, <strong><code>plot</code></strong>, <strong>**<code>kwargs</code></strong>)</p>
 </blockquote>
 <p>Function to search and score one or multiple MS runs by the X!Tandem approach.</p>
 
@@ -916,7 +757,7 @@ <h2 id="Searching-with-database">Searching with database<a class="anchor-link" h
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="store_hdf" class="doc_header"><code>store_hdf</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L985" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>store_hdf</code>(<strong><code>df</code></strong>, <strong><code>path</code></strong>, <strong><code>key</code></strong>, <strong><code>replace</code></strong>=<em><code>False</code></em>, <strong><code>swmr</code></strong>=<em><code>False</code></em>)</p>
+<h4 id="store_hdf" class="doc_header"><code>store_hdf</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L843" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>store_hdf</code>(<strong><code>df</code></strong>, <strong><code>path</code></strong>, <strong><code>key</code></strong>, <strong><code>replace</code></strong>=<em><code>False</code></em>, <strong><code>swmr</code></strong>=<em><code>False</code></em>)</p>
 </blockquote>
 <p>Stores in hdf</p>
 
@@ -941,7 +782,7 @@ <h4 id="store_hdf" class="doc_header"><code>store_hdf</code><a href="https://git
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="search_db" class="doc_header"><code>search_db</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L1006" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>search_db</code>(<strong><code>to_process</code></strong>, <strong><code>callback</code></strong>=<em><code>None</code></em>, <strong><code>parallel</code></strong>=<em><code>False</code></em>, <strong><code>first_search</code></strong>=<em><code>True</code></em>)</p>
+<h4 id="search_db" class="doc_header"><code>search_db</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L864" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>search_db</code>(<strong><code>to_process</code></strong>, <strong><code>callback</code></strong>=<em><code>None</code></em>, <strong><code>parallel</code></strong>=<em><code>False</code></em>, <strong><code>first_search</code></strong>=<em><code>True</code></em>)</p>
 </blockquote>
 <p>Perform a databse search. One file at a time.</p>
 
@@ -979,7 +820,7 @@ <h2 id="Searching-Large-Fasta-and-or-Search-Space">Searching Large Fasta and or
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="search_fasta_block" class="doc_header"><code>search_fasta_block</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L1083" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>search_fasta_block</code>(<strong><code>to_process</code></strong>)</p>
+<h4 id="search_fasta_block" class="doc_header"><code>search_fasta_block</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L941" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>search_fasta_block</code>(<strong><code>to_process</code></strong>)</p>
 </blockquote>
 <p>Search fasta block
 For searches with big fasta files or unspecific searches</p>
@@ -1005,7 +846,7 @@ <h4 id="search_fasta_block" class="doc_header"><code>search_fasta_block</code><a
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="filter_top_n" class="doc_header"><code>filter_top_n</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L1184" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>filter_top_n</code>(<strong><code>temp</code></strong>, <strong><code>top_n</code></strong>=<em><code>10</code></em>)</p>
+<h4 id="filter_top_n" class="doc_header"><code>filter_top_n</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L1042" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>filter_top_n</code>(<strong><code>temp</code></strong>, <strong><code>top_n</code></strong>=<em><code>10</code></em>)</p>
 </blockquote>
 <p>Takes a dataframe and keeps only the top n entries.
 Combines fasta indices for sequences.</p>
@@ -1031,7 +872,7 @@ <h4 id="filter_top_n" class="doc_header"><code>filter_top_n</code><a href="https
 
 
 <div class="output_markdown rendered_html output_subarea ">
-<h4 id="search_parallel" class="doc_header"><code>search_parallel</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L1211" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>search_parallel</code>(<strong><code>settings</code></strong>, <strong><code>calibration</code></strong>=<em><code>None</code></em>, <strong><code>callback</code></strong>=<em><code>None</code></em>)</p>
+<h4 id="search_parallel" class="doc_header"><code>search_parallel</code><a href="https://github.com/mannlabs/alphapept/tree/master/alphapept/search.py#L1069" class="source_link" style="float:right">[source]</a></h4><blockquote><p><code>search_parallel</code>(<strong><code>settings</code></strong>, <strong><code>calibration</code></strong>=<em><code>None</code></em>, <strong><code>callback</code></strong>=<em><code>None</code></em>)</p>
 </blockquote>
 <p>Function to generate a database from a fasta file</p>
 
diff --git a/docs/settings.html b/docs/settings.html
index c44c16a5..af36cb4d 100644
--- a/docs/settings.html
+++ b/docs/settings.html
@@ -152,15 +152,16 @@ <h4 id="save_settings" class="doc_header"><code>save_settings</code><a href="htt
 
 <div class="cell border-box-sizing text_cell rendered"><div class="inner_cell">
 <div class="text_cell_render border-box-sizing rendered_html">
-<h2 id="Settings-Template">Settings Template<a class="anchor-link" href="#Settings-Template"> </a></h2><p>The settings template defines individual settings. The idea is to provide a template so that a graphical user interface can be automatically generated. The list below represents what each item would be when using PyQt. This could be adapted for any kind of GUI library.</p>
+<h2 id="Settings-Template">Settings Template<a class="anchor-link" href="#Settings-Template"> </a></h2><p>The settings template defines individual settings. The idea is to provide a template so that a graphical user interface can be automatically generated. The list below represents what each item would be when using <code>streamlit</code>. This could be adapted for any kind of GUI library.</p>
 <p>Each entry has a type, default values, and a description.</p>
 <ul>
-<li>spinbox -&gt; QSpinBox with minimum and maximum values (int)</li>
-<li>doublespinbox -&gt; QDoubleSpinBox with minimum and maximum values (float)</li>
-<li>path -&gt; Clickable button to select a path to save / load files.</li>
-<li>combobox -&gt; QComboBox, dropdown menu with values to choose from</li>
-<li>checkbox -&gt; QCheckBox, checkbox that can be selected</li>
-<li>checkgroup -&gt; Creates a list of QCheckBox options that can be selected</li>
+<li>spinbox -&gt; <code>st.range</code>, range with minimum and maximum values (int)</li>
+<li>doublespinbox -&gt; <code>st.range</code>, range with minimum and maximum values (float)</li>
+<li>path -&gt; <code>st.button</code>, clickable button to select a path to save / load files.</li>
+<li>combobox -&gt; <code>st.selectbox</code>, dropdown menu with values to choose from</li>
+<li>checkbox -&gt; <code>st.checkbox</code>, checkbox that can be selected</li>
+<li>checkgroup -&gt; <code>st.multiselect</code>, creates a list of options that can be selected</li>
+<li>string -&gt; <code>st.text_input</code>, generic string input</li>
 <li>list -&gt; Creates a list that is displayed</li>
 <li>placeholder -&gt; This just prints the parameter and cannot be changed</li>
 </ul>
@@ -348,6 +349,11 @@ <h3 id="Experimental-Settings">Experimental Settings<a class="anchor-link" href=
   default: []
   description: List of fractions for the raw files.
   type: list
+matching_groups:
+  default: []
+  description: List of macthing groups for the raw files. This only allows match-between-runs
+    of files within the same groups.
+  type: list
 results_path:
   default: null
   description: Path where the results should be stored.
diff --git a/docs/sidebar.json b/docs/sidebar.json
index 9c794177..3fe61a8f 100644
--- a/docs/sidebar.json
+++ b/docs/sidebar.json
@@ -16,6 +16,7 @@
     "Speed": "speed.html",
     "Export": "export.html",
     "Performance": "performance.html",
+    "How to contribute": "contributing.html",
     "AlphaPept workflow and files": "file_formats.html"
   }
 }
\ No newline at end of file
diff --git a/installer/one_click_windows/alphapept_innoinstaller.iss b/installer/one_click_windows/alphapept_innoinstaller.iss
index 38bcf442..4a67c760 100644
--- a/installer/one_click_windows/alphapept_innoinstaller.iss
+++ b/installer/one_click_windows/alphapept_innoinstaller.iss
@@ -2,7 +2,7 @@
 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
 
 #define MyAppName "AlphaPept"
-#define MyAppVersion "0.3.26"
+#define MyAppVersion "0.3.27"
 #define MyAppPublisher "MannLabs"
 #define MyAppURL "https://github.com/MannLabs/alphapept"
 #define MyAppExeName "alphapept.exe"
diff --git a/installer/one_click_windows/create_installer_windows.bat b/installer/one_click_windows/create_installer_windows.bat
index 88c1c6c4..669f6dc3 100644
--- a/installer/one_click_windows/create_installer_windows.bat
+++ b/installer/one_click_windows/create_installer_windows.bat
@@ -11,7 +11,7 @@ call DEL /F/Q/S dist > NUL
 call RMDIR /Q/S build
 call RMDIR /Q/S dist
 call python setup.py sdist bdist_wheel
-call pip install dist/alphapept-0.3.26-py3-none-any.whl
+call pip install dist/alphapept-0.3.27-py3-none-any.whl
 call pip install pyinstaller==4.2
 call cd installer/one_click_windows
 call pyinstaller ../alphapept.spec -y
diff --git a/nbs/00_settings.ipynb b/nbs/00_settings.ipynb
index 3ede2712..c5b49d4d 100644
--- a/nbs/00_settings.ipynb
+++ b/nbs/00_settings.ipynb
@@ -103,18 +103,19 @@
    "source": [
     "## Settings Template\n",
     "\n",
-    "The settings template defines individual settings. The idea is to provide a template so that a graphical user interface can be automatically generated. The list below represents what each item would be when using PyQt. This could be adapted for any kind of GUI library.\n",
+    "The settings template defines individual settings. The idea is to provide a template so that a graphical user interface can be automatically generated. The list below represents what each item would be when using `streamlit`. This could be adapted for any kind of GUI library.\n",
     "\n",
     "Each entry has a type, default values, and a description.\n",
     "\n",
-    "* spinbox -> QSpinBox with minimum and maximum values (int)\n",
-    "* doublespinbox -> QDoubleSpinBox with minimum and maximum values (float)\n",
-    "* path -> Clickable button to select a path to save / load files.\n",
-    "* combobox -> QComboBox, dropdown menu with values to choose from\n",
-    "* checkbox -> QCheckBox, checkbox that can be selected\n",
-    "* checkgroup -> Creates a list of QCheckBox options that can be selected\n",
+    "* spinbox -> `st.range`, range with minimum and maximum values (int)\n",
+    "* doublespinbox -> `st.range`, range with minimum and maximum values (float)\n",
+    "* path -> `st.button`, clickable button to select a path to save / load files.\n",
+    "* combobox -> `st.selectbox`, dropdown menu with values to choose from\n",
+    "* checkbox -> `st.checkbox`, checkbox that can be selected\n",
+    "* checkgroup -> `st.multiselect`, creates a list of options that can be selected\n",
+    "* string -> `st.text_input`, generic string input\n",
     "* list -> Creates a list that is displayed\n",
-    "* placeholder -> This just prints the parameter and cannot be changed"
+    "* placeholder -> This just prints the parameter and cannot be changed\n"
    ]
   },
   {
@@ -267,7 +268,7 @@
     "experiment[\"shortnames\"] = {'type':'list','default':[], 'description':\"List of shortnames for the raw files.\"}\n",
     "experiment[\"file_paths\"] = {'type':'list','default':[], 'description':\"Filepaths of the experiments.\"}\n",
     "experiment[\"fractions\"] = {'type':'list','default':[], 'description':\"List of fractions for the raw files.\"}\n",
-    "\n",
+    "experiment[\"matching_groups\"] = {'type':'list','default':[], 'description':\"List of macthing groups for the raw files. This only allows match-between-runs of files within the same groups.\"}\n",
     "experiment[\"database_path\"] = {'type':'path','default':None, 'filetype':['hdf'], 'folder':False, 'description':\"Path to library file (.hdf).\"}\n",
     "experiment[\"fasta_paths\"] = {'type':'list','default':[], 'description':\"List of paths for FASTA files.\"}\n",
     "\n",
@@ -302,6 +303,11 @@
       "  default: []\n",
       "  description: List of fractions for the raw files.\n",
       "  type: list\n",
+      "matching_groups:\n",
+      "  default: []\n",
+      "  description: List of macthing groups for the raw files. This only allows match-between-runs\n",
+      "    of files within the same groups.\n",
+      "  type: list\n",
       "results_path:\n",
       "  default: null\n",
       "  description: Path where the results should be stored.\n",
@@ -1192,8 +1198,9 @@
       "Converted 11_interface.ipynb.\n",
       "Converted 12_speed.ipynb.\n",
       "Converted 13_export.ipynb.\n",
-      "Converted index.ipynb.\n",
-      "Converted XX_file_formats.ipynb.\n"
+      "Converted 13_performance.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
      ]
     }
    ],
diff --git a/nbs/02_io.ipynb b/nbs/02_io.ipynb
index ebc7abcf..b5d3ac32 100644
--- a/nbs/02_io.ipynb
+++ b/nbs/02_io.ipynb
@@ -69,7 +69,9 @@
    "source": [
     "### Thermo\n",
     "\n",
-    "To read Thermo files, AlphaPept uses the `pyrawfilereader` package, a Python implementation of the commonly used `rawfilereader` tool. By using the custom python version, Thermo files can be read without having to install `MSFileReader`."
+    "To read Thermo files, AlphaPept uses the `pyrawfilereader` package, a Python implementation of the commonly used `rawfilereader` tool. By using the custom python version, Thermo files can be read without having to install `MSFileReader`.\n",
+    "\n",
+    "The user can pass an additional flag `use_profile_ms1`. This will then use the profile data which is not centroided already an peform centroiding. Note that this will lead to slightly different intensities, as the centroided data uses the apex and the centroid algorithm the summed intensity."
    ]
   },
   {
@@ -2112,8 +2114,9 @@
       "Converted 11_interface.ipynb.\n",
       "Converted 12_speed.ipynb.\n",
       "Converted 13_export.ipynb.\n",
-      "Converted index.ipynb.\n",
-      "Converted XX_file_formats.ipynb.\n"
+      "Converted 13_performance.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
      ]
     }
    ],
@@ -2126,7 +2129,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   }
diff --git a/nbs/04_feature_finding.ipynb b/nbs/04_feature_finding.ipynb
index b699fcd4..76b7450c 100644
--- a/nbs/04_feature_finding.ipynb
+++ b/nbs/04_feature_finding.ipynb
@@ -61,19 +61,7 @@
    "cell_type": "code",
    "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "ename": "ModuleNotFoundError",
-     "evalue": "No module named 'alphapept'",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-2-cd4d27e91b08>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;31m#export\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mnumpy\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0malphapept\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mperformance\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0malphapept\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mperformance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mperformance_function\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'alphapept'"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "#export\n",
     "import numpy as np\n",
@@ -681,7 +669,7 @@
     "    mz_ = mass_data[idx_]\n",
     "\n",
     "    int_sum = np.sum(int_) \n",
-    "    int_area = np.trapz(rt_[rt_idx[idx_]], int_) #Area\n",
+    "    int_area = np.abs(np.trapz(rt_[rt_idx[idx_]], int_)) #Area\n",
     "\n",
     "    rt_min = rt_[rt_idx[idx_]].min()\n",
     "    rt_max = rt_[rt_idx[idx_]].max()\n",
@@ -846,7 +834,16 @@
    "cell_type": "code",
    "execution_count": null,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "True\n",
+      "False\n"
+     ]
+    }
+   ],
    "source": [
     "charge = 1\n",
     "\n",
@@ -1244,7 +1241,7 @@
     "\n",
     "        array = array[minpos:maxpos+1]\n",
     "\n",
-    "    return array, intensity_profile"
+    "    return array"
    ]
   },
   {
@@ -1445,6 +1442,7 @@
     "    longest_trace = 0\n",
     "    champion_trace = None\n",
     "    champion_charge = 0\n",
+    "    champion_intensity = 0\n",
     "\n",
     "    # Sort patterns by mass\n",
     "    \n",
@@ -1460,22 +1458,23 @@
     "        trails = get_trails(seed, sorted_pattern, stats, charge_range, iso_mass_range, sortindex_, hill_ptrs, hill_data, int_data, scan_idx, cc_cutoff)\n",
     "\n",
     "        for index, trail in enumerate(trails):\n",
-    "            if len(trail) > longest_trace:  # Needs to be longer than the current champion\n",
-    "\n",
+    "            if len(trail) >= longest_trace:  # Needs to be longer than the current champion\n",
     "                arr = int_list_to_array(trail)\n",
     "                intensity_profile = stats[arr][:,2]\n",
     "                seedpos = np.nonzero(arr==seed_global)[0][0]\n",
     "\n",
     "                # truncate around the seed...\n",
-    "                arr, intensity_profile = truncate(arr, intensity_profile, seedpos, iso_split_level)\n",
+    "                arr = truncate(arr, intensity_profile, seedpos, iso_split_level)\n",
+    "                intensity_profile = stats[arr][:,2]\n",
     "\n",
     "                # Remove lower masses:\n",
     "                # Take the index of the maximum and remove all masses on the left side\n",
     "                if charge_range[index] * stats[seed_global, 0] < 1000:\n",
     "                    maxpos = np.argmax(intensity_profile)\n",
     "                    arr = arr[maxpos:]\n",
-    "\n",
-    "                if len(arr) > longest_trace:\n",
+    "                    intensity_profile = stats[arr][:,2]\n",
+    "                    \n",
+    "                if (len(arr) > longest_trace) | ((len(arr) == longest_trace) & (intensity_profile.sum() > champion_intensity)):\n",
     "                    # Averagine check\n",
     "                    cc = check_averagine(stats, arr, charge_range[index], averagine_aa, isotopes)\n",
     "                    if cc > 0.6:\n",
@@ -1483,6 +1482,7 @@
     "                        champion_trace = arr\n",
     "                        champion_charge = charge_range[index]\n",
     "                        longest_trace = len(arr)\n",
+    "                        champion_intensity = intensity_profile.sum()\n",
     "                        \n",
     "    return champion_trace, champion_charge"
    ]
@@ -1648,8 +1648,6 @@
     "    rt_start = isotope_data[int_max_idx, 4] # This is the start of the most abundant trace\n",
     "    rt_end = isotope_data[int_max_idx, 5]\n",
     "\n",
-    "    int_sum = np.sum(isotope_data[:, 2])\n",
-    "    \n",
     "    # better measurement of the peak with interpolation\n",
     "    \n",
     "    rt_min_ = min(isotope_data[:, 4])\n",
@@ -1719,7 +1717,7 @@
     "    rt_start = rt_range[rt_min_idx]\n",
     "    rt_end = rt_range[rt_max_idx]\n",
     "    \n",
-    "    int_area = np.trapz(trace_sum[rt_min_idx:rt_max_idx], rt_range[rt_min_idx:rt_max_idx])\n",
+    "    int_area = np.abs(np.trapz(trace_sum[rt_min_idx:rt_max_idx], rt_range[rt_min_idx:rt_max_idx]))\n",
     "    int_sum = trace_sum.sum()\n",
     "        \n",
     "    results[idx,:] = np.array([mz, mz_std, mz_most_abundant, charge, rt_start, rt_apex, rt_end, fwhm, n_isotopes, mass, int_apex, int_area, int_sum])"
@@ -2328,7 +2326,31 @@
    "cell_type": "code",
    "execution_count": null,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Converted 00_settings.ipynb.\n",
+      "Converted 01_chem.ipynb.\n",
+      "Converted 02_io.ipynb.\n",
+      "Converted 03_fasta.ipynb.\n",
+      "Converted 04_feature_finding.ipynb.\n",
+      "Converted 05_search.ipynb.\n",
+      "Converted 06_score.ipynb.\n",
+      "Converted 07_recalibration.ipynb.\n",
+      "Converted 08_quantification.ipynb.\n",
+      "Converted 09_matching.ipynb.\n",
+      "Converted 10_constants.ipynb.\n",
+      "Converted 11_interface.ipynb.\n",
+      "Converted 12_speed.ipynb.\n",
+      "Converted 13_export.ipynb.\n",
+      "Converted 13_performance.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
+     ]
+    }
+   ],
    "source": [
     "#hide\n",
     "from nbdev.export import *\n",
@@ -2338,7 +2360,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   }
diff --git a/nbs/05_search.ipynb b/nbs/05_search.ipynb
index ab845744..25843198 100644
--- a/nbs/05_search.ipynb
+++ b/nbs/05_search.ipynb
@@ -22,7 +22,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "This notebook contains all functions related to searching and getting peptide-spectrum-matches (PSMs). When searching, we compare how similar an experimental spectrum is to a theoretical spectrum. As described in the FASTA notebook, we can calculate theoretical fragment masses for a given peptide sequence and get theoretical spectra. Typically, we calculate a database with all possible spectra and compare our experimental data. It could be that the database is too large to be saved on disc; here we keep the database in memory."
+    "This notebook contains all functions related to searching and getting peptide-spectrum-matches (PSMs). When searching, we compare how similar an experimental spectrum is to a theoretical spectrum. As described in the FASTA notebook, we can calculate theoretical fragment masses for a given peptide sequence and get theoretical spectra. Typically, we calculate a database with all possible spectra, save it to disk, and then compare our experimental data. This allows re-using the database and saving time for this computational step. It could be that the database is too large to be saved on disc; in this case, generate the database on the fly, referring to only have a subset of all FASTA entries in memory and processing them."
    ]
   },
   {
@@ -42,9 +42,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Comparing Fragments\n",
+    "## Comparing spectra\n",
     "\n",
-    "To efficiently compare two fragments, we use a pointer based approach. We start with two sorted arrays, the `query_frag` that contains the m/z positions of the query spectrum and the `db_frag` which contains the database fragment that is compared against to. The two pointers compare each m/z position with each other and check wheter they are within a certain tolerance `mtol`. Depending on their delta, either of the pointers is advanced. The function returns an arrray named `hits` that is the same length as the database spectrum and encodes the hit positions."
+    "To efficiently compare two spectra, we use a pointer based approach. We start with two sorted arrays, the `query_frag` that contains the m/z positions of the experimental query spectrum and the `db_frag` which contains the database fragment that is compared against to. The two pointers compare each m/z position with each other and check wheter they are within a certain tolerance `frag_tol`. Depending on their delta, either of the pointers is advanced. The function returns an arrray named `hits` that is the same length as the database spectrum and encodes the hit positions."
    ]
   },
   {
@@ -59,7 +59,7 @@
     "import numpy as np\n",
     "\n",
     "@njit\n",
-    "def compare_frags(query_frag, db_frag, mtol, ppm=False):\n",
+    "def compare_frags(query_frag, db_frag, frag_tol, ppm=False):\n",
     "    \"\"\"\n",
     "    Compare query and database frags and find hits\n",
     "    \"\"\"\n",
@@ -78,7 +78,7 @@
     "        else:\n",
     "            mass_difference = delta_mass\n",
     "\n",
-    "        if abs(mass_difference) <= mtol:\n",
+    "        if abs(mass_difference) <= frag_tol:\n",
     "            hits[d] = q + 1  # Save query position +1 (zero-indexing)\n",
     "            d += 1\n",
     "            q += 1  # Only one query for each db element\n",
@@ -87,45 +87,7 @@
     "        elif delta_mass > 0:\n",
     "            d += 1\n",
     "\n",
-    "    return hits\n",
-    "\n",
-    "def compare_frags_python(query_frag, db_frag, mtol, ppm=False):\n",
-    "    \"\"\"\n",
-    "    Compare query and database frags and find hits (python function for benchmarking)\n",
-    "    \"\"\"\n",
-    "    q_max = len(query_frag)\n",
-    "    d_max = len(db_frag)\n",
-    "    hits = np.zeros(d_max, dtype=np.int16)\n",
-    "    q, d = 0, 0  # q > query, d > database\n",
-    "    while q < q_max and d < d_max:\n",
-    "        mass1 = query_frag[q]\n",
-    "        mass2 = db_frag[d]\n",
-    "        delta_mass = mass1 - mass2\n",
-    "\n",
-    "        if ppm:\n",
-    "            sum_mass = mass1 + mass2\n",
-    "            mass_difference = 2 * delta_mass / sum_mass * 1e6\n",
-    "        else:\n",
-    "            mass_difference = delta_mass\n",
-    "\n",
-    "        if abs(mass_difference) <= mtol:\n",
-    "            hits[d] = q + 1  # Save query position +1 (zero-indexing)\n",
-    "            d += 1\n",
-    "            q += 1  # Only one query for each db element\n",
-    "        elif delta_mass < 0:\n",
-    "            q += 1\n",
-    "        elif delta_mass > 0:\n",
-    "            d += 1\n",
-    "\n",
-    "    return hits\n",
-    "\n",
-    "\n",
-    "from numba import cuda\n",
-    "\n",
-    "try:\n",
-    "    import cupy\n",
-    "except ModuleNotFoundError:\n",
-    "    pass"
+    "    return hits"
    ]
   },
   {
@@ -151,7 +113,7 @@
     "\n",
     "# Hits: Query 2 -> Db 2 and Query 3 -> Db 3\n",
     "\n",
-    "compare_frags(query_frag, db_frag, mtol=1)"
+    "compare_frags(query_frag, db_frag, frag_tol=1)"
    ]
   },
   {
@@ -163,41 +125,41 @@
     "#hide\n",
     "def test_compare_frags():\n",
     "    query_frag = np.array([100, 200, 300, 400])\n",
-    "    mtol = 1\n",
+    "    frag_tol = 1\n",
     "\n",
     "    db_frag = query_frag.copy()\n",
     "\n",
     "    # Self-Comparison: no of hits should be same as length\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == len(query_frag)\n",
     "\n",
     "    # Self-Comparison: above but in tolerance\n",
-    "    hits = compare_frags(query_frag, db_frag + mtol - 0.01, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag + frag_tol - 0.01, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == len(query_frag)\n",
     "\n",
     "    # Self-Comparison: below but in tolerance\n",
-    "    hits = compare_frags(query_frag, db_frag - mtol + 0.01, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag - frag_tol + 0.01, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == len(query_frag)\n",
     "\n",
     "    # Self-Comparison: above tolerance, no hits\n",
-    "    hits = compare_frags(query_frag, db_frag + mtol + 0.01, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag + frag_tol + 0.01, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == 0\n",
     "\n",
     "    # Special case 1: First and last\n",
     "    db_frag = np.array([100, 400])\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == 2\n",
     "\n",
     "    # Special case 2: Two queries matching the same DB frag\n",
     "    query_frag = np.array([100, 100.5])\n",
     "    db_frag = np.array([100, 200, 300])\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == 1\n",
     "\n",
     "    # Special case 3: Two db frags matching the same query frag\n",
     "    db_frag = np.array([100, 100.5])\n",
     "    query_frag = np.array([100, 200, 300])\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
+    "    hits = compare_frags(query_frag, db_frag, frag_tol, ppm=False)\n",
     "    assert np.sum(hits > 0) == 1\n",
     "    \n",
     "test_compare_frags()"
@@ -207,7 +169,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "This function allows us to easily compare a query spectrum against a theoretical database."
+    "This function allows us to easily compare a query spectrum against a spectrum from a theoretical database."
    ]
   },
   {
@@ -251,7 +213,7 @@
     "query_frag = np.array([98.06, 227.10, 263.08, 548.06, 653.31])\n",
     "query_int = np.array([20, 80, 30, 30, 50])\n",
     "\n",
-    "hits = compare_frags(query_frag, db_frag, mtol=1)\n",
+    "hits = compare_frags(query_frag, db_frag, frag_tol=1)\n",
     "\n",
     "hitpos = hits[hits > 0] - 1\n",
     "hit_x = query_frag[hitpos]\n",
@@ -281,11 +243,11 @@
    "source": [
     "## Comparing Spectra\n",
     "\n",
-    "To compare multiple spectra against a database, we first need some helper functions. Per default, AlphaPept calculates in Dalton. To use ppm boundaries, we need the function `ppm_to_dalton` for conversion. \n",
+    "To compare multiple spectra against a database, we first need some helper functions. First, we need a conversion function to convert from Dalton masses to ppm, which is implemented in the `ppm_to_dalton` function. \n",
     "\n",
-    "To minimize the search space, we typically only compare spectra with precursors in the same mass range as defined by `prec_tol`. To look up the limits for search, we define the function `get_idxs`, which is a wrapper to the fast `searchsorted` method from NumPy.\n",
+    "To minimize the search space, we typically only compare spectra with precursors in the same mass range as defined by `prec_tol`. To look up the limits for search, we define the function `get_idxs`, which is a wrapper to the fast `searchsorted` method from `NumPy`.\n",
     "\n",
-    "The actual search takes place in `compare_specs_single` and `compare_specs_parallel`, a single-core and multicore method for comparing spectra."
+    "The actual search takes place in `compare_spectrum_parallel`, which utilizes the performance decorator from the performance notebook. Here we save the top matching spectra for each query spectrum. Note that for code compilation reasons, the code of the previously defined function `compare_frags` is duplicated in here. "
    ]
   },
   {
@@ -318,80 +280,9 @@
     "    idxs_higher = db_masses.searchsorted(query_masses + dalton_offset, side=\"right\")\n",
     "\n",
     "    return idxs_lower, idxs_higher\n",
-    "\n",
-    "@njit\n",
-    "def compare_spectrum(query_idx, idxs_lower, idxs_higher, query_indices, query_frags, query_ints, db_indices, db_frags, best_hits, score, frag_tol, ppm):\n",
-    "    \"\"\"\n",
-    "    Compares a spectrum and writes to best_his, score array\n",
-    "    \"\"\" \n",
-    "    idx_low = idxs_lower[query_idx]\n",
-    "    idx_high = idxs_higher[query_idx]\n",
-    "\n",
-    "    query_idx_start = query_indices[query_idx]\n",
-    "    query_idx_end = query_indices[query_idx + 1]\n",
-    "    query_frag = query_frags[query_idx_start:query_idx_end]\n",
-    "    query_int = query_ints[query_idx_start:query_idx_end]\n",
-    "\n",
-    "    query_int_sum = np.sum(query_int)\n",
-    "\n",
-    "    matches = []\n",
-    "\n",
-    "    for db_idx in range(idx_low, idx_high):    \n",
-    "        db_idx_start = db_indices[db_idx]\n",
-    "        db_idx_end = db_indices[db_idx + 1]\n",
-    "        db_frag = db_frags[db_idx_start:db_idx_end]\n",
-    "        hits = compare_frags(query_frag, db_frag, frag_tol, ppm)\n",
-    "        n_hits = np.sum(hits>0)\n",
-    "        if n_hits > 0:\n",
-    "            matched_int = np.sum(query_int[hits[hits>0]-1]) / query_int_sum\n",
-    "            matches.append((n_hits + matched_int, db_idx))\n",
-    "            \n",
-    "    matches.sort()\n",
-    "    matches = matches[::-1]\n",
-    "\n",
-    "    len_ = min((len(matches), best_hits.shape[1]))            \n",
-    "    best_hits[query_idx, 0:len_] = [_[1] for _ in matches[:len_]]\n",
-    "    score[query_idx, 0:len_] = [_[0] for _ in matches[:len_]]\n",
-    "    \n",
     "    \n",
     "import alphapept.performance\n",
     "\n",
-    "#@alphapept.performance.performance_function(compilation_mode=\"numba-multithread\")\n",
-    "def compare_spectrum_parallel_(query_idx, query_masses, idxs_lower, idxs_higher, query_indices, query_frags, query_ints, db_indices, db_frags, best_hits, score, frag_tol, ppm):\n",
-    "    \"\"\"\n",
-    "    Compares a spectrum and writes to best_his, score array\n",
-    "    \"\"\"\n",
-    "    \n",
-    "    idx_low = idxs_lower[query_idx]\n",
-    "    idx_high = idxs_higher[query_idx]\n",
-    "\n",
-    "    query_idx_start = query_indices[query_idx]\n",
-    "    query_idx_end = query_indices[query_idx + 1]\n",
-    "    query_frag = query_frags[query_idx_start:query_idx_end]\n",
-    "    query_int = query_ints[query_idx_start:query_idx_end]\n",
-    "\n",
-    "    query_int_sum = query_int.sum()\n",
-    "\n",
-    "    matches = []\n",
-    "\n",
-    "    for db_idx in range(idx_low, idx_high):    \n",
-    "        db_idx_start = db_indices[db_idx]\n",
-    "        db_idx_end = db_indices[db_idx + 1]\n",
-    "        db_frag = db_frags[db_idx_start:db_idx_end]\n",
-    "        \n",
-    "        hits = compare_frags(query_frag, db_frag, frag_tol, ppm)\n",
-    "    \n",
-    "        n_hits = (hits>0).sum()\n",
-    "        if n_hits > 0:\n",
-    "            matched_int = (query_int[hits[hits>0]-1]).sum() / query_int_sum\n",
-    "            matches.append((n_hits + matched_int, db_idx))\n",
-    "            \n",
-    "    matches.sort()\n",
-    "    matches = matches[::-1]\n",
-    "\n",
-    "    len_ = min((len(matches), best_hits.shape[1]))            \n",
-    "    best_hits[query_idx, 0:len_] = [_[1] for _ in matches[:len_]]\n",
-    "    score[query_idx, 0:len_] = [_[0] for _ in matches[:len_]]\n",
     "    \n",
     "@alphapept.performance.performance_function\n",
     "def compare_spectrum_parallel(query_idx, query_masses, idxs_lower, idxs_higher, query_indices, query_frags, query_ints, db_indices, db_frags, best_hits, score, frag_tol, ppm):\n",
@@ -476,7 +367,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Wrapper"
+    "## Wrapper\n",
+    "\n",
+    "To conveniently perform peptide-spectrum matches on multiple datasets we define a wrapper `get_psms` that returns the PSMS when handing over `query_data` and `db_data`."
    ]
   },
   {
@@ -654,7 +547,7 @@
    "source": [
     "## Extracting columns for scoring\n",
     "\n",
-    "The basic fragment comparison only counts the number of hits when comparing a theoretical spectrum to an experimental one. Based on the number of hits, we can drastically reduce the number of candidates one wants to analyze for an in-depth comparison, which requires additional features. The following section describes several functions which extract parameters to compare spectrum matches better."
+    "The basic fragment comparison only counts the number of hits and matched intensity fraction when comparing a theoretical spectrum to an experimental one. Based on this metric, we can drastically reduce the number of candidates one wants to analyze for an in-depth comparison, which requires additional features. The following section describes several functions which extract parameters to compare spectrum matches better."
    ]
   },
   {
@@ -663,7 +556,7 @@
    "source": [
     "### Frag Delta\n",
     "\n",
-    "`frag_delta` calculates the the substracts the experimental fragment masses from the theoretical fragment masses for each hit. "
+    "`frag_delta` substracts the experimental fragment masses from the theoretical fragment masses for each hit. "
    ]
   },
   {
@@ -711,7 +604,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Intensity Fraction"
+    "### Intensity Fraction\n",
+    "`intensity_fraction` calculates the fraction of matched intensity. This refers to the intensity of all hits compared to the intensity of all peaks in the query spectrum."
    ]
   },
   {
@@ -761,139 +655,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Intensity Product"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "#export\n",
-    "@njit\n",
-    "def intensity_product(query_int, hits, db_int=None):\n",
-    "    \"\"\"\n",
-    "    Calculate the dot product of matched query intensity to db intensity\n",
-    "    \"\"\"\n",
-    "\n",
-    "    matched_query_int = query_int[hits[hits > 0] - 1]\n",
-    "    if db_int is None:\n",
-    "        matched_intensity = np.sum(matched_query_int)\n",
-    "    else:\n",
-    "        matched_db_int = db_int[hits > 0]\n",
-    "        matched_intensity = np.sum(matched_query_int*matched_db_int)\n",
-    "\n",
-    "    return matched_intensity"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "#hide\n",
-    "def test_intensity_product():\n",
-    "    mtol = 1\n",
-    "    query_frag = np.array([100, 200, 300, 400])\n",
-    "    db_frag = np.array([100, 300, 500, 700])\n",
-    "    query_int = np.array([10, 20, 30, 40])\n",
-    "\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
-    "    assert intensity_product(query_int, hits) == 40\n",
-    "\n",
-    "    query_frag = np.array([100, 200, 300, 400, 600])\n",
-    "    db_frag = np.array([100, 300, 500, 700])\n",
-    "    query_int = np.array([10, 20, 30, 40, 50])\n",
-    "    db_int = np.array([10, 20, 30, 40])\n",
+    "### File Format\n",
     "\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
-    "    assert intensity_product(query_int, hits, db_int = db_int) == 10*10+30*20\n",
-    "    \n",
-    "test_intensity_product()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### B & Y - Hits"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "#export\n",
-    "@njit\n",
-    "def b_y_hits(frag_type, hits):\n",
-    "    \"\"\"\n",
-    "    Count the number of b and y hits\n",
-    "    hits usually start with b-ions > 0, then y-ions < 1\n",
-    "    \"\"\"\n",
-    "    hits_index = hits > 0\n",
-    "\n",
-    "    hit_types = frag_type[hits_index]\n",
-    "\n",
-    "    b_hits = np.sum(hit_types > 0)\n",
-    "    y_hits = np.sum(hit_types < 0)\n",
-    "\n",
-    "    return b_hits, y_hits"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "#hide\n",
-    "def test_b_y_hits():\n",
-    "    # TODO: Write a test to make sure the by hits are correct..\n",
-    "    mtol = 1\n",
-    "    query_frag = np.array([100, 200, 300, 400])\n",
-    "    db_frag = np.array([100, 300, 500, 700])\n",
-    "    query_int = np.array([10, 20, 30, 40])\n",
-    "    frag_type = np.array([1, -1, 2, -2])\n",
-    "\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
-    "\n",
-    "    b_hit, y_hit = b_y_hits(frag_type, hits)\n",
-    "\n",
-    "    assert (b_hit) == 1\n",
-    "    assert (y_hit) == 1\n",
-    "\n",
-    "    frag_type = np.array([-1, -2, -3, -4])\n",
-    "\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
-    "\n",
-    "    b_hit, y_hit = b_y_hits(frag_type, hits)\n",
-    "    \n",
-    "    assert (b_hit) == 0\n",
-    "    assert (y_hit) == 2\n",
-    "\n",
-    "    frag_type = np.array([1, 2, 3, 4])\n",
-    "\n",
-    "    hits = compare_frags(query_frag, db_frag, mtol, ppm=False)\n",
-    "\n",
-    "    b_hit, y_hit = b_y_hits(frag_type, hits)\n",
-    "\n",
-    "    assert (b_hit) == 2\n",
-    "    assert (y_hit) == 0\n",
-    "    \n",
-    "test_b_y_hits()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "### Extracting Score columns"
+    "To have an efficient data format to store PSMs in the search. We use `numpy`-recarrays and define the utility functions `add_column` and `remove_column` to append and remove data."
    ]
   },
   {
@@ -931,18 +695,27 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Reporting\n",
+    "## Extracting features for scoring\n",
+    "\n",
+    "### Indices\n",
     "\n",
-    "When performing a database search, we need to know what experimental spectrum we are comparing with which database entry. \n",
+    "When performing a database search, we need to know which experimental spectrum we compare with what database entry. \n",
     "We distinguish three indices:\n",
+    "\n",
     "* query_idx\n",
     "* raw_idx\n",
     "* feature_idx\n",
-    "Initially, the get_psms functions accepts experimental data in the form of `query_data`. Here, the `query_idx` refers to the index to `query_data`. However, this might not be the same index as of the raw data. This is due to the implementation of the matching of MS1-features to MS2 spectra. Here we allow multiple matches and implement this by repeating the respective spectra. \n",
     "\n",
-    "We then add the two columns `feature_idx` and `raw_idx` to the psms to later be able to distinguish where the match originated from. Here, `raw_idx` refers to the original spectrum.\n",
+    "Initially, the get_psms function accepts experimental data in the form of `query_data`. Here, the `query_idx` refers to the index to `query_data`. However, this might not be the same index as the raw data. This is due to the implementation of the matching of MS1-features to MS2 spectra. Here we allow multiple matches and implement this by repeating the respective spectrum. \n",
+    "\n",
+    "We then add the two columns `feature_idx` and `raw_idx` to the PSMs to later be able to distinguish where the match originated. In this case, `raw_idx` refers to the original spectrum.\n",
+    "\n",
+    "When not applying feature finding, `raw_idx` and `query_idx` are equivalent.\n",
+    "\n",
+    "\n",
+    "### Features\n",
     "\n",
-    "When not applying feature finding, `raw_idx` and `query_idx` are equivalent."
+    "In the `score`-function we use the pre-filtered PSMs to extract additional columns for scoring such as the offset from theoretical to experimental precursor or the number of b- and y-ion hits."
    ]
   },
   {
@@ -1829,10 +1602,13 @@
       "Converted 12_speed.ipynb.\n",
       "Converted 13_export.ipynb.\n",
       "Converted 13_performance.ipynb.\n",
-      "Converted index.ipynb.\n",
+      "Converted 19_qc.ipynb.\n",
       "Converted Untitled.ipynb.\n",
       "Converted Untitled1.ipynb.\n",
-      "Converted XX_file_formats.ipynb.\n"
+      "Converted Untitled2.ipynb.\n",
+      "Converted Untitled3.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
      ]
     }
    ],
@@ -1845,9 +1621,9 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python [conda env:.conda-alphapept] *",
+   "display_name": "Python [conda env:alphapept] *",
    "language": "python",
-   "name": "conda-env-.conda-alphapept-py"
+   "name": "conda-env-alphapept-py"
   }
  },
  "nbformat": 4,
diff --git a/nbs/07_recalibration.ipynb b/nbs/07_recalibration.ipynb
index 1a667498..4b8e040c 100644
--- a/nbs/07_recalibration.ipynb
+++ b/nbs/07_recalibration.ipynb
@@ -22,13 +22,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "This notebook contains everything to perform a recalibration.\n",
-    "\n",
-    "Current ToDo here:\n",
-    "\n",
-    "- Write introduction / comment more on functions\n",
-    "- Describe approach\n",
-    "- Cite relevant papers"
+    "This notebook contains evertyhing related to recalibration of data."
    ]
   },
   {
@@ -42,12 +36,31 @@
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "import numpy as np"
+    "## Recalibration after search\n",
+    "\n",
+    "### Precursor mass calibration\n",
+    "\n",
+    "Recalibration refers to the computational step where masses are recalibrated after a first search. The identified peptides are used to calculate the deviations of experimental masses to their theoretical masses. After recalibration, a second search with decreased precursor tolerance is performed. \n",
+    "\n",
+    "The recalibration is largely motivated by the software lock mass paper:\n",
+    "\n",
+    "[Cox J, Michalski A, Mann M. Software lock mass by two-dimensional minimization of peptide mass errors. J Am Soc Mass Spectrom. 2011;22(8):1373-1380. doi:10.1007/s13361-011-0142-8](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3231580/)\n",
+    "\n",
+    "Here, mass offsets are piecewise linearly approximated. The positions for approximation need to fulfill a number of criteria (e.g., a minimum number of samples and a minimum distance). The AlphaPept implementation is slightly modified by employing a more general `KNeighborsRegressor`-approach. In brief, the calibration is calculated for each point individually by estimating the deviation from its identified neighbors in n-dimensional space (e.g., retention time, mass, mobility).\n",
+    "\n",
+    "Mor specifically, the algorithm consists of the following steps:\n",
+    "\n",
+    "1. Outlier removal: We remove outliers from the identified peptides by only accepting identifications with a mass offset that is within three standard deviations to the mean.\n",
+    "2. For each point, we perform a neighbors lookup of the next n (default 100) neighbors.\n",
+    "3. Next, we perform a regression based on the neighbors to determine the mass offset. The contribution of each neighbor is weighted by their distance.\n",
+    "\n",
+    "\n",
+    "### Fragment mass calibration\n",
+    "\n",
+    "The fragment mass calibration is based on the identified ions (i.e., b-hits and y-hits). For each hit, we calculate the offset to its theoretical mass. The correction is then applied by taking the median offset in ppm and applying it globally."
    ]
   },
   {
@@ -101,10 +114,9 @@
     "\n",
     "        # Remove outliers for calibration\n",
     "        o_mass_std = np.abs(df['o_mass_ppm'].std())\n",
-    "        o_mass_mean = df['o_mass_ppm'].mean()\n",
+    "        o_mass_median = df['o_mass_ppm'].median()\n",
     "    \n",
-    "        #df_sub = df[~df['feature_idx'].isna()].copy() #Remove unmatched features\n",
-    "        df_sub = df.query('o_mass_ppm < @o_mass_mean+@outlier_std*@o_mass_std and o_mass_ppm > @o_mass_mean-@outlier_std*@o_mass_std').copy()\n",
+    "        df_sub = df.query('o_mass_ppm < @o_mass_median+@outlier_std*@o_mass_std and o_mass_ppm > @o_mass_median-@outlier_std*@o_mass_std').copy()\n",
     "\n",
     "        tree_points = df_sub[cols].values\n",
     "\n",
@@ -229,7 +241,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Calbration with regards to database"
+    "#### Database calibration\n",
+    "\n",
+    "Another way to calibrate the fragment and precursor masses is by directly comparing them to a previously generated theoretical mass database. Here, peaks in the distribution of databases are used to align the experimental masses."
    ]
   },
   {
@@ -240,7 +254,6 @@
    "source": [
     "#export\n",
     "\n",
-    "\n",
     "import alphapept.io\n",
     "import pandas as pd\n",
     "import numpy as np\n",
@@ -444,8 +457,9 @@
       "Converted 11_interface.ipynb.\n",
       "Converted 12_speed.ipynb.\n",
       "Converted 13_export.ipynb.\n",
-      "Converted index.ipynb.\n",
-      "Converted XX_file_formats.ipynb.\n"
+      "Converted 13_performance.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
      ]
     }
    ],
diff --git a/nbs/11_interface.ipynb b/nbs/11_interface.ipynb
index ace609ea..bae9a3de 100644
--- a/nbs/11_interface.ipynb
+++ b/nbs/11_interface.ipynb
@@ -1305,7 +1305,7 @@
     "    theme.append(\"--theme.backgroundColor=#FFFFFF\")\n",
     "    theme.append(\"--theme.secondaryBackgroundColor=#f0f2f6\")\n",
     "    theme.append(\"--theme.textColor=#262730\")\n",
-    "    theme.append(\"--theme.font='sans serif'\")\n",
+    "    theme.append(\"--theme.font=sans serif\")\n",
     "    theme.append(\"--theme.primaryColor=#18212b\")\n",
     "\n",
     "    args = [\"streamlit\", \"run\", file_path, \"--global.developmentMode=false\", \"--server.port=8501\", \"--browser.gatherUsageStats=False\"]\n",
@@ -1340,9 +1340,9 @@
       "Converted 11_interface.ipynb.\n",
       "Converted 12_speed.ipynb.\n",
       "Converted 13_export.ipynb.\n",
-      "Converted index.ipynb.\n",
-      "Converted Untitled.ipynb.\n",
-      "Converted XX_file_formats.ipynb.\n"
+      "Converted 13_performance.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
      ]
     }
    ],
@@ -1355,7 +1355,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   }
diff --git a/nbs/contributing.ipynb b/nbs/contributing.ipynb
new file mode 100644
index 00000000..952f0ae1
--- /dev/null
+++ b/nbs/contributing.ipynb
@@ -0,0 +1,378 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2ce03533",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# default_exp contributing"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "980dd63a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#hide\n",
+    "from nbdev.showdoc import *"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2952ffd3",
+   "metadata": {},
+   "source": [
+    "# How to contribute\n",
+    "Last change: 12-July-2021 MTS\n",
+    "\n",
+    "This notebook describes how you can contribute code. See the `Notes for Programmers` for docstring and test conventions.\n",
+    "\n",
+    "Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.\n",
+    "\n",
+    "You can contribute in many ways:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a892d13c",
+   "metadata": {},
+   "source": [
+    "## Report Bugs\n",
+    "\n",
+    "Report bugs at https://github.com/MannLabs/alphapept/issues.\n",
+    "\n",
+    "If you are reporting a bug, please include:\n",
+    "\n",
+    "* Your operating system name and version.\n",
+    "* Any details about your local setup that might be helpful in troubleshooting.\n",
+    "* Detailed steps to reproduce the bug.\n",
+    "\n",
+    "## Fix Bugs\n",
+    "\n",
+    "Look through the GitHub issues for bugs. Anything tagged with \"bug\"\n",
+    "and \"help wanted\" is open to whoever wants to implement it.\n",
+    "\n",
+    "## Implement Features\n",
+    "\n",
+    "Look through the GitHub issues for features. Anything tagged with \"enhancement\"\n",
+    "and \"help wanted\" is open to whoever wants to implement it.\n",
+    "\n",
+    "## Write Documentation\n",
+    "\n",
+    "AlphaPept could always use more documentation, whether as part of the\n",
+    "official AlphaPept documentation, in docstrings, or even on the web in blog posts,\n",
+    "articles, and such. See below for our docstring convention."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5d20436a",
+   "metadata": {},
+   "source": [
+    "## Submit Feedback\n",
+    "\n",
+    "The best way to send feedback is to file an issue at https://github.com/MannLabs/alphapept/issues.\n",
+    "\n",
+    "If you are proposing a feature:\n",
+    "\n",
+    "* Explain in detail how it would work.\n",
+    "* Keep the scope as narrow as possible, to make it easier to implement.\n",
+    "* Remember that this is a volunteer-driven project, and that contributions\n",
+    "  are welcome :)\n",
+    "\n",
+    "## Get Started!\n",
+    "\n",
+    "Ready to contribute? Here's how to set up `alphapept` for local development.\n",
+    "\n",
+    "1. Fork the `alphapept` repo on GitHub.\n",
+    "2. Clone your fork locally:\n",
+    "\n",
+    "    ```\n",
+    "    git clone git@github.com:your_name_here/alphapept.git\n",
+    "    ```\n",
+    "\n",
+    "3. Follow the installation instructions in the readme to install a alphapept environment.\n",
+    "\n",
+    "4. Create a branch for local development::\n",
+    "\n",
+    "    ```\n",
+    "    git checkout -b name-of-your-bugfix-or-feature\n",
+    "    ```\n",
+    "\n",
+    "   Now you can make your changes locally.\n",
+    "\n",
+    "5. See below in the Notes for Programmers about how to use the nbdev environment.\n",
+    "\n",
+    "6. Commit your changes and push your branch to GitHub::\n",
+    "\n",
+    "    ```\n",
+    "    git add .\n",
+    "    git commit -m \"Your detailed description of your changes.\"\n",
+    "    git push origin name-of-your-bugfix-or-feature\n",
+    "    ```\n",
+    "\n",
+    "7. Submit a pull request through the GitHub website."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "faae7cf9",
+   "metadata": {},
+   "source": [
+    "## PR submission guidelines\n",
+    "\n",
+    "* Keep each PR focused. While it's more convenient, do not combine several unrelated fixes together. Create as many branches as needed to keep each PR focused.\n",
+    "* Do not mix style changes/fixes with \"functional\" changes. It's very difficult to review such PRs, and it most likely gets rejected.\n",
+    "* Do not turn an already submitted PR into your development playground. If after you submitted PR, you discovered that more work is needed - close the PR, do the required work and then submit a new PR. Otherwise, each of your commits requires attention from maintainers of the project.\n",
+    "* If, however, you submitted a PR and received a request for changes, you should proceed with commits inside that PR, so that the maintainer can see the incremental fixes and won't need to review the whole PR again. In the exceptional case where you realize it'll take many many commits to complete the requests, then it's probably best to close the PR, do the work and then submit it again. Again, use common sense where you'd choose one way over another."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2eb448ef",
+   "metadata": {},
+   "source": [
+    "## Notes for Programmers\n",
+    "\n",
+    "### Literal Programming\n",
+    "A key feature is the use of [nbdev](https://github.com/fastai/nbdev). We like to keep the entrance barrier low to attract new coders to contribute to the AlphaPept package. For this, we see nbedv as an ideal tool to document and modify code. To install nbdev use `pip install nbdev` and then install the git hooks in the folder where your GitHub repository is cloned (`nbdev_install_git_hooks`). The key commands for nbdev are:\n",
+    "\n",
+    "* `nbdev_build_lib`: build the library from the notebooks\n",
+    "* `nbdev_test_nbs`: test all notebooks\n",
+    "* `nbdev_clean_nbs`: clean the notebooks (strip from unnecessary metadata)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "df47acc3",
+   "metadata": {},
+   "source": [
+    "### Docstring convention\n",
+    "\n",
+    "The docstring convention we adhere to is from the [Google stylegyuide](https://google.github.io/styleguide/pyguide.html).\n",
+    "\n",
+    "Below a sample docstring (Note the empty space after Args/Returns/Raises):\n",
+    "\n",
+    "```\n",
+    "def sample_function(a_string: str, a_int: int, a_flag: bool) -> str:\n",
+    "    \"\"\"\n",
+    "    Sample function that performs a sample calculation.\n",
+    "    \n",
+    "    Args:\n",
+    "        a_string: A string.\n",
+    "        a_int: An integer.\n",
+    "        a_flag: A flag.\n",
+    "\n",
+    "    Returns:\n",
+    "        A string based on the input.  \n",
+    "        \n",
+    "    Raises:\n",
+    "        ValueError: An error occured when the integer is negative.\n",
+    "        \n",
+    "    \"\"\"\n",
+    "```\n",
+    "\n",
+    "> Note that type hints are highly recommended but not mandatory (see also [PEP484](https://www.python.org/dev/peps/pep-0484/)). As the Legacy codebase had no type hints we are slowly moving towards more type hinting coverage.\n",
+    "\n",
+    "> Note that adding additional elements to the docstring could break how the documentation is displayed. Below is the function from above but exported to show how it would appear in the documentation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c5b9a8f6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#export\n",
+    "def sample_function(a_string: str, a_int: int, a_flag: bool) -> str:\n",
+    "    \"\"\"\n",
+    "    Sample function that performs a sample calculation.\n",
+    "    \n",
+    "    Args:\n",
+    "        a_string: A string.\n",
+    "        a_int: An integer.\n",
+    "        a_flag: A flag.\n",
+    "\n",
+    "    Returns:\n",
+    "        A string based on the input.  \n",
+    "        \n",
+    "    Raises:\n",
+    "        ValueError: An error occured when the integer is negative.\n",
+    "        \n",
+    "    \"\"\""
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c6a94188",
+   "metadata": {},
+   "source": [
+    "## Tests\n",
+    "\n",
+    "In order to make AlphaPept a sustainable package, it is imperative that all functions have tests. This is not only to ensure the proper execution of the function but also for the long run when wanting to keep up-to-date with package updates. For tracking package updates, we rely on [dependabot](https://dependabot.com). For continuous integration, we use GitHub Actions."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d3d8ff35",
+   "metadata": {},
+   "source": [
+    "### Unit Tests\n",
+    "\n",
+    "Within the nbdev notebooks, we try to write small tests for each function. They should be lightweight so that running (and hence testing) the notebooks should not take too long. You define your code in a cell with the `#export`-flag and you can write a text in the following cell. To prevent the cell from showing up in the documentation, you can add a `#hide`-flag. To be flexible and maybe migrate from the notebook tests to `pytest` we write the tests with leading `test_` and the function name and execute it in the cell.\n",
+    "\n",
+    "#### Example\n",
+    "\n",
+    "Cell that defines the code:\n",
+    "\n",
+    "```\n",
+    "#export\n",
+    "\n",
+    "def function_a():\n",
+    "    return 5\n",
+    "```\n",
+    "\n",
+    "Next cell with the test function and calling the test.\n",
+    "```\n",
+    "def test_function_a():\n",
+    "    assert function_a() == 5\n",
+    "\n",
+    "test_function_a()\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e67cd341",
+   "metadata": {},
+   "source": [
+    "### GitHub Actions\n",
+    "\n",
+    "We use GitHub Actions as part of the continuous integration framework.\n",
+    "\n",
+    "Within the repository, the actions are defined as yml files in `./.github/workflows/`. \n",
+    "\n",
+    "Currently, the following actions are present:\n",
+    "\n",
+    "* cla_assistant: Action that runs a check if a contributor has signed the CLA\n",
+    "* gpu_test: Helper to run GPU tests. This is not part of the automated pipeline\n",
+    "* main: Main CI/CD from nbdev. This installs the library and runs all notebooks\n",
+    "* performance_test: This action freezes the environment and runs multiple test cases specified in `test_ci.py`. Used to benchmark new versions.\n",
+    "* quick_test: This action runs the whole AlphaPept pipeline on small test files\n",
+    "* release: This action triggers the installer compilation and creates a release draft\n",
+    "\n",
+    "The `quick_test` and `performance_test` are hooked to the MongoDB, so results are reported here.\n",
+    "\n",
+    "Some actions are executed on each push (e.g. `quick_test`, others need to be triggered manually (e.g. `release`).\n",
+    "\n",
+    "### Adding an integration test\n",
+    "\n",
+    "\n",
+    "\n",
+    "The best way to add an integration test is by specifying a new test in `test_ci.py` and then adding a job to the `performance_test` action. `test_ci.py` contains a file dictionary with links to files. The idea here is as follows: On the local machine, there should be a `BASE_DIR` that contains all files used for running the test cases. If the files are not present, the script will download them with the URL from the file dictionary. To define a test case, one needs to initiate a `TestRun`-class and provide the files to be used. Running a test will always take the current `default_settings.yaml` and modifying the respective files. You can add subsequent analysis (e.g., such as calculating the false discovery FDR for a mixed species experiment by adding a function to the test class.\n",
+    "\n",
+    "To test an integration test locally, simply call the `test_ci.py` with the respective test (e.g. thermo_irt) like so: `python test_ci.py thermo_irt`.\n",
+    "\n",
+    "\n",
+    "### Integrating Actions\n",
+    "\n",
+    "> Note: Unless you are a core developer, there should not be the need to add actions.\n",
+    "\n",
+    "New actions can be added by creating a `.yml` file and placing it in the workflows folder.\n",
+    "We can distinguish local actions, which run on our self-hosted [runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners)  and cloud actions that run on GitHub servers.\n",
+    "\n",
+    "The local runners are meant to run computationally intensive tasks such as the `performance_test` and the `quick_test`, whereas the cloud actions are meant for unit tests.\n",
+    "\n",
+    "> Note: If you fork the repository, you will not be able to execute tests on the local runners. They are restricted to the `MannLabs` repository. \n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1b4b8343",
+   "metadata": {},
+   "source": [
+    "## Test Coverage\n",
+    "\n",
+    "Ideally, we would like to track test coverage. However, this isn't easy when having numba-compiled functions. Any solutions to this are greatly appreciated."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "97177e24",
+   "metadata": {},
+   "source": [
+    "## Linting\n",
+    "\n",
+    "It is planned to include a GitHub action that automatically runs a linter on the code."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ea362281",
+   "metadata": {},
+   "source": [
+    "### Callbacks\n",
+    "\n",
+    "As AlphaPept is intended to be the backend of a tool with GUI, we ideally want to be able to get a progress bar out of the major functions. For this, we can pass a `callback`-argument to the major functions. If the argument is passed, it will return the current progress in the range from 0 to 1.\n",
+    "\n",
+    "### Version bumping\n",
+    "\n",
+    "We are using the python package [`bump2version`](https://github.com/c4urself/bump2version). You can use this to bump the version number (install via `pip install bump2version`). Currently specified is: `bump2version`: (`major`, `minor`, `patch`):\n",
+    "\n",
+    "* e.g.: `bump2version patch` for a patch"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "435cf649",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Converted 00_settings.ipynb.\n",
+      "Converted 01_chem.ipynb.\n",
+      "Converted 02_io.ipynb.\n",
+      "Converted 03_fasta.ipynb.\n",
+      "Converted 04_feature_finding.ipynb.\n",
+      "Converted 05_search.ipynb.\n",
+      "Converted 06_score.ipynb.\n",
+      "Converted 07_recalibration.ipynb.\n",
+      "Converted 08_quantification.ipynb.\n",
+      "Converted 09_matching.ipynb.\n",
+      "Converted 10_constants.ipynb.\n",
+      "Converted 11_interface.ipynb.\n",
+      "Converted 12_speed.ipynb.\n",
+      "Converted 13_export.ipynb.\n",
+      "Converted 13_performance.ipynb.\n",
+      "Converted contributing.ipynb.\n",
+      "Converted file_formats.ipynb.\n",
+      "Converted index.ipynb.\n"
+     ]
+    }
+   ],
+   "source": [
+    "#hide\n",
+    "from nbdev.export import *\n",
+    "notebook2script()"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python [conda env:alphapept] *",
+   "language": "python",
+   "name": "conda-env-alphapept-py"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/nbs/index.ipynb b/nbs/index.ipynb
index 5c837be3..99ca3b39 100644
--- a/nbs/index.ipynb
+++ b/nbs/index.ipynb
@@ -186,11 +186,6 @@
     "This allows us to select different modules. To start the GUI use:\n",
     "* `alphapept gui`\n",
     "\n",
-    "Likewise, to start the watcher use:\n",
-    "* `alphapept watcher`\n",
-    "\n",
-    " > AlphaPept has a watcher module that continuously monitors a target folder and automatically performs file conversion and feature finding on new files.\n",
-    "\n",
     "To run a workflow, use:\n",
     "* `alphapept workflow your_own_workflow.yaml`\n",
     "An example workflow is easily generated by running the GUI once and saving the settings which an be modified on a per-project basis."
@@ -200,18 +195,6 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Analyzing an experiment\n",
-    "This describes the minimal steps to analyze an experiment.\n",
-    "\n",
-    "### GUI\n",
-    "\n",
-    "1. Open the GUI. Drag and drop experimental files and at least one fasta in the `Experiment` tab. \n",
-    "2. Default settings are loaded and can be changed or saved in the `Settings` tab\n",
-    "3. Navigate to the Run panel and click `Start`\n",
-    "\n",
-    "### Investigating the result files\n",
-    "The experimental results will be stored in the corresponding *.hdf-files.\n",
-    "\n",
     "### CMD / Python\n",
     "1. Create a settings-file. This can be done by changing the `default_settings.yaml` in the repository or using the GUI.\n",
     "2. Run the analysis with the new settings file. `alphapept run new_settings.yaml`\n",
@@ -225,6 +208,23 @@
     "```"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Dockerization\n",
+    "\n",
+    "We provide a dockerfile to show how to run AlphaPept in a containerized environment. This allows building powerful pipelines e.g. with AWS Batch. The dockerfile will launch the browser-based GUI. \n",
+    "\n",
+    "1. Install docker\n",
+    "2. Build the dockerfile with `docker build -t docker-alphapept:latest .` This took approximately ~10 minutes on a 2019 MacBookPro)\n",
+    "3. Run the container `docker run -p 8501:8501 -v /Users/mstrauss/Desktop/docker:/home/alphapept/ docker-alphapept:latest alphapept gui`\n",
+    "\n",
+    "Notes: \n",
+    "`-p 8501:8501` forwards the streamlit port to the same port on your machine. You can access AlphaPept via `localhost:8501`\n",
+    "`-v /Users/mstrauss/Desktop/docker:/shared_folder/` mounts the volume to your docker container. E.g., in this case, the local `docker`-folder on the Desktop can be accessed via `/shared_folder/`."
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
diff --git a/requirements.txt b/requirements.txt
index 82893a17..17ebe187 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,17 +9,18 @@ numpy==1.19.4
 pandas==1.2.1
 psutil==5.8.0
 pyyaml==5.3.1
-scikit-learn==0.23.2
+scikit-learn==0.24.2
 scipy==1.6.0
 seaborn==0.11.1
 tables==3.6.1
 tqdm==4.56.0
 SQLAlchemy==1.3.22
-pythonnet==2.5.1
+pythonnet==2.5.2
 pyteomics==4.4.2
 fastcore==1.3.19
 click==7.1.2
-streamlit==0.80.0
+streamlit==0.83.0
 plotly_express==0.4.0
 watchdog==2.1.0
 wget==3.2
+streamlit-aggrid==0.1.9
diff --git a/sandbox/Timing Check.ipynb b/sandbox/Timing Check.ipynb
new file mode 100644
index 00000000..f17c608b
--- /dev/null
+++ b/sandbox/Timing Check.ipynb
@@ -0,0 +1,236 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# AlphaPept Timing Check\n",
+    "\n",
+    "This notebook will run several testfiles and test the timing."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "import alphapept.settings\n",
+    "import sys\n",
+    "sys.path.append(os.path.join(\"..\"))\n",
+    "import test_ci\n",
+    "\n",
+    "try:\n",
+    "    import wget\n",
+    "except ImportError:\n",
+    "    !pip install wget\n",
+    "    import wget\n",
+    "    \n",
+    "tmp_folder = os.path.join(os.getcwd(),'temp/')\n",
+    "\n",
+    "\n",
+    "BASE_DIR = os.path.join(tmp_folder , 'test_files/') # Storarge location for test files\n",
+    "TEST_DIR = os.path.join(tmp_folder, 'test_temp/')\n",
+    "ARCHIVE_DIR = os.path.join(tmp_folder, ' test_archive/')\n",
+    "\n",
+    "MONGODB_USER = ''\n",
+    "MONGODB_URL = ''\n",
+    "\n",
+    "if not os.path.isdir(tmp_folder):\n",
+    "    os.mkdir(tmp_folder)\n",
+    "\n",
+    "test_ci.config_test_paths(BASE_DIR, TEST_DIR, ARCHIVE_DIR, MONGODB_USER, MONGODB_URL)\n",
+    "\n",
+    "timing_check = {}\n",
+    "\n",
+    "def get_timings():\n",
+    "    settings = alphapept.settings.load_settings(os.getcwd() + '/temp/test_temp/results.yaml')\n",
+    "\n",
+    "    total = settings['summary']['timing']['total (min)']\n",
+    "    total_no_db = total - settings['summary']['timing']['create_database (min)']\n",
+    "    total_no_db_ff_raw = total_no_db - settings['summary']['timing']['feature_finding (min)'] - settings['summary']['timing']['import_raw_data (min)'] \n",
+    "    \n",
+    "    return total, total_no_db, total_no_db_ff_raw"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Single Files\n",
+    "\n",
+    "Compare to the single HeLa test files"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Thermo IRT"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "%run -i ../test_ci.py $tmp_folder thermo_irt\n",
+    "\n",
+    "try:\n",
+    "    timing_check['thermo_irt'] = get_timings()\n",
+    "except Exception as e:\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Thermo HeLa"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "%run -i ../test_ci.py $tmp_folder thermo_hela\n",
+    "\n",
+    "\n",
+    "try:\n",
+    "    timing_check['thermo_hela'] = get_timings()\n",
+    "except Exception as e:\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## PXD006109"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "%run -i ../test_ci.py $tmp_folder PXD006109 \n",
+    "\n",
+    "try:\n",
+    "    timing_check['PXD006109'] = get_timings()\n",
+    "except Exception as e:\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Bruker HeLa"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%run -i ../test_ci.py $tmp_folder bruker_hela\n",
+    "\n",
+    "try:\n",
+    "    timing_check['bruker_hela'] = get_timings()\n",
+    "except Exception as e:\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## PXD010012"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "%run -i ../test_ci.py $tmp_folder PXD010012\n",
+    "\n",
+    "try:\n",
+    "    timing_check['PXD010012'] = get_timings()\n",
+    "except Exception as e:\n",
+    "    pass"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "timing_check"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.DataFrame(timing_check).T\n",
+    "\n",
+    "df.columns = ['Full', 'Precomputed database', 'Precomputed database, feature finding and raw import']\n",
+    "display(df)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.10"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": true,
+   "sideBar": true,
+   "skip_h1_title": false,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": false
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/sandbox/timings/AWS_t3a2xlarge.txt b/sandbox/timings/AWS_t3a2xlarge.txt
new file mode 100644
index 00000000..3490fe49
--- /dev/null
+++ b/sandbox/timings/AWS_t3a2xlarge.txt
@@ -0,0 +1,6 @@
+('thermo_irt', 2.6584309418996175, 1.8089467088381448, 0.3122383634249369)
+('thermo_hela', 40.49624100526174, 28.425606218973794, 10.874556668599446)
+('PXD006109', 46.14482674996058, 36.265213449796036, 18.18958926995595)
+('bruker_irt', 2.50820556084315, 1.6971589485804242, 0.2269182721773786)
+('bruker_hela', 131.23155813614528, 120.00698292255402, 15.666107944647472)
+('PXD010012', 790.4507396340371, 781.0386478543282, 85.14979895750686)
diff --git a/sandbox/timings/MacbookPro.txt b/sandbox/timings/MacbookPro.txt
new file mode 100644
index 00000000..2c46a9a0
--- /dev/null
+++ b/sandbox/timings/MacbookPro.txt
@@ -0,0 +1,3 @@
+('thermo_irt', 1.1917218367258708, 0.784204399585724, 0.11588225364685072)
+('thermo_hela', 22.8871666153272, 17.012265102068582, 5.763965630531311)
+('PXD006109', 35.51450748046239, 30.293032280604045, 11.938988967736563)
diff --git a/sandbox/timings/OptiPlex_7080.txt b/sandbox/timings/OptiPlex_7080.txt
new file mode 100644
index 00000000..a19c7ca5
--- /dev/null
+++ b/sandbox/timings/OptiPlex_7080.txt
@@ -0,0 +1,6 @@
+('thermo_irt', 1.240765913327535, 0.8365668892860414, 0.13507523536682137)
+('thermo_hela', 16.31970647176107, 12.40516355832418, 4.395901731650036)
+('PXD006109', 17.4378648241361, 14.006497824192047, 7.822503157456715)
+('bruker_irt', 1.179467236995697, 0.74474808772405, 0.10781133969624845)
+('bruker_hela', 56.92645552953084, 52.982006247838335, 5.600968444347377)
+('PXD010012', 242.1341561595599, 238.844724047184, 24.4392961382866)
diff --git a/sandbox/timings/Workstation_pcmann338.txt b/sandbox/timings/Workstation_pcmann338.txt
new file mode 100644
index 00000000..c4df6439
--- /dev/null
+++ b/sandbox/timings/Workstation_pcmann338.txt
@@ -0,0 +1,6 @@
+('thermo_irt', 1.7255209922790526, 1.2278632005055745, 0.19583288828531903)
+('thermo_hela', 19.392379307746886, 15.182751286029815, 5.435349237918853)
+('PXD006109', 21.237761998176573, 17.75740594069163, 9.304674128691357)
+('bruker_irt', 2.050253899892171, 1.5622381885846455, 0.14791781107584623)
+('bruker_hela', 110.94583313862482, 106.6815674940745, 6.87005113363265)
+('PXD010012', 194.01067462762197, 190.48523796399434, 22.52863572835922)
diff --git a/settings.ini b/settings.ini
index d1607882..c327174b 100644
--- a/settings.ini
+++ b/settings.ini
@@ -7,7 +7,7 @@ author = Maximilian T. Strauss
 author_email = straussmaximilian@gmail.com
 copyright = Mann Labs
 branch = master
-version = 0.3.26
+version = 0.3.27
 min_python = 3.6
 max_python = 4
 audience = Developers


Test patch:
diff --git a/sandbox/timing_test.py b/sandbox/timing_test.py
new file mode 100644
index 00000000..280d1b89
--- /dev/null
+++ b/sandbox/timing_test.py
@@ -0,0 +1,45 @@
+import os
+import alphapept.settings
+import sys
+sys.path.append(os.path.join(".."))
+import test_ci
+
+
+tmp_folder = os.path.join(os.getcwd(),'temp/')
+
+BASE_DIR = os.path.join(tmp_folder , 'test_files/') # Storarge location for test files
+TEST_DIR = os.path.join(tmp_folder, 'test_temp/')
+ARCHIVE_DIR = os.path.join(tmp_folder, ' test_archive/')
+
+MONGODB_USER = ''
+MONGODB_URL = ''
+
+if not os.path.isdir(tmp_folder):
+    os.mkdir(tmp_folder)
+
+test_ci.config_test_paths(BASE_DIR, TEST_DIR, ARCHIVE_DIR, MONGODB_USER, MONGODB_URL)
+
+timing_check = {}
+
+def get_timings(run):
+    settings = alphapept.settings.load_settings(os.getcwd() + '/temp/test_temp/results.yaml')
+
+    total = settings['summary']['timing']['total (min)']
+    total_no_db = total - settings['summary']['timing']['create_database (min)']
+    total_no_db_ff_raw = total_no_db - settings['summary']['timing']['feature_finding (min)'] - settings['summary']['timing']['import_raw_data (min)']
+
+    return run, total, total_no_db, total_no_db_ff_raw
+
+def run_and_time(run):
+    try:
+        test_ci.main(run)
+        with open("timings.txt", "a") as text_file:
+            text_file.write(f"{get_timings(run)}\n")
+    except Exception as e:
+        print(e)
+        pass
+
+if __name__ == "__main__":
+
+    for run in ['thermo_irt','thermo_hela','PXD006109','bruker_irt','bruker_hela','PXD010012']:
+        run_and_time(run)