{ "cells": [ { "cell_type": "markdown", "id": "f59a21dd", "metadata": {}, "source": [ "# Visualization Tutorial for CAMEA\n", "In this tutorial script data from the CAMEA multiplexing spectrometer at SINQ PSI will be loaded, converted and visualized using an interactive 3D visualization. All of this requires you to already have installed the software package MJOLNIR in you current environment. For instructions on this, see the provided tutorial overview.\n", "\n", "## Script build up\n", "This tutorial script is split into multiple parts where key functions are called one after the other. Some of these parts are supposed to be call multiple times, e.g. plotting of data, while others need only to be run once as an initialization of packages and loading of data, these will be signified with a comment in the top of the cell\n", "\n", "## Nice to know\n", "All plots generated directly through MJOLNIR shows the current mouse position in the relevant space through a tool tip in the matplotlib window. It is most likely lokated in the top right corner. If you cannot find it, try to expand the windows size." ] }, { "cell_type": "markdown", "id": "ca8a57b5", "metadata": {}, "source": [ "## Check of package installation\n", "First of all, a check of the packages needed for this tutorial is performed by simply importing MJOLNIR and printing the current version of the software. If this step succeeds all of MJOLNIR's dependencies have an overwhelming probability of also being installed correctly, e.g. Matplotlib or numpy." ] }, { "cell_type": "code", "execution_count": 1, "id": "0323d18e", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:07.691521Z", "iopub.status.busy": "2023-05-01T08:23:07.691521Z", "iopub.status.idle": "2023-05-01T08:23:07.718643Z", "shell.execute_reply": "2023-05-01T08:23:07.717767Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Your version is 1.3.0 and MJOLNIR is correctly installed.\n" ] } ], "source": [ "import sys\n", "sys.path.append(r'C:\\Users\\lass_j\\Documents\\Software\\MJOLNIR')\n", "# Run once\n", "try:\n", " import MJOLNIR\n", " print('Your version is',MJOLNIR.__version__,'and MJOLNIR is correctly installed.')\n", "except ImportError:\n", " print('MJOLNIR could not be found. Either the current environment is different from the one into which MJOLNIR was installed, or the MJOLNIR installation failed. Please contact the instructors :-/ .')\n", "\n", " " ] }, { "cell_type": "markdown", "id": "a1196873", "metadata": {}, "source": [ "## Helper functions\n", "In this tutorial some functions need to be used which are not standard. These are simply given in the following cell" ] }, { "cell_type": "code", "execution_count": 2, "id": "86495ec2", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:07.720685Z", "iopub.status.busy": "2023-05-01T08:23:07.720685Z", "iopub.status.idle": "2023-05-01T08:23:07.733653Z", "shell.execute_reply": "2023-05-01T08:23:07.733151Z" } }, "outputs": [], "source": [ "# Run once\n", "\n", "### helper function to print the values of your fit\n", "def PrintParameters(vals,err):\n", " if not np.all(np.isfinite(err)):\n", " for par in range(len(vals)):\n", " print('{} +- {}'.format(vals[par],err[par],[par]))\n", " return\n", " errdecimal = -(np.floor(np.log10(np.abs(err))).astype(int))\n", " \n", " errdecimal+= (np.floor(err*10**errdecimal)==1).astype(int)\n", " print(errdecimal)\n", " for par in range(len(vals)):\n", " print('{} +- {}'.format(np.round(vals[par],decimals=errdecimal[par]),np.round(err[par],decimals=errdecimal[par])))\n", " \n", "def Gaussian(x,A,mu,sigma,B):\n", " return A*np.exp(-np.power(x-mu,2.0)/(2.0*sigma**2))+B\n", " " ] }, { "cell_type": "markdown", "id": "29d27b4d", "metadata": {}, "source": [ "## Loading of data\n", "We are now in a position where data can be loaded. One experiment usually results in multiple data files each containing a specific scan. These different data files are then to be loaded and visualized together. This is taken care of by the DataSet object. To load the data a list of file locations has to be provided to this DataSet object, but because we as scientists are lazy, MJOLNIR contains a function to generate these lists from the folder path and the file numbers.\n", "\n", "In the cell below change the folder to be the correct for you. Running the snippet below might take some time as a lot of data files are loaded" ] }, { "cell_type": "code", "execution_count": 3, "id": "7eca0219", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:07.736682Z", "iopub.status.busy": "2023-05-01T08:23:07.735687Z", "iopub.status.idle": "2023-05-01T08:23:11.554812Z", "shell.execute_reply": "2023-05-01T08:23:11.554045Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DataFiles loaded: C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001371.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001372.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001373.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001374.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001375.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001376.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001377.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001378.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001379.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001380.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001381.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001382.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001383.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001384.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001385.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001386.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001387.hdf\n", "C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data\\camea2021n001388.hdf\n" ] } ], "source": [ "# Run once\n", "from MJOLNIR.Data import DataSet\n", "from MJOLNIR._tools import fileListGenerator\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "plt.rcParams['figure.dpi'] = 200\n", "\n", "# Load data files camea2021n00xxxx.hdf with the following numbers\n", "numbers = '1371-1388'\n", "\n", "##### CHANGE TO YOUR PATH TO THE DATA #############\n", "folder = r'C:\\Users\\lass_j\\Documents\\CAMEATutorial\\Data' #\n", "###################################################\n", "dataFiles = fileListGenerator(numbers, folder, year = 2021)\n", "print('DataFiles loaded: '+'\\n'.join(dataFiles))\n", "\n", "ds = DataSet.DataSet(dataFiles)\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "bfe8dbdb", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:11.571812Z", "iopub.status.busy": "2023-05-01T08:23:11.571812Z", "iopub.status.idle": "2023-05-01T08:23:11.585813Z", "shell.execute_reply": "2023-05-01T08:23:11.585040Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[5.500007] [-46.996902] -130.0 -29.9999\n", "[5.500007] [-51.0008] -29.9999 -130.0\n", "[6.999954] [-46.999397] -138.0 -37.9999\n", "[6.999954] [-51.0008] -37.9999 -138.0\n", "[8.499966] [-39.9999] -140.0 -39.9999\n", "[8.499966] [-43.9989] -39.9999 -140.0\n", "[9.999946] [-36.0009] -145.0 -44.9999\n", "[10.000035] [-39.9999] -44.9999 -145.0\n", "[11.499852] [-36.0009] -148.0 -47.9999\n", "[11.499852] [-39.9999] -47.9999 -148.0\n", "[11.499852] [-36.0009] -147.0 -46.9999\n", "[11.499852] [-39.9999] -46.9999 -147.0\n", "[10.000035] [-36.0009] -144.0 -43.9999\n", "[10.000035] [-39.9999] -43.9999 -144.0\n", "[8.499966] [-39.9999] -139.0 -38.9999\n", "[8.499966] [-43.9989] -38.9999 -139.0\n", "[7.000005] [-46.999397] -137.0 -36.9999\n", "[7.000005] [-51.0008] -36.9999 -38.9999\n" ] } ], "source": [ "for df in ds:\n", " print(df.Ei,df.twotheta,df.A3[0],df.A3[-1])" ] }, { "cell_type": "markdown", "id": "58289c6f", "metadata": {}, "source": [ "## Conversion of data files\n", "As mentioned in the introductionary talk about CAMEA, the instrument has a variable resolution in the energy direction and can be tune after an experiment has been conducted. The energy resolution is chosen in the conversion step where you have a choice between 3 build in normalization tables. \n", "\n", "The difference between the three can be understood by looking at the definition of the active areas on the 1D detectors. Each 1D detector is split into 8 main areas corresponding to the eight analyzers located beneath. Because of the way the instrument has been build, the signal within each of these 8 areas is energy dependent. That is, in all of the active areas the end closest to the sample position measures neutrons with a final energy lower than at the opposite end. By splitting up such an active area into a variable number of section, different energy sensitivity is reached. \n", "\n", "For CAMEA the three normalization tables are currently 1, 3, and 8, signifying the number of segments each active area is split into. The choice of which to choose depends on the scattering intensity of the sample and the duration of the experiments as choosing e.g. binning 8 for a weak signal will split the few neutrons detected into many points. \n", "\n", "As a standard 8 prismatic pixels per active area is choosen, which, combined with the 104 detector tubes and 8 active areas per detector, resulting in a total of 6656 distinct pixel per scan step. \n", "\n", "The following conversion snippet might take a minute or two depending on computing power. " ] }, { "cell_type": "code", "execution_count": 5, "id": "1bd93406", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:11.587843Z", "iopub.status.busy": "2023-05-01T08:23:11.587843Z", "iopub.status.idle": "2023-05-01T08:23:13.736704Z", "shell.execute_reply": "2023-05-01T08:23:13.735933Z" } }, "outputs": [], "source": [ "# Try to run this and the following visualization for different binnings\n", "\n", "ds.convertDataFile(binning=8)\n" ] }, { "cell_type": "markdown", "id": "522846d0", "metadata": {}, "source": [ "## Visualization of the data\n", "With the data both loaded and converted from detector pixels and neutron count into intensity in reciprocal space, it is now time to plot the data. As a first step it is most often a good idea to create an overview of the data. This can be done by using the interactive 3D viewing tool, Viewer3D, generated by calling the View3D method on the data set. What is performed is a binning in 3D into voxels (3D pixels) with constant width (dqx), depth (dqy), and height (dE). From this definition, it can be seen that the arguments the function needs are exactly the bin size in the three directions. \n", "\n", "Because each sample has their own unit cell size and thus also reciprocal lattice vectors, the size of the bins is to be specified in reciprocal Angstrom in the two momentum direction and milli electron V in energy. For the current data, suitable bin sizes are 0.04/Å, 0.04/Å, 0.08 meV as inserted below.\n", "\n", "For the graph to be interactive, it is not possible to plot the figure inline, but rather the qt backend is to be used. (It is just fine if this makes no sense to you and you can simply enjoy that the first line is given below)\n", "\n", "The interactive window should pop up, and some different visualization directions can be chosen. As default intensity is plotted as function of momentum transfer along the two main direction in the scattering plane and for constant energy. Specifically, the lowest energy transfer is chosen.\n", "\n", "\n", "| Key press| Effect |\n", "| ----------- | ----------- |\n", "| 0 | Intensity as function of Momentum and Energy transfer perpendicular to plane vector 1 |\n", "| 1 | Intensity as function of Momentum and Energy transfer perpendicular to plane vector 2 |\n", "| 2 | Intensity as function of Momentum for constant energy transfer |\n", "| + or UpArrow | Increment step along constant axis |\n", "| - or DownArrow | Decrement step along constant axis |\n", "| s | Save current figure |\n", "| End | Skip to the end of allowed stop in this projection |\n", "| Home | Skip to the start of allowed stop in this projection |\n", "\n", "\n", "By clicking on a point in the view, the corresponding reciprocal lattice point is written in the output!" ] }, { "cell_type": "markdown", "id": "9d8ff96e", "metadata": {}, "source": [ "## Extract intensity as function of HKLE for a given cut" ] }, { "cell_type": "code", "execution_count": 6, "id": "11fc51b2", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:13.739703Z", "iopub.status.busy": "2023-05-01T08:23:13.739703Z", "iopub.status.idle": "2023-05-01T08:23:14.559485Z", "shell.execute_reply": "2023-05-01T08:23:14.558483Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\lass_j\\AppData\\Local\\Temp\\ipykernel_7896\\3582549767.py:18: RuntimeWarning: invalid value encountered in true_divide\n", " intensity = np.divide(data[0]*data[-1],data[1]*data[2])\n" ] } ], "source": [ "dqx = 0.04\n", "dqy = 0.04\n", "dE = 0.08\n", "\n", "data,bins = ds.binData3D(dqx,dqy,dE)\n", "\n", "from MJOLNIR._tools import invert\n", "\n", "sample = ds[0].sample\n", "\n", "# sample.UB converts directly from HKL to QxQyQz in the space of the instrument, BUT(!) the coordinate\n", "# system of QxQyQz is not aligned so that we have nice HKL vectors along the X and Y axes. This is done \n", "# by RotMat. In addition, Qz is always 0, which is why I project it out with the 2x3 matrix.\n", "\n", "# the invert function inverts a non-square matrix.\n", "\n", "convertMatrix = invert(np.einsum('ij,jk,kl->il',sample.RotMat,np.array([[1.0,0.0,0.0],[0.0,1.0,0.0]]),sample.UB))\n", "intensity = np.divide(data[0]*data[-1],data[1]*data[2])\n", "HKLE = np.concatenate([np.einsum('ij,j...->i...',convertMatrix,bins[:2]),[bins[2]]])\n", "\n", "# HKLE has the shape 4,X.shape\n", "# That is, first index is H, K, L, E, while the latter three are the position in 3D space\n", "# Intensity has the shape of X but one shorter in all directions. Bins are the corners\n", "# while intensity is the center value\n" ] }, { "cell_type": "markdown", "id": "21e3187f", "metadata": {}, "source": [ "## Play around with the visualization" ] }, { "cell_type": "code", "execution_count": 7, "id": "fdc4e336", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:14.562687Z", "iopub.status.busy": "2023-05-01T08:23:14.562687Z", "iopub.status.idle": "2023-05-01T08:23:15.838359Z", "shell.execute_reply": "2023-05-01T08:23:15.838359Z" } }, "outputs": [], "source": [ "# Play around in this cell\n", "%matplotlib qt\n", "\n", "dqx = 0.04\n", "dqy = 0.04\n", "dE = 0.08\n", "\n", "grid = True # if this is True the reciprocal lattice is shown in the plot\n", "\n", "V = ds.View3D(dqx,dqy,dE,grid=grid)\n", "\n", "# The standard colour axis simply goes from the lowest to heighest intensity, but the following values makes the plot nicer\n", "V.set_clim(0,0.01)\n", "\n" ] }, { "cell_type": "markdown", "id": "1c908a2c", "metadata": {}, "source": [ "## Examine the data\n", "As we want to be able to fit these magnon dispersions make a list of positions in reciprocal space where it would make sense to perform a cut. Think about the following points (the answer to some of these questions are not set in stone)\n", "\n", " - How many branches are in the data?\n", " - Which scattering plane has been measured?\n", " - Which positions are most important for determining the spin gap, the band width, and the steepness of the branch?\n", " - Should the cuts be performed along constant energy or at a constant Q point?\n", " - How many cuts are necessary?\n", " - How long should the cuts be and what happens if they are too long/short?\n", " \n", "We are now in a position to start performing 1D cuts through the data. In the following snippet two cuts are performed: \n", " - a constant E cut through the dispersion between (-1,0,0) and (-2,0,0) at 3.7 meV \\pm 0.1 meV\n", " - a constant Q cut at (-1.15,0,0.15) between 0 meV and 6 meV" ] }, { "cell_type": "code", "execution_count": 8, "id": "5ca5f07b", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:16.031357Z", "iopub.status.busy": "2023-05-01T08:23:16.030330Z", "iopub.status.idle": "2023-05-01T08:23:16.579381Z", "shell.execute_reply": "2023-05-01T08:23:16.578494Z" } }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Intensity [arb]')" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Play around in this cell\n", "\n", "# Define the Q points for constant energy cut\n", "Q1 = np.array([-1.0,0.0,0]) # RLU\n", "Q2 = np.array([-.5,0.0,0]) # RLU\n", "\n", "# Define energy range to integrate over\n", "Estart = 3.9 # meV\n", "Estop = 4.1 # meV\n", "\n", "# Define perpendicular width of cut, i.e. how wide the cut is to be in the Q plane\n", "width = 0.05 # 1/Å\n", "\n", "# Define the size of the bins along the cut direction\n", "minPixel = 0.01 # 1/Å\n", "\n", "# The flag 'extend' sets whether or not the cut is to be extended in both directions to include all data\n", "# The flag 'constantBins' decides if the bins used in the cut all have the same size. If not, an adaptive binning method is used\n", "ax1D,data1D,_ = ds.plotCut1D(q1=Q1,q2=Q2,width=width,minPixel=minPixel,Emin=Estart,Emax=Estop,extend=False,constantBins=True)\n", "\n", "# ax1D contains the matplotlib axis element into which the plot has been generated\n", "# data1D is a pandas dataframe containing the data of the cut\n", "# The last return value is bins, which contains a list of bin positions both in energy and the two q directions.\n", "# As it is not needed in this tutorial it has been named '_' which signifies an unused variable\n", "\n", "# If the plot is to be recreated (later used to overplot with our fit), the following code can be used\n", "Intensity = data1D['Int']\n", "# The error is calculated using the Gaussian approximation and normalized to monitor and instrument sensitivity\n", "Error = data1D['Int_err']\n", "H = data1D['H']\n", "\n", "\n", "fig1DCut,ax1DCut = plt.subplots()\n", "ax1DCut.errorbar(H,Intensity,yerr=Error,fmt='.')\n", "ax1DCut.set_xlabel('H [RLU]')\n", "ax1DCut.set_ylabel('Intensity [arb]')\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "ad94a0fc", "metadata": { "execution": { "iopub.execute_input": "2023-05-01T08:23:16.583395Z", "iopub.status.busy": "2023-05-01T08:23:16.582394Z", "iopub.status.idle": "2023-05-01T08:23:16.610372Z", "shell.execute_reply": "2023-05-01T08:23:16.609704Z" } }, "outputs": [ { "data": { "text/html": [ "
| \n", " | Qx | \n", "Qy | \n", "H | \n", "K | \n", "L | \n", "Energy | \n", "intensity | \n", "Monitor | \n", "Normalization | \n", "BinCount | \n", "Int | \n", "Int_err | \n", "BinDistance | \n", "
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | \n", "0.965433 | \n", "0.841618 | \n", "-0.993319 | \n", "1.918646e-35 | \n", "-2.153623e-08 | \n", "4.0 | \n", "0.0 | \n", "3000000.0 | \n", "0.035531 | \n", "15.0 | \n", "0.000000 | \n", "0.000000 | \n", "0.013361 | \n", "
| 1 | \n", "0.957895 | \n", "0.835047 | \n", "-0.985564 | \n", "1.903666e-35 | \n", "-2.136808e-08 | \n", "4.0 | \n", "3.0 | \n", "2800000.0 | \n", "0.029103 | \n", "14.0 | \n", "0.000515 | \n", "0.000298 | \n", "0.028872 | \n", "
| 2 | \n", "0.950357 | \n", "0.828475 | \n", "-0.977808 | \n", "1.888685e-35 | \n", "-2.119993e-08 | \n", "4.0 | \n", "2.0 | \n", "5600000.0 | \n", "0.050022 | \n", "28.0 | \n", "0.000200 | \n", "0.000141 | \n", "0.044384 | \n", "
| 3 | \n", "0.942819 | \n", "0.821904 | \n", "-0.970053 | \n", "1.873705e-35 | \n", "-2.103178e-08 | \n", "4.0 | \n", "3.0 | \n", "5600000.0 | \n", "0.060355 | \n", "28.0 | \n", "0.000249 | \n", "0.000143 | \n", "0.059895 | \n", "
| 4 | \n", "0.935281 | \n", "0.815333 | \n", "-0.962297 | \n", "1.858725e-35 | \n", "-2.086363e-08 | \n", "4.0 | \n", "5.0 | \n", "5200000.0 | \n", "0.043641 | \n", "26.0 | \n", "0.000573 | \n", "0.000256 | \n", "0.075406 | \n", "
| ... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
| 59 | \n", "0.520697 | \n", "0.453919 | \n", "-0.535738 | \n", "1.034804e-35 | \n", "-1.161537e-08 | \n", "4.0 | \n", "5.0 | \n", "10800000.0 | \n", "0.073636 | \n", "54.0 | \n", "0.000340 | \n", "0.000152 | \n", "0.928524 | \n", "
| 60 | \n", "0.513159 | \n", "0.447348 | \n", "-0.527982 | \n", "1.019824e-35 | \n", "-1.144722e-08 | \n", "4.0 | \n", "3.0 | \n", "9400000.0 | \n", "0.089070 | \n", "47.0 | \n", "0.000168 | \n", "0.000097 | \n", "0.944036 | \n", "
| 61 | \n", "0.505622 | \n", "0.440777 | \n", "-0.520227 | \n", "1.004844e-35 | \n", "-1.127907e-08 | \n", "4.0 | \n", "0.0 | \n", "7600000.0 | \n", "0.058296 | \n", "38.0 | \n", "0.000000 | \n", "0.000000 | \n", "0.959547 | \n", "
| 62 | \n", "0.498084 | \n", "0.434205 | \n", "-0.512471 | \n", "9.898633e-36 | \n", "-1.111092e-08 | \n", "4.0 | \n", "4.0 | \n", "8800000.0 | \n", "0.092436 | \n", "44.0 | \n", "0.000216 | \n", "0.000108 | \n", "0.975058 | \n", "
| 63 | \n", "0.490546 | \n", "0.427634 | \n", "-0.504715 | \n", "9.748829e-36 | \n", "-1.094277e-08 | \n", "4.0 | \n", "2.0 | \n", "8400000.0 | \n", "0.078219 | \n", "42.0 | \n", "0.000128 | \n", "0.000090 | \n", "0.990569 | \n", "
64 rows × 13 columns
\n", "