You can run a cell by hitting Shift + Enter on the keyboard

A more extensive tutorial can be found here Skip to: "Getting Started With Jupyter Notebooks" if you are hazy.


In [ ]:
# Import and load some packages

# makes python a little like matlab.
%pylab  
import sfg2d  # This imports the sfg2d package

Import Data

The SfgRecord object is the central object of the sfg2d toolkit. Almost everything of the sfg2d toolkit relyes on this object. You can create an SfgRecord by passing it a path to a datafile. It will then import this data.


In [ ]:
bg10 = sfg2d.SfgRecord('/home/malte/MeasurmentData/Viktor/2018/05/23/11_bg_quartz_ssp_purged1_gal1_pu0_pr1.dat')
# You can use the TAB key for autocompletion of a path. Enter a fractial path and autocomplete it.
bg100 = sfg2d.SfgRecord('/home/malte/MeasurmentData/Viktor/2018/05/23/12_bg_d2o_ssp_purged1_gal1_pu1_pr0.dat')

Load some data for normalization. Note how we pass the background bg10 directly to the normalization data itself. base stands for background, because its the baseline of the data. By calling bg10.select('rawData') we collect the raw data from bg10. This is the same as calling bg10.rawData. If you dont belive it you can try to veryfiy this by checking (exercise). The advantege of select ist, that you can pass aditional transformations to you data. Here we just tell it it should average all frames by passing frame_med=True ofcourse there are many many more options. Most are documented in the help string of the select function. Try to open this help function now (exercise).


In [ ]:


In [ ]:
q0 = sfg2d.SfgRecord(
    '/home/malte/MeasurmentData/Viktor/2018/05/23/11_sc_quartz_ssp_purged1_gal1_pu0_pr1.dat',
    base=bg10.select('rawData', frame_med=True),  # Here we set a baseline
)

Sometimes multiple data files belong to the same measurment. Therefore you can pass a list of filenames to SfgRecord and it will load all these files as if they were one big measurment. To have a complere SfgRecord you also need a background aka base and an normalization aka norm. The following shows you how you can pass this to an SfgRecord.


In [ ]:
d0 = sfg2d.SfgRecord([
        '/home/malte/MeasurmentData/Viktor/2018/05/23/16_ts_d2o_ssp_purged1_gal1_pu1_pr1.dat',
        '/home/malte/MeasurmentData/Viktor/2018/05/23/17_ts_d2o_ssp_purged1_gal1_pu1_pr1.dat',
    ],
    # And add baseline and norm
    base=bg100.select('rawData', frame_med=True),  # Set a baseline/background
    norm=q0.select('basesubed', frame_med=True)  # Set a normalization
)

Backgroud

This is a more programmatic way of how you could import your background. Thisway, you dont need to pass the same keywords (kwargs) to the select function over and over gain.


In [ ]:
# Position your cursor after the ./ and hit the TAB key
kwargs = {"prop" : "rawData", "delay_mean" : True}  # Define some default keywords.
bg0 = sfg2d.SfgRecord("./").select(**kwargs) # the ** allows you to pass a dict as a list of keywords to a function.
bg1 = sfg2d.SfgRecord("./").select(**kwargs)

Note that bg0 is now just an array and not an SfgRecord object any more. Exercise, try to see the diference.


In [ ]:

Ir Profile

Similar as with the baseline you could define default keywords for the import of the normalization data if you need to import multiple of them.


In [ ]:
kwargs = {'base': bg0}
select_kwargs = {'frame_med': True, 'prop': 'basesubed'}
q0 = sfg2d.SfgRecord("./", **kwargs).select(**select_kwargs)
q1 = sfg2d.SfgRecord("./", **kwargs).select(**select_kwargs)

Pump-Probe Data

And for the sake of completness here the same for pump probe data


In [ ]:
kwargs = {'base': bg0, 'norm': q0}
d0 = sfg2d.SfgRecord("./", **kwargs)
d1 = sfg2d.SfgRecord("./", **kwargs)

There are more ways to add baseline data to an SfgRecord. After creation of the SfgRecord you can slo set the baseline as you see below. As an exercise, try to set the baseline to a constant value.


In [ ]:
d0.base = bg0 # Alternative way of setting a baseline. Ofcoures this also works for q0.norm or q0.rawData

Many more options are availabe. E.G. a method to replace broken pixels. Note don't use this for spikes. That is not what it is meant for.


In [ ]:
d0.replace_pixel(q0, 496) # Replace broken pixel

Using an SFGRecord

Take a look at what an SfgRecord object is:


In [ ]:
d0

