nx5d

Quick Start

  • Home
  • A First Example

User's Guide

  • Overview
  • Installing
  • Core Concepts
  • API Guide
  • Managing Spice
    • Repository Setup
    • Using The Spice API
      • Seeding
      • Anchoring & Updating
      • Advanced Tasks
    • Using The Utility
  • Loading Data
  • Adapting And Extending

Tutorials

  • A Real-Life Example

Reference

  • Supported Beamlines
  • API Reference
  • Glossary
  • License
nx5d
  • User's Guide
  • Managing Spice

Managing Spice¶

Based on our example dataset, we will demonstrate how the spice set it's being shipped with was built. Note that the actual presence of the data is not necessary at this point -- we can manage all the spice we need to using just knowledge about the data, and don't require the HDF5 file itself.

Generally, there are two ways to manage spice, both demonstrated below: via Nx5d's Python API, and via a CLI utility called slim.

Repository Setup¶

To make the spice storage persistent, the first thing to do is designate a folder that will hold all the spice: let's assume /tmp/srepo for the sake of demonstration. Therein, all the proposals will receive their own subfolder, and we'll put a designated ./spice sub-subfolder to actually hold the spice data.

The project that provided the example data is called "252-cw33-13528-pudell" at KMC3-XPP. We'll use the same name for the proposal here. Generally, the spice management systems below will create the proposal repo folder when necessary. But creating them by hand has some advantages for better understanding:

In [12]:
Copied!
mkdir -p /tmp/srepo/252-cw33-13525-pudell/spice
mkdir -p /tmp/srepo/252-cw33-13525-pudell/spice

If you're working within a Bash shell, you might also want to set the NX5D_SPICE_REPO variable -- many Nx5d spice API elements work smoother with it, and the slim utility actually depends on this:

export NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice"

From this point on we're ready to start creating and managing the example project's spice by any of the following methods.

Using The Spice API¶

The proposal name of "252-cw33-13525-pudell" was nice for "internal use", but Nx5d will find it clumsy: there's lot of functionality involved in the API that will depend on having a Python-esque name for the proposal. Therefore the first thing we're going to do is decide how to translate the proposal name (called "key" in Nx5d) into a Python-variable capable version of itself (called "handle" in Nx5d). We're actually going to take the prpoposer name (pudell here) followed by the BESSY-internal proposal number (13525 here).

We know that the positions and lengthst of all the parameters are fixed, so the parsing is easy:

In [7]:
Copied!
key2handle = lambda x: x[15:]+x[9:14]
key2handle = lambda x: x[15:]+x[9:14]

And let's put the transformation to the test:

In [8]:
Copied!
key2handle("252-cw33-13525-pudell")
key2handle("252-cw33-13525-pudell")
Out[8]:
'pudell13525'

Looks good.

Now dive right in by initializing the repository class. Normally the class would automatically obtain its repository location using the using the NX5D_SPICE_REPO environment variable defined above.

Because this is a tutorial that you're supposed to be able to type along in your Jupyter notebook, where new environment variables may not be easily available, we're passing the repo URL as an argument when initializing -- along with the key => handle transformator:

In [9]:
Copied!
from nx5d.spice import FsSpiceRepository
repo = FsSpiceRepository(url="/tmp/srepo/{proposal}/spice/", proposal_k2h=key2handle)
from nx5d.spice import FsSpiceRepository repo = FsSpiceRepository(url="/tmp/srepo/{proposal}/spice/", proposal_k2h=key2handle)

We can then test right away if the repo class can actually list our proposals:

In [10]:
Copied!
repo.all()
repo.all()
Out[10]:
{'252-cw33-13525-pudell': 'pudell13525'}

