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
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
)
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 [ ]:
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)
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
In [ ]:
d0
Metadata about the SfgRecord is available
In [ ]:
d0.metadata
This is the rawData
In [ ]:
d0.rawData
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.
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)
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
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.
In [ ]:
d0.trace()
Here you see quite some return. The reason is, trace()
returns diferent things per default 3.
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
)
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
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.
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)
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)