Metadata about the SfgRecord is available


In [ ]:
d0.metadata

This is the rawData


In [ ]:
d0.rawData

The Data Structure

To get some understanding about the data structure lets have a look what d0.rawData actually is


In [ ]:
type(d0.rawData)

This tells us, d0.rawData is a numpy array. The default documentation about a numpy array can be found here. You dont need to know all of it, but atleast know the diference between a list and a numpy.array. Also to go on you need to know how you index and slice a numpy array. This you find here

Now lets have a look at the shape of the rawData


In [ ]:
d0.rawData.shape

As you can see, rawData is a 4 dimensional array. Each index has a certain meaning.

  • The 1st index stands for a pump_probe value.
  • The 2nd index stands for frames/scans. E.G. a repetition of the same scan.
  • The 3rd index stands for y-pixels or spectra
  • and the 4th index represents the pixel.

This means:


In [ ]:
d0.rawData[0,0,0,0]

returns a single value, namely the intensity of the first pixel of the first spectrum of the first frame at the first timedelay.

If you want to get the complete spectrum, you can do:


In [ ]:
d0.rawData[0,0,0]
# or 
d0.rawData[0, 0, 0, :]

Both give the samre result. As you can seen, we now make use of numpys indexing. If this is not clear, check or find a tutorial about this, as this is out of the scope of this tutorial here.

Beeing able to work on the rawData is all nice, but oftern you want to work with the normalized data. Fear not. this is readily available under:


In [ ]:
d0.normalized

If you have defined a base and a norm

Verify, that this is a numpy array and has the same shape as rawData or basesubed


In [ ]:

This is how you could take the mean frame wise mean of an normalized data set:


In [ ]:
d0.normalized[0, :, 0, :].mean(0)

or you can do:


In [ ]:
d0.normalized.mean(1)[0, 0]

Exercises:

- Try to understand why both methods give the same result
- Try to average all th timedelays as well as the frames and pic the 2nd spectrum
- Try to average frames, keep all time delays, pick the first spectrum and sum pixel 400 to 500

In [ ]:
d0.normalized.mean(1)[:, 1, 400:500].sum(1)

Additional properties

If the calibration is correct, SfgRecords automatically calculate wavenumbers and wavelengths for you


In [ ]:
d0.pixel

In [ ]:
d0.wavelength

In [ ]:
d0.wavenumber

You can also obtain the baseline or the normalization you used for the SfgRecord in case you forgot what it was


In [ ]:
d0.norm, d0.base

The normalized and baselinesubtracted data is also availabel


In [ ]:
d0.normalized, d0.basesubed

Bleach

The SfgRecord object can do much more then subtracting baselines and normalize. You can also select a certain index as the index of the pumped and the unpumped signal. By default the pumped_index is assumed to be 0 and the unpumped_index is assumed to be 1. You can set the indeces up on SfgRecord creation or afterwards. To set it afterwards do:


In [ ]:
d0.unpumped_index = 0
d0.pumped_index = 1

And lets get the bleach from this:


In [ ]:
d0.bleach

Ups what happend here? This looks strange. The reason is, the bleach is a function. Therefore you must call it with brackets:


In [ ]:
d0.bleach()

Aaaah a lot better. Now there is some data.

The reason for bleach beeing a function is, that there are multiple ways of calculating the bleach. The most important diference is the opt kwarg of bleach. Per default it is rel and stand for relative. Thus the bleach is calculated by dividing the pumped and the unpumped spectrum. If you want to get the difference set opt to be: abs


In [ ]:
d0.bleach('abs')

Note the shape of the bleach:


In [ ]:
d0.bleach().shape

Note:

As you see, the 3rd dimension (the spectrum/y-pixel) dimension is 1. This means, we could just drop it because it is not needed any more. The bleach will allways be one spectrum. However I decided to keep it here, because this way plotting functions definded for spectra, that rely on 4dimensional data will just work with the bleach as well. In other words by having the same data structure again and again and again everything can be handeled the same way.

Trace

Following to the bleach, we are often interested in the trace of the data. That is the sum or average over a certain area of the bleach.


In [ ]:
d0.trace()

Here you see quite some return. The reason is, trace() returns diferent things per default 3.

  • the time delay
  • the trace data
  • the error of the trace data estimated by taking the standard error of the mean per frame Following is a better way of lokking at the return

In [ ]:
time, y, yerr = d0.trace()
y

Similar to bleach, trace has many options. Some will only become clear after the select function is introduced. So here are some examples