From here on we can access the proposal directly as repo.pudell13525 (if you're typing this in a Jupyter or IPython shell, try the -autocompletion).

Seeding¶

We're going to add two spice types essential for processing:

  • exp_info definition of the experimental geometry, required by kmc3recipes algorithms
  • offsets for after-the-fact corrections of goniometer angles, supported by Kmc3recipes
  • recipes the piece that actually ties the proposal to the Kmc3recipes module, in Nx5d's perception ;-)

We won't descend into the details of why exactly the structure of the spice types is defined as it is -- please take that as a given now, and possibly consult documentation of Kmc3recipes later for in-depth understanding. Without further ado, here's the seed code:

In [11]:
Copied!
s1 = repo.pudell13525.seed("exp_info", payload={
    "goniometerAxes": {"theta": "x+", "chi": "y+", "phi": "z+"},
    "detectorAxes": {"tth": "x+"},
    "detectorTARAlign": [0.0, 0.0, 0.0],
    "imageAxes": ["x-", "z-"],
    "imageSize": [195, 487],
    "imageCenter": [95, 244],
    "imageDistance": 481.0,
    "imageChannelSize": [0.172, 0.172],
    "sampleFaceUp": "z+",
    "beamDirection": [0, 1, 0],
    "sampleNormal": [0, 0, 1],
    "beamEnergy": 10000.0,
})

s2 = repo.pudell13525.seed('offsets', theta=0.0, tth=0.0, chi=0.0, phi=0.0)

s3 = repo.pudell13525.seed('recipes',
                           sRSM='pymod://kmc3recipes.cooking/sRSM',
                           TRSM='pymod://kmc3recipes.cooking/TRSM',
                           rocking='pymod://kmc3recpes.cooking/rocking')
                    
s1 = repo.pudell13525.seed("exp_info", payload={ "goniometerAxes": {"theta": "x+", "chi": "y+", "phi": "z+"}, "detectorAxes": {"tth": "x+"}, "detectorTARAlign": [0.0, 0.0, 0.0], "imageAxes": ["x-", "z-"], "imageSize": [195, 487], "imageCenter": [95, 244], "imageDistance": 481.0, "imageChannelSize": [0.172, 0.172], "sampleFaceUp": "z+", "beamDirection": [0, 1, 0], "sampleNormal": [0, 0, 1], "beamEnergy": 10000.0, }) s2 = repo.pudell13525.seed('offsets', theta=0.0, tth=0.0, chi=0.0, phi=0.0) s3 = repo.pudell13525.seed('recipes', sRSM='pymod://kmc3recipes.cooking/sRSM', TRSM='pymod://kmc3recipes.cooking/TRSM', rocking='pymod://kmc3recpes.cooking/rocking')

The .seed() call returns a copy of the spice object that was created, for you to check. One property of the object might be its "uuid" field:

In [12]:
Copied!
s1['uuid'], s2['uuid'], s3['uuid']
s1['uuid'], s2['uuid'], s3['uuid']
Out[12]:
('b2b735b5-09f2-4590-9179-c7e11f297d4b',
 'f2503200-2858-4f04-8a1e-2a7d54e99cdf',
 '1f26f503-6e1c-4c27-838d-e6598436fbdb')

Keep in mind that most spice API operations, including .seed(), are designed to be idem-potent. This means that if called repeatedly on the same proposal in the same repository, only the first call will actually have a changing effect. Or in other words, all subsequent calls will return objects with the same UUID -- those of the objects that have been created on the first seed of the said type.

This is a deliberate design decision to make it easy for spice operations to be embedded in Jupyter notebook cells (which tend to be executed over and over again).

A thing you might have noticed is how we are passing down the contents of the spice type: the most comfortable way is by using appropriately named parameters (like theta or sRSM). But we can also pass a complete dictionary to seed .seed() via its payload=... parameter.

Anchoring & Updating¶

Studying the lab book, we'll see that we have designated changes mostly related to the definition of the center pixel of the detector and the beam energy (runs 38, 47, 58, 118, 164, ...) and some changes to the theta angle offset at scan 118, valid also for 119 (but not for 120).

Here's the code that produces the new anchor points and updates.

In [13]:
Copied!
repo.pudell13525.anchor('r0000', 'exp_info')
repo.pudell13525.anchor('r0038', 'exp_info')
repo.pudell13525.anchor('r0043', 'exp_info')
repo.pudell13525.anchor('r0047', 'exp_info')
repo.pudell13525.anchor('r0058', 'exp_info')
repo.pudell13525.anchor('r0118', 'exp_info')
repo.pudell13525.anchor('r0164', 'exp_info')
repo.pudell13525.anchor('r0226', 'exp_info')
repo.pudell13525.anchor('r0238', 'exp_info')
repo.pudell13525.anchor('r0279', 'exp_info')
repo.pudell13525.anchor('r0412', 'exp_info')
repo.pudell13525.anchor('r0431', 'exp_info')
repo.pudell13525.anchor('r0118', 'offsets')
repo.pudell13525.anchor('r0120', 'offsets')

repo.pudell13525.update('r0000', 'exp_info', beamEnergy=10000.0, imageCenter=[95,244])
repo.pudell13525.update('r0038', 'exp_info', beamEnergy=6800.0, imageCenter=[95,312])
repo.pudell13525.update('r0043', 'exp_info', beamEnergy=10207.0)
repo.pudell13525.update('r0047', 'exp_info', beamEnergy=13614.0)
repo.pudell13525.update('r0058', 'exp_info', beamEnergy=6800.0)
repo.pudell13525.update('r0118', 'exp_info', beamEnergy=10207.0)
repo.pudell13525.update('r0118', 'offsets', theta=30.0)
repo.pudell13525.update('r0120', 'offsets', theta=30.0)
repo.pudell13525.update('r0164', 'exp_info', beamEnergy=6800.0)
repo.pudell13525.update('r0226', 'exp_info', beamEnergy=10207.0)
repo.pudell13525.update('r0238', 'exp_info', beamEnergy=13614.0)
repo.pudell13525.update('r0279', 'exp_info', beamEnergy=10000.0, imageCenter=[95,244])
repo.pudell13525.update('r0412', 'exp_info', beamEnergy=10207.0, imageCenter=[95,312])
repo.pudell13525.update('r0431', 'exp_info', beamEnergy=13614.0)
pass
repo.pudell13525.anchor('r0000', 'exp_info') repo.pudell13525.anchor('r0038', 'exp_info') repo.pudell13525.anchor('r0043', 'exp_info') repo.pudell13525.anchor('r0047', 'exp_info') repo.pudell13525.anchor('r0058', 'exp_info') repo.pudell13525.anchor('r0118', 'exp_info') repo.pudell13525.anchor('r0164', 'exp_info') repo.pudell13525.anchor('r0226', 'exp_info') repo.pudell13525.anchor('r0238', 'exp_info') repo.pudell13525.anchor('r0279', 'exp_info') repo.pudell13525.anchor('r0412', 'exp_info') repo.pudell13525.anchor('r0431', 'exp_info') repo.pudell13525.anchor('r0118', 'offsets') repo.pudell13525.anchor('r0120', 'offsets') repo.pudell13525.update('r0000', 'exp_info', beamEnergy=10000.0, imageCenter=[95,244]) repo.pudell13525.update('r0038', 'exp_info', beamEnergy=6800.0, imageCenter=[95,312]) repo.pudell13525.update('r0043', 'exp_info', beamEnergy=10207.0) repo.pudell13525.update('r0047', 'exp_info', beamEnergy=13614.0) repo.pudell13525.update('r0058', 'exp_info', beamEnergy=6800.0) repo.pudell13525.update('r0118', 'exp_info', beamEnergy=10207.0) repo.pudell13525.update('r0118', 'offsets', theta=30.0) repo.pudell13525.update('r0120', 'offsets', theta=30.0) repo.pudell13525.update('r0164', 'exp_info', beamEnergy=6800.0) repo.pudell13525.update('r0226', 'exp_info', beamEnergy=10207.0) repo.pudell13525.update('r0238', 'exp_info', beamEnergy=13614.0) repo.pudell13525.update('r0279', 'exp_info', beamEnergy=10000.0, imageCenter=[95,244]) repo.pudell13525.update('r0412', 'exp_info', beamEnergy=10207.0, imageCenter=[95,312]) repo.pudell13525.update('r0431', 'exp_info', beamEnergy=13614.0) pass

We can get an overview over the current spice pool using the .ls() method. (Also, simply evaluating a spice proposal object in a Jupyter notebook will produce a similar output, because the corresponding HTML representation method has been overwritten to return the same as .ls()).

In [14]:
Copied!
repo.pudell13525.ls()
repo.pudell13525.ls()
Out[14]:
exp_info offsets recipes
anchor
b2b735 f25032 1f26f5
r0000 f2103b
r0038 b4c926
r0043 ecf2be
r0047 e6d590
r0058 24e0d4
r0118 398dfd add241
r0120 a5900e
r0164 5c5db7
r0226 ca768f
r0238 17b8f0
r0279 8969e8
r0412 74d181
r0431 e09e8c

Advanced Tasks¶

In the current implementation, the top-level overview will not display any spice data or keys. We only show the first characters of the UUID -- which can then be used to locate the specific BranchView object, e.g. using the collection's .find() method:

In [15]:
Copied!
repo.pudell13525.collection.find(uuid='141', startswith=True)
repo.pudell13525.collection.find(uuid='141', startswith=True)
Out[15]:
[]

While the Collection object (here repo.pudell13525) encapsulates on-disk access and operations, the corresponding .collection encapsulates a memory-only access to spice. The most prominent functions you'll ever need from a collection are .find() and .view().

.view() produces a list of virtual spice objects, viewed from the perspective of a specific data scan. This is not something that the user is usually doing directly (though they can). Instead, Nx5d's data Proposal & Scan system does this indirectly.

The .find() method searches the spice tree based on metadata arguments not data! This is a particularly useful feature for identifying specific spice entries based on UUIDs, types or revision numbers, and preparing updates to them -- to solve conflicts, for instance.

Functions like .handles() or a .anchors() in a spice proposal objects give direct representation of the spice tree sorted by handles, respectively anchors. They can also filter for these criteria (they're a specialized versions of .find(), of sorts...).

Feel free to explore :-)

In [16]:
Copied!
repo.pudell13525.view('r0137')
repo.pudell13525.view('r0137')
Out[16]:
handle clean revision uuid anchor data
viewpoint
r0137 exp_info True 8 398dfd r0118 {'goniometerAxes': {'theta': 'x+', 'chi': 'y+'...
r0137 offsets True 4 a5900e r0120 {'theta': 30.0, 'tth': 0.0, 'chi': 0.0, 'phi':...
r0137 recipes True 1 1f26f5 {'sRSM': 'pymod://kmc3recipes.cooking/sRSM', '...

Using The slim Utility¶

slim is short for Spice List Manager, and is a command-line utility to manage a spice repository directly from the shell -- no Jupyter or other Python shells required (though slim itself is written in Python, and this tutorial is written as a Jupyter notebook).

Slim supports the same commands as the API to the same end -- in fact it's just a wrapper around Nx5d's Pyhon spice API and some JSON parsing & dumping.

For the following, we assume that the environment variable NX5D_SPICE_REPO been properly defined. If you're using Slim in a proper Bash shell, as you should, you can define it as demonstrated at the beginning of this section.

If you insist on running them from a Jupyter notebook, then you'll have to have exported NX5D_SPICE_REPO beforehand (or use the proper --env ... parameter if you're running Jupyter as a container image).

In [33]:
Copied!
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \
  slim proposals?
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \ slim proposals?
Object `proposals` not found.
In [23]:
Copied!
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \
  slim 252-cw33-13525-pudell list
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \ slim 252-cw33-13525-pudell list
Proposal: 252-cw33-13525-pudell
       exp_info offsets recipes
anchor                         
         b2b735  f25032  1f26f5
r0000    f2103b                
r0038    b4c926                
r0043    ecf2be                
r0047    e6d590                
r0058    24e0d4                
r0118    398dfd  add241        
r0120            a5900e        
r0164    5c5db7                
r0226    ca768f                
r0238    17b8f0                
r0279    8969e8                
r0412    74d181                
r0431    e09e8c                
In [24]:
Copied!
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \
  slim 252-cw33-13525-pudell anchor r0120 offsets
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \ slim 252-cw33-13525-pudell anchor r0120 offsets
Proposal: 252-cw33-13525-pudell
a5900eda-1c08-411a-be74-71e75bd8e866
In [25]:
Copied!
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \
  slim 252-cw33-13525-pudell update offsets@r0120 theta=0.0
! NX5D_SPICE_REPO="/tmp/srepo/{proposal}/spice" \ slim 252-cw33-13525-pudell update offsets@r0120 theta=0.0
Proposal: 252-cw33-13525-pudell
6ac2a91b-6026-4c6d-8852-676326adf0ad
In [ ]:
Copied!

Previous Next

Built with MkDocs using a theme provided by Read the Docs.