{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Configuration\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Usage\n", "\n", "Let's assume you started a new project and created a [`jupyter notebook`](https://jupyter.org/)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%ls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Initialize a new project\n", "\n", "The recommended way is to import the `CONFIG` and initialize a new project. However this isn't required just recommended.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyglotaran_extras import CONFIG\n", "\n", "CONFIG.init_project()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This will look up all config files in your home folder, the notebook folders parent folder,\n", "and the notebook folder, combine them and create a new config and schema file in the notebook\n", "folder for you, as well as rediscovering and reloading the config (see [file-lookup](#file-lookup)).\n", "\n", "```{note}\n", "If a config file already exists, the file creation will be skipped in order to not overwrite an\n", "exported custom schema with your own plot functions.\n", "```\n", "\n", "```{admonition} Tip\n", "If you don't want the config to be shown in the cell output, just add a `;` after `CONFIG.init_project()`.\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%ls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Discovering and loading config files\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to only work with one config file you can simply load it.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CONFIG.load(\"../fs_config.yml\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you don't like the way config files are looked up you can manually rediscover them and reload the config.\n", "\n", "```{note}\n", "Note that the reload is only used for demonstration purposes, since the config is autoreloaded before being used (see [auto-reload](#auto-reload))\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CONFIG.rediscover(include_home_dir=False, lookup_depth=3)\n", "CONFIG.reload()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How the config affects plotting\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To demonstrate the difference between not using the config and using the config we create a copy of our `project_config` as well as an `empty_config` (same as not having a config at all).\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyglotaran_extras.config.config import Config\n", "\n", "project_config = CONFIG.model_copy(deep=True)\n", "empty_config = Config()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Default plotting behavior\n", "\n", "By default plots don't do renaming to make it easier to find the underlying data in the dataset.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from glotaran.testing.simulated_data.parallel_spectral_decay import DATASET\n", "\n", "from pyglotaran_extras import plot_data_overview\n", "\n", "CONFIG._reset(empty_config)\n", "\n", "plot_data_overview(DATASET);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Manually adjusting the Plot\n", "\n", "So in order to make you plots ready for a publication you have to set all the labels and\n", "add plot function arguments each time you call it, and keeping things in sync for all plots you generate.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axes = plot_data_overview(DATASET, linlog=True, linthresh=2, use_svd_number=True)\n", "axes[0].set_xlabel(\"Time (ps)\")\n", "axes[0].set_ylabel(\"Wavelength (nm)\")\n", "axes[1].set_xlabel(\"Time (ps)\")\n", "axes[1].set_ylabel(\"\")\n", "axes[2].set_ylabel(\"Singular Value (a.u.)\")\n", "axes[3].set_xlabel(\"Wavelength (nm)\")\n", "axes[3].set_ylabel(\"\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using the plot config\n", "\n", "The same as with manually changing your plots and function arguments can be achieved with plot config,\n", "but it is way less code, keeps all plots in sync for you and spares you from copy pasting the same things all\n", "over the place.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CONFIG._reset(project_config)\n", "\n", "plot_data_overview(DATASET);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Temporarily changing the config\n", "\n", "Let's assume that one dataset uses wavenumbers instead of wavelength as spectral axis.\n", "\n", "You can simply define a `PerFunctionPlotConfig` and call your plot function inside of a `plot_config_context`.\n", "This way you can even override function specific configuration defined in your config file.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyglotaran_extras import PerFunctionPlotConfig\n", "from pyglotaran_extras import plot_config_context\n", "\n", "my_plot_config = PerFunctionPlotConfig(\n", " axis_label_override={\"spectral\": \"Wavenumber (cm$^{-1}$)\"}\n", ")\n", "\n", "with plot_config_context(my_plot_config):\n", " plot_data_overview(DATASET)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using the config for you own function\n", "\n", "The plot config isn't just for our builtin functions but you can also use it with your own custom \n", "functions.\n", "\n", "```{note}\n", "For axes label changing to work with you function the function needs to either take them as argument \n", "or return them.\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyglotaran_extras import use_plot_config\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "@use_plot_config()\n", "def my_plot(swap_axis=False):\n", " fig, ax = plt.subplots()\n", " ax.set_xlabel(\"x\")\n", " ax.set_ylabel(\"y\")\n", " x = np.linspace(-10,10)\n", " y = x**2\n", " if swap_axis is True:\n", " x,y = y,x\n", " ax.plot(x,y,)\n", " return fig, ax\n", "\n", "\n", "my_plot();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For quick prototyping of our config we will just use `PerFunctionPlotConfig` and `plot_config_context`\n", "from the previous section. \n", "\n", "```{note}\n", "If you aren't writing documentation you can just export the config to update the json schema and \n", "change the file directly including editor support 😅.\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_plot_config = PerFunctionPlotConfig(\n", " axis_label_override={\"x\":\"x-axis\",\"y\":\"y-axis\"},\n", " default_args_override={\"swap_axis\":True}\n", ")\n", "\n", "with plot_config_context(my_plot_config):\n", " my_plot();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we are happy with the config we can just look at the corresponding yaml and \n", "copy paste it into a new `my_plot` section inside of the `plotting` section in the config." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_plot_config" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally we can export the config that is aware of our new function `my_plot`, which will:\n", "- Update the existing config (nothing to do in this case)\n", "- Update the schema file to know about `my_plot`\n", "\n", "So the next time we change something in our config it will be able to autocomplete and lint our the content." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CONFIG.export()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## FAQ\n", "\n", "### Do I have to use the config?\n", "\n", "No. Using the config is fully optional, however we recommend using it since it reduces the amount of\n", "code you need to write and lets anybody reading your analysis focus on the science rather than the\n", "python code used to make your plots.\n", "\n", "### What can the configuration be used for?\n", "\n", "The main goal of the config is to configure plot functions and reduce tedious code duplication like:\n", "\n", "- Renaming labels of axes\n", "- Overriding default values to plot function calls\n", "\n", "We try to have sensible default values for our plot functions, but there is no `one fits all` solution.\n", "\n", "Especially since arguments like `linthresh` (determines the range in which a `linlog` plot is linear)\n", "are highly dependent on your data.\n", "\n", "Thus we give you the power to customize the default values to your projects needs, without having\n", "repeating them over and over each time you call a plot function.\n", "\n", "### Can I still change plot labels myself?\n", "\n", "Yes, the config gets applied when a config enabled plot function is called you can still\n", "work with the return figure and axes as you are used to be.\n", "\n", "### Does using a config mean arguments I pass to a function get ignored?\n", "\n", "No, arguments from the config are only used you don't pass an argument.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(file-lookup)=\n", "\n", "### How are config files looked up?\n", "\n", "When you import anything from `pyglotaran_extras` the location of your project is determined.\n", "This location then is used to look for `pygta_config.yaml` and `pygta_config.yml` in the following folders:\n", "\n", "- Your user home directory\n", "- The projects parent folder\n", "- The project folder\n", "\n", "If you don't want to include your home folder or a different lookup depth relative to your project\n", "folder you can use `CONFIG.rediscover`.\n", "If you only want to load the config from a single file you can use `CONFIG.load`.\n", "\n", "(auto-reload)=\n", "\n", "### Do I need to reload the config after changing a file?\n", "\n", "No, the config keeps track of when each config file was last modified and automatically reloads if needed.\n", "\n", "(value-determination)=\n", "\n", "### How is determined what config values to use?\n", "\n", "The config follows the locality and specificity principles.\n", "\n", "#### Locality\n", "\n", "Locality here means that the closer the configuration is to the plot function call the higher its importance.\n", "\n", "Lets consider the example of the default behavior where configs are looked up in the home directory,\n", "projects parent folder and project folder.\n", "When the global `CONFIG` instance is loaded it merges the configs in the following order:\n", "\n", "- Your user home directory\n", "- The projects parent folder\n", "- The project folder\n", "\n", "Where each merge overwrites duplicate values from the config it gets merged into.\n", "\n", "#### Specificity\n", "\n", "For ease of use and reduced duplications, the plot config has a `general` section\n", "that applies to a plot function with use those arguments or plot labels.\n", "\n", "Lets assume that your experimental data use time in picoseconds (ps) and wavelengths in nanometers (nm).\n", "Instead of a defining the label override for each function you can simply it to the general section as\n", "see above and if a function doesn't have it defined itself it also gets applied for this function.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CONFIG.plotting.get_function_config(\"plot_svd\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To demonstrate the effects on the config we will reuse `wavenumber_config` for the usage example.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with plot_config_context(my_plot_config):\n", " plot_svd_config_wavenumber = CONFIG.plotting.get_function_config(\"plot_svd\")\n", "plot_svd_config_wavenumber" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This change is only valid inside of the `plot_config_context` and reset afterwards\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Which arguments and label are used are defined by the following hierarchy.\n", "\n", "- Plot function arguments\n", "- `plot_config_context`\n", "- Global `CONFIG.plotting` instance `function config`\n", "- Global `CONFIG.plotting` instance `general`\n", "\n", "````{note}\n", "For compound functions like `plot_overview` which consist of multiple plot config enabled functions\n", "the `default_args_override` for `plot_overview` will be passed down to the other functions and\n", "override their usage of own `default_args_override` config (if arguments are passed they aren't\n", "default arguments anymore 😅).\n", "Where as `axis_label_override` for the functions config is first applied to the intermediate plots\n", "and `axis_label_override` from `plot_overview` is only applied after that on final plot.\n", "\n", "```mermaid\n", "graph TD\n", " A[plot_overview] --> |\"default_args_override (plot_overview)\"| B[plot_svd]\n", " B --> |\"axis_label_override (plot_svd)\"| C[intermediate plot]\n", " C --> |\"axis_label_override (plot_overview)\"| D[final plot]\n", "```\n", "````\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CONFIG.plotting.get_function_config(\"plot_svd\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What is the `pygta_config.schema.json` file for?\n", "\n", "TLDR; It enables autocomplete and error detection in your editor.\n", "\n", "[JSON-schema](https://json-schema.org/) is a format that is used to describe data structures\n", "including their types in a language agnostic way.\n" ] } ], "metadata": { "kernelspec": { "display_name": "pyglotaran310", "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.10.14" } }, "nbformat": 4, "nbformat_minor": 2 }