In [ ]:
y = d0.trace(
    roi_delay=slice(3, 10), # pick only the 3rd to 10th time delay
    y_only=True, # return only the y values and omit time delay and y_err values
)
y.shape

In [ ]:
d0.bleach(roi_pixel=slice(400, 500)).shape

In [ ]:
d0.trace(
    roi_wavenumber = slice(2500, 2800),  # probably the most important keyword. This lets you select the desired region to calculate the mean over.
    # if wavenumber is not correctly calibrated, you can fallback to pixels
    # roi_pixel = slice(400, 500), 
    y_only=True
)

The Select Function of an SfgRecord

Now you have some understanding about the data structure within an SfgRecord. In the next section I want to show you how you kan kind of forget most of the details, and have a somewhat generic interface to interact and transform the data. The magic function is the select member function of the SfgRecord. Lets have a look at it:


In [ ]:
d0.select().shape

As we see, it has returned some data in the usual fashion. If you pass no arguments to this function, it basically returns normalized


In [ ]:
all(d0.select() == d0.normalized)

The advantage of select is, that it provides us with a common interface to transform our data. E.g. lets take the frame wise median:


In [ ]:
d0.select(frame_med=True).shape

Compared to our old d0.normalized.mean(1, keepdims=1) this has not many benefits jet.

But the strength becomes, obvious when you want to do many different transformations.


In [ ]:
d0.select(frame_med=True, delay_mean=True, roi_pixel=slice(400, 1200)).shape

This is already easier then:


In [ ]:
np.median(d0.norm.mean(0, keepdims=1), 1, keepdims=1)[:, :, :, slice(400, 1200)].shape

And the more options of select you use, the more obvious this becomes. For example the medfilt_pixel or the resample_freqs filter.

Side Note: The select function has also some programmatic reasons. Due to the select function, all data transformations go into a single functions and the transformation can be applied to all data. I dont need to define special transofmrations for bleach, or trace or basesubed or normalized. In the end all becomes the same. There is also a third reason for the select function but that will be shown in another tutorial.

Of course, the select function alows you to actually select some data


In [ ]:
d0.select('trace', roi_wavenumber=slice(2000, 2800), frame_med=True).shape

Plotting

To plot data we use matplotlib its not always the most conviniet plotting. However it is free widely used and really really powerfull. There is almost nothing it can't do. There is no reason to explain matplotlib here, a good starting point is the default tutorial. The only thing you need to note right now, is that we made all of the matplotlib functionas availe in the first line of code %pylab notebook. This imports all the matplotlib functions in the way you need them.

To make our live a easier, I have defined a plotting module within sfg2d it can be found under sfg2d.plot. This has quite some functions in it, but many are deprecated, or for a very special usecase. You dont need to master all of them. For now sfg2d.plot.spectrum, sfg2d.plot.trace and sfg2d.plot.track should be enough.


In [ ]:
## kwargs common for x and y data
kwargs = {
    'roi_wavenumber' : slice(2200, 2800),
}

## select some x data
xdata = d0.select(
    'wavenumber',
    **kwargs
)

## select some y data
ydata = d0.select(
    'basesubed', 
    frame_med=True, 
    delay_mean=True,
    #roi_delay=slice(0, 1),
    roi_spectra=slice(0, 2),  # best use slices for rois, there is still a bug hiding when using arrays
    medfilt_pixel=5,  # Median filter of 5 pixels
    **kwargs,
)

## Make the actual figure and plot
fig, ax = subplots()  # defines a figure
sfg2d.plot.spectrum(xdata, ydata)  # Calls a plot function that can handle 4 dim data.

Trace

A trace is the pixel wise mean over a selected area of the bleach.


In [ ]:
## kwargs common for x and y data
kwargs = {
    #'roi_wavenumber' : slice(2200, 2800),
}

## select some x data
xdata = d0.select(
    'pp_delays',
    **kwargs
)

## select some y data
ydata = d0.select(
    'trace', 
    frame_med=True, 
    roi_wavenumber=slice(2400, 2600),
    **kwargs,
)

fig, ax  = subplots()
sfg2d.plot.trace(xdata, ydata)

Track

A track plots the pixel wise sum of a spectrum vs the time of a spectraum. By Looking a the track, you can see if your measurment was stable or, or if the internsity changed during time.


In [ ]:
## kwargs common for x and y data
kwargs = {
    #'roi_wavenumber' : slice(2200, 2800),
}

## select some y data
ydata = d0.select(
    'track', 
    roi_wavenumber=slice(2200, 2800),
    roi_spectra=slice(0, 2),
    **kwargs,
)

fig, ax = subplots()
sfg2d.plot.track(None, ydata)