NAVARP(1)
π¦ NAVARP β the navarp manual page, bestie edition π
okay so you opened man navarp and now you're here. welcome. this is the navarp(1) page and honestly? it's giving β¨scienceβ¨. let's get into it.
πΈ NAME πΈ
navarp β navarp Documentation. that's it, that's the name, short and iconic π
π ABSTRACT π
Navigation tools for Angle Resolved Photoemission spectroscopy data, aka ARPES, aka the moment your beamtime becomes a whole personality. fr fr it gives you:
- a companion app during ARPES data acquitision (as in beamtime);
- a set of dedicated libs helping to get high quality figures for publication.
By Federico Bisti, University of L'Aquila, Italy. [image] π
π CONTENTS π
πΎ Installation πΎ
Download and install the last Anaconda distribution for Python 3.x from here β you can install it with or without admin privilege (just remember which option you picked, bestie, you'll need it later π).
After installation, few commands must be run in the proper command line interface before being able to use the NavARP package, and this command line interface depends on the operating system.
The command line interface corresponds to the Anaconda Prompt on Windows (that can be found in the Start menu after searching Anaconda Prompt or after opening the Anaconda Navigator), and to the terminal on macOS and Linux. (yes bestie, on mac and linux it's just the terminal, no special app, slay π«Ά)
Regarding macOS, it might happen that after Anaconda installation the default Python version accessible from the terminal is still the default one from macOS (so not the one related to the Anaconda distribution). To check if it is the case, type and run python --version in the terminal. If the word Anaconda is not the printed lines, then Python is not of Anaconda and the installation cannot continue. To fix it, you can change the python environment by typing conda activate. Or (at your risk!), you can try to change your .bash_profile making sure that the Anaconda directory is the first one in the line beginning with export PATH=... . π
β οΈ note the "(at your risk!)" β that's not me being dramatic, that's the manpage. editing your .bash_profile is lowkey the main character move with consequences.
Therefore, launch the proper command line interface and run the following command to install igor (necessary for opening ibw and pxt files) and colorcet (it gives additional perceptually uniform colormap):
conda install --channel conda-forge igor colorcet
Otherwise, add conda-forge channel first and then install the packages:
conda config --append channels conda-forge
conda install igor colorcet
After this step, NavARP must be installed as a package using pip. To do it with admin privilege (depending on the Anaconda installation), run the following command for the last stable version:
pip install https://gitlab.com/fbisti/navarp/-/archive/master/navarp-master.zip
Without administrator privilege run instead:
pip install --user https://gitlab.com/fbisti/navarp/-/archive/master/navarp-master.zip
If you are brave enough you can also install the version still under development by using one of the two commands:
pip install https://gitlab.com/fbisti/navarp/-/archive/develop/navarp-develop.zip
pip install --user https://gitlab.com/fbisti/navarp/-/archive/develop/navarp-develop.zip
π«£ "if you are brave enough" β the manpage said it, not me. the develop branch is the vibe but it's also the vibe.
After this steps NavARP should run directly by typing in the command line interface the following command:
navarp
Instead, for getting familiar with the libraries, launch Jupyter Notebook from the Anaconda Navigator (or in proper command line interface run the command jupyter notebook) and open some examples which you can find in the example folder: π
Finally, if you are familiar with conda you can also create a dedicated enviroment (for example navarp-env) and install only the basic packages using the following commands:
conda create --name navarp-env numpy scipy matplotlib colorcet h5py pyqt=5 jupyter pyyaml click
conda activate navarp-env
conda install -c conda-forge igor
And then you can install it directly with:
pip install https://gitlab.com/fbisti/navarp/-/archive/master/navarp-master.zip
Or maybe you can also have it under control with git, then do:
conda install -c conda-forge git
git clone https://gitlab.com/fbisti/navarp
pip install -e .
The version under development will be installed and any modification in the local directory will affect the installed program. π
π Update π
To update the NavARP project to the last version, run the following command for the last stable version:
pip install https://gitlab.com/fbisti/navarp/-/archive/master/navarp-master.zip
Or for the last version still under development run:
pip install https://gitlab.com/fbisti/navarp/-/archive/develop/navarp-develop.zip
π₯οΈ Usage π₯οΈ
The following documentation is covering only the basic commands in the NavARP GUI (navarp.py). The independent usage of the navarp.utils libraries is instead reported as Jupyter notebooks in the example folder. π§
π Loading data π
NavARP can open the following data types:
- NXarpes file from LOREA/ALBA(ES) and I05/Diamond(GB);
- HDF5 file from SXARPES-ADRESS/PSI(CH);
- NEXUS file from Antares/Soleil(FR) (only deflector scan);
- folder with txt-files from Cassiopee/Soleil(FR);
- txt-file from MBS A1Soft program;
- zip- or txt-file from Scienta-Omicron SES program;
- sp2 file from Specs program;
pxt,ibwanditxfile of Igor-pro as saved by Scienta-Omicron SES program;- txt file in itx format of Igor-pro considering the order (energy, angle, scan);
- yaml file with dictionary to load files (txt, sp2 or pxt) in a folder or to only add metadata to a single file.
To open the file click on the button on the top left and select the single file or, in the case collection of txt-files from Cassiopee/Soleil(FR), select a ROI-file inside the folder. To open a folder with a collection of txt-files from MBS A1Soft, sp2-files from Specs program, pxt- or txt-files from Scienta-Omicron SES program, write first a yaml-file inside that folder with the instruction for opening the file. If for example the folder contains "file_name_001.txt, file_name_002.txt, file_name_003.txt, etc." the related yaml-file inside that folder (called for example file_name.yaml) can be:
# ----------------------------------------------------------------
# Required parameters
# ----------------------------------------------------------------
# file_path, the * must to be exactly where the variable number is
file_path: 'file_name_*.txt'
# scans can be start and step (as below), or start and stop (replace step with stop)
# in the case below, it starts from 20 with a step of 0.5
scans:
start: 20
step: 0.5
# scan_type can be 'tilt', 'polar', 'azimuth', 'deflector' or 'hv'
scan_type: 'azimuth'
# ----------------------------------------------------------------
# Optional parameters
# ----------------------------------------------------------------
# photon energy, it can be specified and other value from the files will be discarded
hv: 60
# analyzer, this define the analyzer geometry, if not specified default values will be used
analyzer:
tht_ap: 50
phi_ap: 0
work_fun: 4.5
deflector: False
πΉοΈ Navigate through the data πΉοΈ
To navigate the data use the Navigation panel at the top right or mouse right-click on the top (bottom) graph to change the energy (scan) value.
The mouse action can be only a single right-click-and-release in a particular region or the right-click can be kept pressed for a smooth movement to a final release point. π±οΈβ¨
Important, the right-click mouse navigation works properly if the "pan/zoom" or "zoom rect" are not selected in the navigation toolbar at the bottom of the figure (the GUI start with neither of the two options selected).
With the mouse cursor on top of a graph (either top or bottom), use the mouse scroll to change the integration region for the iso-value (energy or angle), each scroll step is doubling or halving such region.
π Colormap scale π
Color scale setting can be modified in the Color scale parameters tab on the right (click on the tab name if not already selected).
β Fermi level β
Fermi level can be determined in the Fermi level alignment tab (click on the tab name if not already selected).
The "No Fermi level alignment" option keep the data in kinetic energy (E_{kin}) or, in the case of photon energy scan, in binding energy (E_{bin}) as obtained from E_{bin} = E_{kin} - (h\nu - \Phi), where h\nu is the photon energy and \Phi is the analyzer work function.
"Use Fermi level at" uses the Fermi level inserted in the box below. "Find Fermi level using" looks for the Fermi level following the set-up selected below where: "Energy range" can be all the available one (selecting the radio button on full) or within the horizontal lines in the top panel (selecting cursor instead); the Fermi level can be the same for all the scan image (selecting "Scan value" all) or different values for each scan image, option particularly useful in photon energy scan ("Scan value" radio button on each).
π Transformation in the k-space π
In the k-space transformation tab it is possible to set up all the parameters used for the transformation from angle to momentum scale (the method is based on the kinetic energy of the electrons and so it is independent from the Fermi level alignment).
The from cursor button auto-fills the "Point in angles" with the actual cursor position in the figure.
The set \Gamma just put zeros in k_x and k_y. The inner potential (V0) is 10 by default, it is used only for the photon energy scan and so it must be properly modified only in that case. Use photons = yes to include the photon momentum for the determination of the initial electron momentum. In this case, the "Analyzer" parameters must be properly filled (important, at the present version, the "Analyzer" parameters, and so the photon momentum, are correctly defined only for beamlines LOREA/ALBA(ES) and SXARPES-ADRESS/PSI(CH)). Once everything is properly set, it is possible to click the Iso-E (k) blue button in the "Plot mode". π
π File information π
File information tab is the selected one by default after starting the GUI and it is reporting the information extracted from the data file.
π Credits π
π Development Lead π
- Federico Bisti <federico.bisti@univaq.it>
π«Ά Contributors π«Ά
None yet. Why not be the first?
π Contributing π
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
You can contribute in many ways:
β¨ Types of Contributions β¨
π Report Bugs π
Report bugs at Issue tracker.
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 Issue tracker for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it.
Implement Features π»
Okay bestie, so you wanna build something? Go scroll the Issue tracker for features. Anything that's tagged with "enhancement" and "help wanted" is literally up for grabs β open to whoever wants to implement it. It's giving come get your bag π«Ά
Write Documentation π
NavARP could <em>always</em> use more documentation β whether it's part of the official NavARP docs, in docstrings, or even out on the web in blog posts, articles, and such. Fr fr, docs are the whole vibe. β¨
Submit Feedback π
The best way to send feedback is to file an issue at Issue tracker.
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 navarp for local development. Slay, let's go step by step:
- Fork the navarp repo on GitLab.
- Clone your fork locally:
$ git clone git@gitlab.com:your_name_here/navarp.git
- Install your local copy into a virtualenv. Assuming you have
virtualenvwrapperinstalled, this is how you set up your fork for local development:
$ mkvirtualenv navarp
$ cd navarp/
$ python setup.py develop
- Create a branch for local development:
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
- When you're done making changes, check the code by using
navarp.pyand/or running the examples. - Commit your changes and push your branch to GitLab:
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
- Submit a pull request through the GitLab website.
Pull Request Guidelines π
Before you submit a pull request, check that it meets these guidelines:
- The pull request should include tests.
- If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in
README.rst.
Deploying π’
A reminder for the maintainers on how to deploy. Make sure all your changes are committed (including an entry in CHANGELOG.rst). Then run:
$ bump2version patch # possible: major / minor / patch
$ git push
$ git push --tags
Changelog π
1.0.0: 2021/04/11 π
This is one of the most important release of NavARP, and the project is approaching to a more stable stage. This new version adds very important methods to the NavEntry class which are nicely shown in the now-available examples gallery! In addition, the loading speed has been improved for many text-based files input, and now the krx-MBS format is also supported. Below a list of the main changes divided by modules:
navarp_gui.py:- Added more colorscale maps;
- After closing the program, its window size and position are saved (as a file called
.navarpinside the user home directory) and they will be used to restore the program the next time it will be opened;
utils.navfile:- Added support for
krx-MBSfile format; - Added support for
ibwfile format as saved by Scienta-Omicron SES program; - Added support for
txt-Scientafile format with 3 dimensions; - Added support for igor-pro text file format (saved as
.itxor.txt); - Loading speed of about x3 faster for txt-based file input (
txtfrom Scienta and MBS,sp2from Specs); - Added
set_efermimethod toNavEntryto set the efermi of the entry object; - Added
autoset_efermimethod toNavEntryto automatically found the efermi by curve fitting; - Added
plt_efermi_fitmethod toNavEntryto show theautoset_efermiresults; - Added
set_tht_anmethod toNavEntryto set thetht_anangle of the the entry object for k-space tranformation; - Added
set_kspacemethod toNavEntryto settht_an,phi_an,scans_0andphiused in the k-space tranformation; - Added
isoscan,isoenergy,isoangleandisokmethods in theNavEntrycalling the respectively classes inisomclass. - Extended dictionary in
yaml-file, if loaded overwrite any attribute andefermiandtht_ancan be also directly assigned.
- Added support for
utils.isomclass(new file):- Added
IsoScanclass with the methods to show in a plot, export asNXdataor Igor-pro text file, postprocessing as interpolation, second derivative and curvature; - Added
IsoEnergyclass with the methods to show in a plot, export asNXdataor Igor-pro text file, postprocessing as interpolation, second derivative and curvature; - Added
IsoAngleclass with the methods to show in a plot, postprocessing as second derivative and curvature; - Added
IsoKclass with the methods to show in a plot, export asNXdataor Igor-pro text file, postprocessing as second derivative and curvature;
- Added
utils.fermilevel:- Added fit procedure without using
lmfit,lmfitis no longer required in navarp; - Removed all the previous functions based on
lmfit.
- Added fit procedure without using
utils.ktransf:- Removed all the deprecated functions for k-space transformation.
examplesdirectory:- The old
exampledirectory now is calledexamplesand contains the python script to get the gallery.
- The old
extras.simulation:- Added functions to simulate the graphene band structure probed by deflector and hv scans.
requirements.txtandsetup.py:- Removed
lmfitfrom the required packages.
- Removed
0.18.0: 2020/04/11 πΈ
This version gives the possibility to open four additional file formats.
navarp.py(now renamed asnavarp_gui.py):- Added
clickcommand; - Import
navarp.utils, so now navarp requires installation withpipbefore usage; - Renamed as
navarp_gui.pyso to avoid importing conflicts.
- Added
utils.navfile:- Added loading
MBS A1Softtext-files; - Added case
scan_type=deflectorfor Lorea by readingdefl_angles.
- Added loading
setup.py:- Added
clickcommand.
- Added
0.17.0: 2020/08/14 π¦
This version gives the possibility to open four additional file formats.
navarp.py:- Added
.navarpconfiguration file saved in the local user home - Set the initial default value of k-transf to be without photons contribution
- Added
utils.navfile:- Added loading Scienta-Omicron SES zip and text files;
- Added loading Specs Prodigy
sp2files; - Added loading Igor-pro
pxtfiles as saved by Scienta-Omicron SES; - Added loading folder with
txt,sp2orpxtfiles using instructions in ayamlfile.
setup.py:- Improved requirements avoiding to install
PyQt5if incondait is already present.
- Improved requirements avoiding to install
0.16.0: 2020/08/05 π
This version adds the sphinx-doc integrated in GitLab pages and utils.kinterp.
navarp.py:- Added azimuth
scan_type - Added
arctan2value for sample alignment - Fixed Antares data including the case of
MBSAcquisition_1
- Added azimuth
utils:- Added
kinterp
- Added
β¨ EXAMPLES β¨
Examples gallery πΌοΈ
Below is a gallery of examples
Simulated cone basic analysis π¦
Simple workflow for analyzing a deflector scan data. The same workflow can be applied in the case of manipulator angular scans.
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np
Import the navarp libraries:
from navarp.utils import navfile
Load the data from a file:
file_name = r"nxarpes_simulated_cone.nxs"
entry = navfile.load(file_name)
Out:
instrument_name = simulated
Plot a single slice Ex: scan = 0.5
scan = 0.5
entry.isoscan(scan).show()
[image: plot basic commands] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3cea3d9160>
Fermi level determination
entry.autoset_efermi(energy_range=[93.8, 94.3])
entry.plt_efermi_fit()
print("Fermi level = {:.3f} meV".format(entry.efermi))
print("Energy resolution = {:.0f} meV".format(entry.efermi_fwhm*1000))
print("hv = {:g} eV".format(np.squeeze(entry.hv)))
[image: plot basic commands] [image]
Out:
/build/navarp-uNOccK/navarp-1.0.0/navarp/utils/fermilevel.py:67: RuntimeWarning: divide by zero encountered in true_divide
ddata_s_denergies = ddata_s_denergies/np.abs(data_sum)
/build/navarp-uNOccK/navarp-1.0.0/navarp/utils/fermilevel.py:67: RuntimeWarning: invalid value encountered in true_divide
ddata_s_denergies = ddata_s_denergies/np.abs(data_sum)
Fermi level at 93.8881 eV
Energy resolution = 67.2 meV (i.e. FWHM of the Gaussian shape which, convoluted with a step function, fits the Fermi edge)
Photon energy is now set to 98.4881 eV (instead of 100.0000 eV)
Fermi level = 93.888 meV
Energy resolution = 67 meV
hv = 98.4881 eV
Since now the Fermi level is known, the same plot is automatically aligned Ex: scan = 0.5
scan = 0.5
entry.isoscan(scan).show()
[image: plot basic commands] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9894d00>
Plotting iso-energetic cut Ex: isoenergy cut at ekin = efermi
ebin = 0
debin = 0.005
entry.isoenergy(ebin, debin).show()
[image: plot basic commands] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9893790>
Total running time of the script: ( 0 minutes 3.123 seconds)
Export isoenergy at the Fermi level π€
Simple workflow for exporting the isoenergy at the Fermi level. The data are a deflector scan on graphene as simulated from a third nearest neighbor tight binding model. The same workflow can be applied to any tilt-, polar-, deflector- or hv-scan.
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np
Instead of loading the file as for example:
# from navarp.utils import navfile
# file_name = r"nxarpes_simulated_cone.nxs"
# entry = navfile.load(file_name)
Here we build the simulated graphene signal with a dedicated function defined just for this purpose:
from navarp.extras.simulation import get_tbgraphene_deflector
entry = get_tbgraphene_deflector(
scans=np.linspace(-5., 20., 91),
angles=np.linspace(-25, 6, 400),
ebins=np.linspace(-13, 0.4, 700),
tht_an=-18,
phi_an=0,
hv=120,
gamma=0.05
)
Fermi level autoset
entry.autoset_efermi(scan_range=[-5, 5], energy_range=[115.2, 115.8])
print("Energy of the Fermi level = {:.0f} eV".format(entry.efermi))
print("Energy resolution = {:.0f} meV".format(entry.efermi_fwhm*1000))
entry.plt_efermi_fit()
[image: plot export isoenergy as nxs or itx] [image]
Out:
...and the output that comes back is:
Fermi level at 115.4091 eV
Energy resolution = 138.1 meV (i.e. FWHM of the Gaussian shape which, convoluted with a step function, fits the Fermi edge)
Photon energy is now set to 120.0091 eV (instead of 120.0000 eV)
Energy of the Fermi level = 115 eV
Energy resolution = 138 meV
β¨ Set the k-space for the transformation β¨
okay so now we gotta tell navarp where the heck we are in k-space, and we do that with this little guy:
entry.set_kspace(
tht_p=0.1,
k_along_slit_p=1.7,
scan_p=0,
ks_p=0,
e_kin_p=114.3,
)
and she answers with:
tht_an = -17.979
scan_type = deflector
inn_pot = 14.000
scans_0 = 0.000
phi_an = 0.000
kspace transformation ready
Export the Fermi surface: π first things first, let's just look at it:
entry.isoenergy(0, 0.02).show()
[image: plot export isoenergy as nxs or itx] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9439d60>
Then, to actually export it, it has to be interpolated onto a uniform grid. You do that by defining kbins in the isoenergy definition β that's the number of points for the momentum along the analyzer slit and for the scan. In this case the number will be [1000, 800], and we're gonna call that isoenergy object isoatfermi. sphinx_gallery_thumbnail_number = 3
isoatfermi = entry.isoenergy(0, 0.02, kbins=[1000, 800])
Showing what we're about to save is always the same move, no cap:
isoatfermi.show()
[image: plot export isoenergy as nxs or itx] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce93d71f0>
To export it as an NXdata class of the nexus format, uncomment this line:
# isoatfermi.export_as_nxs('fermimap.nxs')
To export it as an igor-pro text file (itx), uncomment this line:
# isoatfermi.export_as_itx('fermimap.itx')
Total running time of the script: ( 0 minutes 20.597 seconds)
π Export isoenergy at the Fermi level π
Simple little workflow for exporting the isoenergy at the Fermi level. The data are a deflector scan on graphene, simulated from a third nearest neighbor tight binding model. The same workflow can be applied to any tilt-, polar-, deflector- or hv-scan. slay, versatility.
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np
Instead of loading the file, as for example:
# from navarp.utils import navfile
# file_name = r"nxarpes_simulated_cone.nxs"
# entry = navfile.load(file_name)
Here we build the simulated graphene signal with a dedicated function defined just for this purpose:
from navarp.extras.simulation import get_tbgraphene_deflector
entry = get_tbgraphene_deflector(
scans=np.linspace(-0.1, 0.1, 3),
angles=np.linspace(-25, 6, 400),
ebins=np.linspace(-13, 0.4, 700),
tht_an=-18,
phi_an=0,
hv=120,
gamma=0.05
)
π Fermi level autoset π
entry.autoset_efermi(scan_range=[-2, 2], energy_range=[115.2, 115.8])
print("Energy of the Fermi level = {:.0f} eV".format(entry.efermi))
print("Energy resolution = {:.0f} meV".format(entry.efermi_fwhm*1000))
entry.plt_efermi_fit()
[image: plot postprocessing isoscan] [image]
Out:
Fermi level at 115.4065 eV
Energy resolution = 143.7 meV (i.e. FWHM of the Gaussian shape which, convoluted with a step function, fits the Fermi edge)
Photon energy is now set to 120.0065 eV (instead of 120.0000 eV)
Energy of the Fermi level = 115 eV
Energy resolution = 144 meV
β¨ Set the k-space for the transformation β¨
entry.set_kspace(
tht_p=0.1,
k_along_slit_p=1.7,
scan_p=0,
ks_p=0,
e_kin_p=114.3,
)
Out:
tht_an = -17.979
scan_type = deflector
inn_pot = 14.000
scans_0 = 0.000
phi_an = 0.000
kspace transformation ready
π Post processing on the isoscan: π
First of all, let's show it:
entry.isoscan(0).show()
[image: plot postprocessing isoscan] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ceb81dfa0>
The second derivative can be obtained by using sigma in the definition β and sigma defines the extension in points of the Gaussian filter that's used to then get the second derivative. In this case the sigma is different from zero only on the second element, meaning that the derivative will be performed only along the energy axis: sphinx_gallery_thumbnail_number = 3
entry.isoscan(0, sigma=[0, 5]).show()
[image: plot postprocessing isoscan] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9418070>
Only the Gaussian filtered image can be obtained by using sigma again, but this time also specifying order=0, which by default is equal to 2, giving the second derivative as before:
entry.isoscan(0, sigma=[3, 5], order=0).show()
[image: plot postprocessing isoscan] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce944a370>
To export it as an NXdata class of the nexus format, uncomment this line:
# entry.isoscan(0, 0, sigma=[3, 5], order=0).export_as_nxs('fermimap.nxs')
To export it as an igor-pro text file (itx), uncomment this line:
# entry.isoscan(0, 0, sigma=[3, 5], order=0).export_as_itx('fermimap.itx')
Total running time of the script: ( 0 minutes 1.881 seconds)
π¦ Interpolation on Graphene deflector scan π¦
Simple workflow for the interpolation of data along a generic path in the k-space, starting from its isoenergy cuts. The data are a deflector scan on graphene, simulated from a third nearest neighbor tight binding model. The same workflow can be applied to any tilt-, polar-, deflector- or hv-scan. one workflow, every scan type, iykyk.
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np
import matplotlib.pyplot as plt
Instead of loading the file, as for example:
# from navarp.utils import navfile
# file_name = r"nxarpes_simulated_cone.nxs"
# entry = navfile.load(file_name)
Here we build the simulated graphene signal with a dedicated function defined just for this purpose:
from navarp.extras.simulation import get_tbgraphene_deflector
entry = get_tbgraphene_deflector(
scans=np.linspace(-5., 20., 91),
angles=np.linspace(-25, 6, 400),
ebins=np.linspace(-13, 0.4, 700),
tht_an=-18,
phi_an=0,
hv=120,
gamma=0.05
)
π Fermi level autoset π
entry.autoset_efermi(scan_range=[-5, 5], energy_range=[115.2, 115.8])
print("Energy of the Fermi level = {:.0f} eV".format(entry.efermi))
print("Energy resolution = {:.0f} meV".format(entry.efermi_fwhm*1000))
entry.plt_efermi_fit()
[image: plot interpolation gr deflector scan] [image]
Out:
Fermi level at 115.4094 eV
Energy resolution = 137.5 meV (i.e. FWHM of the Gaussian shape which, convoluted with a step function, fits the Fermi edge)
Photon energy is now set to 120.0094 eV (instead of 120.0000 eV)
Energy of the Fermi level = 115 eV
Energy resolution = 138 meV
πΈ Check for the Fermi level alignment πΈ
entry.isoscan(scan=0, dscan=0).show(yname='eef')
[image: plot interpolation gr deflector scan] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9495d90>
β Plotting iso-energetic cut at ekin = efermi β
entry.isoenergy(0, 0.02).show()
[image: plot interpolation gr deflector scan] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9f502e0>
π§ Set the k-space for the transformation π§
entry.set_kspace(
tht_p=0.1,
k_along_slit_p=1.7,
scan_p=0,
ks_p=0,
e_kin_p=114.3,
)
Out:
tht_an = -17.979
scan_type = deflector
inn_pot = 14.000
scans_0 = 0.000
phi_an = 0.000
kspace transformation ready
and then check the isoenergy at the Fermi level:
entry.isoenergy(0, 0.02).show()
[image: plot interpolation gr deflector scan] [image]
Out:
<matplotlib.collections.QuadMesh object at 0x7f3ce9349c70>
π Define the interpolation path points π
kbins = 900
k_GK = 1.702
k_pts_xy = np.array([
[0, 0],
[k_GK, 0],
[k_GK*np.cos(np.pi/3), k_GK*np.sin(np.pi/3)+0.05],
[0, 0]
])
kx_pts = k_pts_xy[:, 0]
ky_pts = k_pts_xy[:, 1]
klabels = [
r'$\Gamma$',
r'$\mathrm{K}$',
r'$\mathrm{K}^{\prime}$',
r'$\Gamma$'
]
and show them on the isoenergy at the Dirac point energy:
entry.isoenergy(-1.1, 0.02).show()
plt.plot(kx_pts, ky_pts, '-+')
[image: plot interpolation gr deflector scan] [image]
Out:
[<matplotlib.lines.Line2D object at 0x7f3ce94a5f70>]
Run the interpolation by defining an isok:
isok = entry.isok(kx_pts, ky_pts, klabels)
Show the final results with the executed path on the isoenergy: sphinx_gallery_thumbnail_number = 6
fig, axs = plt.subplots(1, 2)
entry.isoenergy(0, 0.02).show(ax=axs[0])
isok.path_show(axs[0], 'k', 'k', xytext=(8, 8))
qmesh = isok.show(ax=axs[1])
fig.tight_layout()
fig.colorbar(qmesh)
[image: plot interpolation gr deflector scan] [image]
Out:
<matplotlib.colorbar.Colorbar object at 0x7f3ce943a190>
Total running time of the script: ( 0 minutes 21.724 seconds)
π The old way analysis π
Old workflow for analyzing deflector scan data. This workflow uses all the functions in the most explicit way possible, without using any entry method. This is NOT a recommended workflow β but it can help you understand what's actually going on behind the entry methods. lore drop, honestly.
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np
import matplotlib.pyplot as plt
Import the navarp libraries:
from navarp.utils import navfile, fermilevel, navplt, ktransf, isocut
Load the data from a file:
file_name = r"nxarpes_simulated_cone.nxs"
entry = navfile.load(file_name)
Out:
instrument_name = simulated
Plot a single slice. Ex: scan = 0.5
scan = 0.5
scan_ind = np.argmin(abs(entry.scans-scan))
isoscan = isocut.maps_sum(scan, 0, entry.scans, entry.data)
qmisoscan = navplt.pimage(
entry.angles, entry.energies, isoscan, cmap='binary', style='tht_ekin')
[image: plot cone old way] [image]
Fermi level determination
energy_range = [93.8, 94.3]
data_sum = np.sum(entry.data, axis=tuple([0, 1]))
popt = fermilevel.fit_efermi(entry.energies, data_sum, energy_range)
efermi, res = popt[0], popt[1]*4
fig, axfit = plt.subplots(1) axfit.axvline(popt[0]) axfit.plot(entry.energies, data_sum, '+') axfit.plot(entry.energies, fermilevel.fermi_fun(entry.energies, *popt), 'r-') axfit.set_xlabel(r'Kinetic Energy (eV)') axfit.set_xlim(energy_range) dvis = data_sum[ (entry.energies >= energy_range[0]) & (entry.energies <= energy_range[1]) ] dvis_min = dvis.min() dvis_max = dvis.max() dvis_delta = dvis_max - dvis_min axfit.set_ylim( dvis_min - dvis_delta*0.05, dvis_max + dvis_delta*0.05 )
# Overwrite hv from the derived fermi level and the known work function hv_from_file = np.copy(entry.hv) entry.hv = np.array([efermi + entry.analyzer.work_fun]) print( "hv = {:g} eV (from the file was {:g})".format( np.squeeze(entry.hv), np.squeeze(hv_from_file)) ) print("Energy resolution = {:.0f} meV".format(res*1000))
*[image: plot cone old way]* *[image]*
**Out:**
/build/navarp-uNOccK/navarp-1.0.0/navarp/utils/fermilevel.py:67: RuntimeWarning: divide by zero encountered in true_divide ddata_s_denergies = ddata_s_denergies/np.abs(data_sum) /build/navarp-uNOccK/navarp-1.0.0/navarp/utils/fermilevel.py:67: RuntimeWarning: invalid value encountered in true_divide ddata_s_denergies = ddata_s_denergies/np.abs(data_sum) hv = 98.4881 eV (from the file was 100) Energy resolution = 67 meV
Okay bestie, quick heads up before you panic at those `RuntimeWarning`s β they're literally just numpy being dramatic about a divide-by-zero. It's giving messy but it's fine. π
---
**Plot a single slice with fermi level alignment** β Ex: `scan = 0.5`
scan = 0.5 scan_ind = np.argmin(abs(entry.scans-scan)) isoscan = isocut.maps_sum(scan, 0, entry.scans, entry.data)
qmisoscan = navplt.pimage( entry.angles, entry.energies-efermi, isoscan, cmap='magma_r', style='tht_eef')
*[image: plot cone old way]* *[image]*
**Plotting iso-energetic cut** β Ex: isoenergy cut at `ekin = efermi`
ekin = efermi dekin = 0.005 isoev = isocut.maps_sum(ekin, dekin, entry.energies, entry.data) qmisoev = navplt.pimage( entry.angles, entry.scans, isoev, cmap='magma_r', style='tht_phi', cmapscale='linear')
*[image: plot cone old way]* *[image]*
`Total running time of the script: ( 0 minutes 1.861 seconds)`
---
### Graphene deflector scan
<rainbow>Simple workflow for analyzing a deflector scan data of graphene</rainbow> as simulated from a third nearest neighbor tight binding model. And like, the same workflow can be applied to any tilt-, polar- or deflector-scan. Periodt. π«Ά
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np import matplotlib.pyplot as plt
Instead of loading the file as for example:
from navarp.utils import navfile
file_name = r"nxarpes_simulated_cone.nxs"
entry = navfile.load(file_name)
Here we build the simulated graphene signal with a dedicated function defined just for this purpose:
from navarp.extras.simulation import get_tbgraphene_deflector
entry = get_tbgraphene_deflector( scans=np.linspace(-5., 5., 51), angles=np.linspace(-7, 7, 300), ebins=np.linspace(-3.3, 0.4, 450), tht_an=-18, phi_an=0, hv=120 )
**Plot a single analyzer image at `scan = 0`**
First I have to extract the isoscan from the entry, so I use the isoscan method of entry:
iso0 = entry.isoscan(scan=0, dscan=0)
Then to plot it using the 'show' method of the extracted `iso0`:
iso0.show(yname='ekin')
*[image: plot gr deflector scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce94162b0>
Or by string concatenation, directly as:
entry.isoscan(scan=0, dscan=0).show(yname='ekin')
*[image: plot gr deflector scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce917f160>
**Fermi level determination**
The initial guess for the binding energy is: `ebins = ekins - (hv - work_fun)`. <lavender>However</lavender>, the better way is to proper set the Fermi level first and then derives everything form it. The Fermi level can be given directly as a value using:
entry.set_efermi(114.8)
Or it can be detected from a fit using the method `autoset_efermi`. In both cases the binding energy and the photon energy will be updated consistently. <purple>Note that the work function depends on the beamline or laboratory. If not specified is 4.5 eV.</purple>
entry.autoset_efermi(scan_range=[-5, 5], energy_range=[115.2, 115.8]) print("Energy of the Fermi level = {:.0f} eV".format(entry.efermi)) print("Energy resolution = {:.0f} meV".format(entry.efermi_fwhm*1000))
entry.plt_efermi_fit()
*[image: plot gr deflector scan]* *[image]*
**Out:**
Fermi level at 115.4016 eV Energy resolution = 56.6 meV (i.e. FWHM of the Gaussian shape which, convoluted with a step function, fits the Fermi edge) Photon energy is now set to 120.0016 eV (instead of 120.0000 eV) Energy of the Fermi level = 115 eV Energy resolution = 57 meV
**Plot a single analyzer image at `scan = 0` with the Fermi level aligned**
entry.isoscan(scan=0, dscan=0).show(yname='eef')
*[image: plot gr deflector scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce936e100>
**Plotting iso-energetic cut at `ekin = efermi`**
entry.isoenergy(0).show()
*[image: plot gr deflector scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce93f4d00>
**Plotting in the reciprocal space (k-space)**
I have to define first the reference point to be used for the transformation. Meaning a point in the angular space which I know it correspond to a particular point in the k-space. In this case the graphene Dirac-point which is at `ekin = 114.3 eV`, in angle is at `(tht_p, phi_p) = (0.1, 0)` and in the k-space has to correspond to `(kx, ky) = (1.7, 0)`.
entry.set_kspace( tht_p=0.1, k_along_slit_p=1.7, scan_p=0, ks_p=0, e_kin_p=114.3, )
**Out:**
tht_an = -17.979 scan_type = deflector inn_pot = 14.000 scans_0 = 0.000 phi_an = 0.000 kspace transformation ready
Once it is set, all the isoscan or isoenergy extracted from the entry will now get their proper k-space scales:
entry.isoscan(0).show()
*[image: plot gr deflector scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce944da30>
entry.isoenergy(0).show()
*[image: plot gr deflector scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce92c0c40>
I can also place together in a single figure different images:
`sphinx_gallery_thumbnail_number = 5`
fig, axs = plt.subplots(1, 2)
entry.isoscan(0).show(ax=axs[0]) entry.isoenergy(0).show(ax=axs[1])
plt.tight_layout()
*[image: plot gr deflector scan]* *[image]*
**Many other options:**
fig, axs = plt.subplots(2, 2)
scan = 0.8 dscan = 0.05 ebin = -0.4 debin = 0.05
entry.isoscan(scan, dscan).show(ax=axs[0][0], xname='tht', yname='ekin') entry.isoscan(scan, dscan).show(ax=axs[0][1], cmap='binary')
axs[0][0].axhline(ebin-debin+entry.efermi) axs[0][0].axhline(ebin+debin+entry.efermi)
axs[0][1].axhline(ebin-debin) axs[0][1].axhline(ebin+debin)
entry.isoenergy(ebin, debin).show( ax=axs[1][0], xname='tht', yname='phi', cmap='cividis') entry.isoenergy(ebin, debin).show( ax=axs[1][1], cmap='magma', cmapscale='log')
axs[1][0].axhline(scan, color='w')
x_note = 0.05 y_note = 0.98
for ax in axs[0][:]: ax.annotate( "$scan : = : {} \pm {} ; ^\circ$".format(scan, dscan), (x_note, y_note), xycoords='axes fraction', size=8, rotation=0, ha="left", va="top", bbox=dict( boxstyle="round", fc='w', alpha=0.65, edgecolor='None', pad=0.05 ) )
for ax in axs[1][:]: ax.annotate( "$E-E_F : = : {} \pm {} ; eV$".format(ebin, debin), (x_note, y_note), xycoords='axes fraction', size=8, rotation=0, ha="left", va="top", bbox=dict( boxstyle="round", fc='w', alpha=0.65, edgecolor='None', pad=0.05 ) )
plt.tight_layout()
*[image: plot gr deflector scan]* *[image]*
`Total running time of the script: ( 0 minutes 5.859 seconds)`
---
### Graphene hv scan
<rainbow>Simple workflow for analyzing a photon energy scan data of graphene</rainbow> as simulated from a third nearest neighbor tight binding model. The same workflow can be applied to any photon energy scan. π
Import the "fundamental" python libraries for a generic data analysis:
import numpy as np import matplotlib.pyplot as plt
Instead of loading the file as for example:
from navarp.utils import navfile
file_name = r"nxarpes_simulated_cone.nxs"
entry = navfile.load(file_name)
Here we build the simulated graphene signal with a dedicated function defined just for this purpose:
from navarp.extras.simulation import get_tbgraphene_hv
entry = get_tbgraphene_hv( scans=np.arange(90, 150, 2), angles=np.linspace(-7, 7, 300), ebins=np.linspace(-3.3, 0.4, 450), tht_an=-18, )
**Plot a single analyzer image at `scan = 90`**
First I have to extract the isoscan from the entry, so I use the isoscan method of entry:
iso0 = entry.isoscan(scan=90)
Then to plot it using the 'show' method of the extracted `iso0`:
iso0.show(yname='ekin')
*[image: plot gr hv scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce8f78be0>
Or by string concatenation, directly as:
entry.isoscan(scan=90).show(yname='ekin')
*[image: plot gr hv scan]* *[image]*
**Out:**
<matplotlib.collections.QuadMesh object at 0x7f3ce9164be0>
**Fermi level determination**
The initial guess for the binding energy is: `ebins = ekins - (hv - work_fun)`. However, the better way is to proper set the Fermi level first and then derives everything form it. In this case the Fermi level kinetic energy is changing along the scan since it is a photon energy scan. So to set the Fermi level I have to give an array of values corresponding to each photon energy. By definition I can give:
efermis = entry.hv - entry.analyzer.work_fun entry.set_efermi(efermis)
Or I can use a method for its detection, but in this case, it is important to give a proper energy range for each photon energy. For example for each photon a good range is within `0.4 eV` around the photon energy minus the analyzer work function:
energy_range = ( (entry.hv[:, None] - entry.analyzer.work_fun) + np.array([-0.4, 0.4])[None, :])
okay bestie, picking up right where we left off β the Fermi-level autoset saga continues π
β¨
entry.autoset_efermi(energy_range=energy_range)
Out: πΈ
scan(eV) efermi(eV) FWHM(meV) new hv(eV) 90.0000 85.4000 58.5 90.0000 92.0000 87.3999 58.8 91.9999 94.0000 89.3995 59.6 93.9995 96.0000 91.4000 58.7 96.0000 98.0000 93.4004 58.5 98.0004 100.0000 95.3997 59.6 99.9997 102.0000 97.4000 59.1 102.0000 104.0000 99.3997 60.1 103.9997 106.0000 101.4007 58.5 106.0007 108.0000 103.4006 58.5 108.0006 110.0000 105.4002 59.4 110.0002 112.0000 107.4006 57.4 112.0006 114.0000 109.4006 58.9 114.0006 116.0000 111.4004 58.9 116.0004 118.0000 113.3998 59.5 117.9998 120.0000 115.4002 59.4 120.0002 122.0000 117.4009 57.7 122.0009 124.0000 119.4004 58.0 124.0004 126.0000 121.4001 58.9 126.0001 128.0000 123.4002 59.0 128.0002 130.0000 125.4004 58.6 130.0004 132.0000 127.4006 58.9 132.0006 134.0000 129.4005 57.3 134.0005 136.0000 131.4002 59.1 136.0002 138.0000 133.4001 58.7 138.0001 140.0000 135.4000 58.1 140.0000 142.0000 137.4007 57.6 142.0007 144.0000 139.4005 58.5 144.0005 146.0000 141.4004 57.8 146.0004 148.0000 143.4001 60.0 148.0001
So in both cases, the binding energy and the photon energy get updated consistently, no cap. Quick note though: the work function depends on your beamline or lab. If you don't specify it, it defaults to `4.5 eV`.
To vibe-check the Fermi level detection, you can peek at each photon energy. Here I'm only showing the first 10 photon energies, because we're not doing a whole marathon:
for scan_i in range(10): print("hv = {} eV, E_F = {:.0f} eV, Res = {:.0f} meV".format( entry.hv[scan_i], entry.efermi[scan_i], entry.efermi_fwhm[scan_i]*1000 )) entry.plt_efermi_fit(scan_i=scan_i)
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
Out: π
hv = 89.99998283633579 eV, E_F = 85 eV, Res = 59 meV hv = 91.9998627668236 eV, E_F = 87 eV, Res = 59 meV hv = 93.99949668680733 eV, E_F = 89 eV, Res = 60 meV hv = 96.00002470385328 eV, E_F = 91 eV, Res = 59 meV hv = 98.00040009140567 eV, E_F = 93 eV, Res = 59 meV hv = 99.99973916893491 eV, E_F = 95 eV, Res = 60 meV hv = 101.99995912743783 eV, E_F = 97 eV, Res = 59 meV hv = 103.99974209235835 eV, E_F = 99 eV, Res = 60 meV hv = 106.00073875670226 eV, E_F = 101 eV, Res = 58 meV hv = 108.0006350982113 eV, E_F = 103 eV, Res = 59 meV
**Plot a single analyzer image at scan = 110 with the Fermi level aligned** β iconic.
entry.isoscan(scan=110).show(yname='eef')
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π¦
Out: β¨
<matplotlib.collections.QuadMesh object at 0x7f3ce910a3a0>
**Plotting iso-energetic cut at ekin = efermi** β slay.
entry.isoenergy(0).show()
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
Out: π«
<matplotlib.collections.QuadMesh object at 0x7f3ce92906d0>
**Plotting in the reciprocal space (k-space)** β this is where it gets main character.
First I gotta define the reference point for the transformation. Like, a point in angular space that I know maps to a particular point in k-space. For this graphene Dirac-point, at `hv = 120` it's at `ekin = 114.3 eV` and `tht_p = -0.6` (see the figure below), and in k-space that has to correspond to `kx = 1.7`.
hv_p = 120
entry.isoscan(scan=hv_p, dscan=0).show(yname='ekin', cmap='cividis')
tht_p = -0.6 e_kin_p = 114.3 plt.axvline(tht_p, color='w') plt.axhline(e_kin_p, color='w')
entry.set_kspace( tht_p=tht_p, k_along_slit_p=1.7, scan_p=0, ks_p=0, e_kin_p=e_kin_p, inn_pot=14, p_hv=True, hv_p=hv_p, )
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π§
Out: π₯
tht_an = -18.040 scan_type = hv inn_pot = 14.000 phi_an = 0.000 k_perp_slit_for_kz = 0.000 kspace transformation ready
Once that's set, every isoscan or iscoenergy pulled from the entry gets its proper k-space scales, periodt:
entry.isoscan(120).show()
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
Out: π
<matplotlib.collections.QuadMesh object at 0x7f3ce9244340>
sphinx_gallery_thumbnail_number = 17
entry.isoenergy(0).show(cmap='cividis')
- <rainbow>[image: plot gr hv scan] [image]</rainbow> β
Out: π
<matplotlib.collections.QuadMesh object at 0x7f3ce9324b20>
You can also put different images together in a single figure, bestie:
fig, axs = plt.subplots(1, 2)
entry.isoscan(120).show(ax=axs[0]) entry.isoenergy(-0.9).show(ax=axs[1])
plt.tight_layout()
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
**Many other options**, because we love choices:
fig, axs = plt.subplots(2, 2)
scan = 110 dscan = 0 ebin = -0.9 debin = 0.01
entry.isoscan(scan, dscan).show(ax=axs[0][0], xname='tht', yname='ekin') entry.isoscan(scan, dscan).show(ax=axs[0][1], cmap='binary')
axs[0][1].axhline(ebin-debin) axs[0][1].axhline(ebin+debin)
entry.isoenergy(ebin, debin).show( ax=axs[1][0], xname='tht', yname='phi', cmap='cividis') entry.isoenergy(ebin, debin).show( ax=axs[1][1], cmap='magma', cmapscale='log')
axs[1][0].axhline(scan, color='w', ls='--') axs[0][1].axvline(1.7, color='r', ls='--') axs[1][1].axvline(1.7, color='r', ls='--')
x_note = 0.05 y_note = 0.98
for ax in axs[0][:]: ax.annotate( "$scan : = : {} eV$".format(scan, dscan), (x_note, y_note), xycoords='axes fraction', size=8, rotation=0, ha="left", va="top", bbox=dict( boxstyle="round", fc='w', alpha=0.65, edgecolor='None', pad=0.05 ) )
for ax in axs[1][:]: ax.annotate( "$E-E_F : = : {} \pm {} ; eV$".format(ebin, debin), (x_note, y_note), xycoords='axes fraction', size=8, rotation=0, ha="left", va="top", bbox=dict( boxstyle="round", fc='w', alpha=0.65, edgecolor='None', pad=0.05 ) )
plt.tight_layout()
- <rainbow>[image: plot gr hv scan] [image]</rainbow> π
Total running time of the script: ( 0 minutes 7.611 seconds)
---
# β¨ API REFERENCE β¨
- `genindex` π
- `modindex` π
- `search` π
---
# π«Ά PROJECT ON GITLAB π«Ά
- Source repository π«Ά
- Issue tracker π«Ά
---
# π AUTHOR π
**Federico Bisti** β the absolute legend π
---
# π COPYRIGHT π
2021, Federico Bisti
---
and that's the whole page, bestie β `NAVARP(1)`, `Sep 30, 2021`. byeee π
β¨
π¦ where this page comes from
- debian bookworm: python3-navarp 1.3.0-1 Β· GPL-3.0-or-later
- debian trixie: python3-navarp 1.6.0-4 Β· GPL-3.0-or-later
- ubuntu jammy: python3-navarp 1.3.0-1 Β· GPL-3.0-or-later
- ubuntu noble: python3-navarp 1.6.0-2 Β· GPL-3.0-or-later
- ubuntu questing: python3-navarp 1.6.0-4
- ubuntu resolute: python3-navarp 1.6.0-4build1 Β· GPL-3.0-or-later
- ubuntu stonking: python3-navarp 1.6.0-4build1 Β· GPL-3.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms