Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
355 changes: 353 additions & 2 deletions refellips/dataSE.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
A basic representation of a 1D dataset
"""

import copy
import numpy as np
import pandas as pd
from refnx._lib import possibly_open_file
Expand Down Expand Up @@ -250,7 +251,7 @@ def refresh(self):

"""
if self.filename is not None:
with open(self.filename) as f:
with possibly_open_file(self.filename) as f:
self.load(f)


Expand Down Expand Up @@ -496,7 +497,7 @@ def open_HORIBAfile(
linenodict = {}
MDingest = False

with open(fname, "r") as f:
with possibly_open_file(fname, "r") as f:
lines = f.readlines()

for i, line in enumerate(lines):
Expand Down Expand Up @@ -542,3 +543,353 @@ def open_HORIBAfile(
data = [data_df["nm"], data_df["AOI"], data_df["Psi"], data_df["Delta"]]

return DataSE(data, name=name, reflect_delta=reflect_delta, **metadata)


def open_M2000file(fname, take_every=1, dropdatapoints=1):
"""
Opens raw text files exported by Woolam's 'CompleteEASE' software.


Parameters
----------
fname : file-handle or string
File to load the dataset from.

dropdatapoints : int
Includes every nth element from the woolam dataset in DataSE.
Use to speed up fitting for datasets where a narrow wavelength
resolution is not required.

take_every : int
For time-resolved data sets only. Includes every nth dataset in
the returned dictionary. Usefully for reducing the number of data
points that needs to be processed when you don't need high temporaral
resolution

Returns
----------
datasets : DataSE structure or dict
If the file contains a single measurment, function returns
a single DataSE object containing wavelength, angle of
incidence, psi and delta.

If the file contains multiple measurements, such as a time-
series dataset, then it returns a dictionary containing
DataSE objects for each measurement point. The Dictionary
keys correspond to the measurement details (e.g., time in seconds).

Metadata contained within the file is stored in DataSE.metadata.
"""

mode = None
metadata = {}

with possibly_open_file(fname, mode="r") as file:
file.readline() # Skip the blank first line
firstline = file.readline()[:-1]
secondline = file.readline()[:-1]

if secondline == "nm":
mode = "single"
skiplines = 3
elif secondline == "Dynamic":
mode = "dynamic"
skiplines = 4
else:
raise ValueError(
"Currently, only single and time-series datasets can be read"
)

header = firstline[len("VASEmethod[") : -len("]")]
for ele in header.split(", "):
key, val = ele.split("=")
metadata[key] = val

if mode == "single":
return _open_M2000file_standard(
fname=fname,
dropdatapoints=dropdatapoints,
skiplines=skiplines,
metadata=metadata,
)
elif mode == "dynamic":
return _open_M2000file_kinetic(
fname=fname,
take_every=take_every,
dropdatapoints=dropdatapoints,
skiplines=skiplines,
metadata=metadata,
)


def _open_M2000file_standard(fname, dropdatapoints=1, metadata={}, skiplines=3):
data = []
with possibly_open_file(fname, mode="r") as file:
for i in range(skiplines):
file.readline()

count = 0
while True:
data_row = []

count += 1

line = file.readline().split("\t")
if not line:
break
if len(line) == 1:
break
data_row.append(float(line[0])) # Wavelength
data_row.append(float(line[1])) # Angle
data_row.append(float(line[2])) # Psi
data_row.append(float(line[3])) # Delta
data_row.append(float(line[4])) # Psi Error
data_row.append(float(line[5])) # Delta Error

line = file.readline().split("\t")
data_row.append(float(line[2])) # Unknown
data_row.append(float(line[3])) # Depolarization %
data_row.append(float(line[4])) # Unknown

line = file.readline().split("\t")
data_row.append(float(line[2])) # Unknown
data_row.append(float(line[3])) # Intensity
data_row.append(float(line[4])) # Unknown

data.append(data_row)

data = np.array(data)
data = data[::dropdatapoints]
return DataSE(data[:, [0, 1, 2, 3]].T, **metadata)


def _open_M2000file_kinetic(
fname, take_every=1, dropdatapoints=1, skiplines=4, metadata={}
):
"""
time in seconds
"""
df = pd.read_csv(
fname,
skiprows=skiplines,
sep="\t",
names=[
"Wavelength, nm",
"Angle of incidence, ˚",
"Psi",
"Delta",
"Psi error",
"Delta error",
"None",
"Time, min",
],
)

time_dict = {}
for idx, (time, subdf) in enumerate(df.groupby("Time, min")):
if idx % take_every == 0:
time_dict[np.round(time * 60, 1)] = DataSE(
np.array(
[
subdf["Wavelength, nm"],
subdf["Angle of incidence, ˚"],
subdf["Psi"],
subdf["Delta"],
]
)[:, ::dropdatapoints],
**metadata,
)

return time_dict


def open_FilmSenseFile(fname):
"""
Opens text files exported from FilmSense ellipsometers.


Parameters
----------
fname : file-handle or string
File to load the dataset from.

Returns
----------
datasets : DataSE structure or dict
If the file contains a single measurment, function returns
a single DataSE object containing wavelength, angle of
incidence, psi and delta.

If the file contains multiple measurements, such as a time-
series dataset, then it returns a dictionary containing
DataSE objects for each measurement point. The Dictionary
keys correspond to the measurement details (e.g., time).

Metadata contained within the file is stored in DataSE.metadata.
"""
with possibly_open_file(fname, "r") as f:
header = f.readline()
if header == "Film_Sense_Data\n":
return _open_FilmSenseFile_standard(f)
elif header == "Film_Sense_Dyn_Data\n":
return _open_FilmSenseFile_dynamic(f)
else:
assert False, "Filetype not recognized"


def _parse_FilmSenseFileHeader(firstline, mode="standard"):
firstline = firstline[:-1] # remove newline char
firstline = firstline.split("\t")

metadata = {
"numwvls": int(firstline[0]),
"numdatasets": int(firstline[1]),
"nomAOI": float(firstline[2]),
}

if mode == "standard":
metadata["AlignX"] = float(firstline[3])
metadata["AlignY"] = float(firstline[4])
metadata["AvInten"] = float(firstline[5])
elif mode == "dynamic":
metadata["?"] = float(firstline[3])
else:
assert False, "mode not recognized"

return metadata


def _open_FilmSenseFile_standard(f):
"""
Opens a single measurement captured by FS series
ellipsometers from filmsense. This function is designed to be
called by open_FilmSenseFile.
"""
metadata = _parse_FilmSenseFileHeader(f.readline())

# Note - in the documentation the first numwvls lines are only supposed
# have 4 columns. In these data files they have 8.

df = pd.DataFrame(
columns=[
"Wavelength",
"led_Br",
"led_ExpL",
"led_ExpR",
"N",
"C",
"S",
"P",
"Intensity",
"Delta",
"Psi",
],
index=np.linspace(
1, metadata["numwvls"], metadata["numwvls"], dtype=int
),
)

for i in range(metadata["numwvls"]):
line = f.readline().split("\t")
df.loc[i + 1, "Wavelength"] = float(line[0])
df.loc[i + 1, "led_Br"] = float(line[1])
df.loc[i + 1, "led_ExpL"] = float(line[2])
df.loc[i + 1, "led_ExpR"] = float(line[3])

for i in range(metadata["numwvls"]):
line = f.readline().split("\t")
df.loc[i + 1, "N"] = float(line[0])
df.loc[i + 1, "C"] = float(line[1])
df.loc[i + 1, "S"] = float(line[2])
df.loc[i + 1, "P"] = float(line[3])
df.loc[i + 1, "Intensity"] = float(line[4])

S = np.array(df["S"], dtype=np.float32)
N = np.array(df["N"], dtype=np.float32)
C = np.array(df["C"], dtype=np.float32)
df["Psi"] = np.rad2deg(np.arccos(N) / 2)
# Delta1 = 180+np.rad2deg(np.arctan(np.array(df['S'], dtype=np.float32)/np.array(df['C'], dtype=np.float32)))

df["Delta"] = np.rad2deg(np.angle((C + 1j * S) / (1 + N)))

Psi = np.array(df["Psi"]).astype(np.float64)
Delta = np.array(df["Delta"]).astype(np.float64)

Deltamask = Delta < 0
Delta[Deltamask] = 360 + Delta[Deltamask]

AOI = np.ones_like(Psi) * metadata["nomAOI"]
Wvl = np.array(df["Wavelength"]).astype(np.float64)

return DataSE(data=[Wvl, AOI, Psi, Delta], reflect_delta=False, **metadata)


def _open_FilmSenseFile_dynamic(f):
"""
Opens dynamic (time resolved) data captured by FS series
ellipsometers from filmsense. This function is designed to be
called by open_FilmSenseFile.
"""
metadata = _parse_FilmSenseFileHeader(f.readline(), mode="dynamic")

base_df = pd.DataFrame(
columns=[
"Wavelength",
"led_Br",
"led_ExpL",
"led_ExpR",
"N",
"C",
"S",
"P",
"Intensity",
"Delta",
"Psi",
],
index=np.linspace(
1, metadata["numwvls"], metadata["numwvls"], dtype=int
),
)

for i in range(metadata["numwvls"]):
line = f.readline().split("\t")
base_df.loc[i + 1, "Wavelength"] = float(line[0])
base_df.loc[i + 1, "led_Br"] = float(line[1])
base_df.loc[i + 1, "led_ExpL"] = float(line[2])
base_df.loc[i + 1, "led_ExpR"] = float(line[3])

f.readline() # read header

time_series = {}
for i in range(metadata["numdatasets"]):
line = f.readline()[:-2]
line = line.split("\t")
time = float(line[0])
df = copy.deepcopy(base_df)

for j in range(metadata["numwvls"]):
J = j * 5
df.loc[j + 1, "N"] = float(line[J + 1])
df.loc[j + 1, "C"] = float(line[J + 2])
df.loc[j + 1, "S"] = float(line[J + 3])
df.loc[j + 1, "P"] = float(line[J + 4])
df.loc[j + 1, "Intensity"] = float(line[J + 5])

S = np.array(df["S"], dtype=np.float32)
N = np.array(df["N"], dtype=np.float32)
C = np.array(df["C"], dtype=np.float32)
df["Psi"] = np.rad2deg(np.arccos(N) / 2)
df["Delta"] = np.rad2deg(np.angle((C + 1j * S) / (1 + N)))

Psi = np.array(df["Psi"]).astype(np.float64)
Delta = np.array(df["Delta"]).astype(np.float64)
Deltamask = Delta < 0
Delta[Deltamask] = 360 + Delta[Deltamask]
AOI = np.ones_like(Psi) * metadata["nomAOI"]
Wvl = np.array(df["Wavelength"]).astype(np.float64)

time_series[time] = DataSE(
data=[Wvl, AOI, Psi, Delta], reflect_delta=False, **metadata
)

return time_series
18 changes: 18 additions & 0 deletions refellips/tests/Filmsense_kineticTest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Film_Sense_Dyn_Data
8 7 -70.012 2
367.20 14.32 3.39 2.48 1.218 1.363 0.059
449.64 16.00 6.05 7.27 1.453 1.634 -0.013
525.63 26.05 4.72 2.96 1.275 1.154 -0.107
593.58 14.23 6.64 4.04 1.273 1.046 -0.156
656.32 17.51 6.78 3.84 1.265 0.971 -0.259
732.22 25.33 6.58 3.26 1.263 1.171 -0.228
852.88 23.45 5.67 3.48 1.008 0.929 -0.315
946.73 25.09 3.51 4.97 0.645 0.916 -0.172
Time UV_N UV_C UV_S UV_P UV_I Blue_N Blue_C Blue_S Blue_P Blue_I Green_N Green_C Green_S Green_P Green_I Yellow_N Yellow_C Yellow_S Yellow_P Yellow_I Red_N Red_C Red_S Red_P Red_I IR735_N IR735_C IR735_S IR735_P IR735_I IR850_N IR850_C IR850_S IR850_P IR850_I IR950_N IR950_C IR950_S IR950_P IR950_I AlignX AlignY AveInt Temp TempSrc Fit_Diff Thick(nm).2
4.953 0.58698 -0.69405 0.41683 0.99839 14.1911 0.84641 -0.50868 0.15761 0.99875 21.7871 0.90022 -0.41844 0.12045 0.99955 12.7409 0.92246 -0.37264 0.10105 0.99940 5.5965 0.93427 -0.34544 0.08841 0.99947 15.0567 0.94354 -0.32226 0.07666 0.99956 14.7487 0.95231 -0.29850 0.06330 1.00013 15.0186 0.95683 -0.28524 0.05574 0.99967 11.7999 -0.007 0.002 16.7077 30.72 33.938 0.0018764 3.2528
14.858 0.58698 -0.69407 0.41679 0.99841 14.1889 0.84641 -0.50869 0.15758 0.99875 21.7858 0.90022 -0.41845 0.12043 0.99955 12.7369 0.92246 -0.37265 0.10103 0.99941 5.5882 0.93427 -0.34545 0.08839 0.99947 15.0501 0.94354 -0.32226 0.07664 0.99956 14.7373 0.95231 -0.29850 0.06329 1.00013 15.0115 0.95683 -0.28524 0.05573 0.99967 11.7931 -0.007 0.002 16.6965 30.72 33.969 0.001878 3.2517
24.766 0.58675 -0.69342 0.41821 0.99841 14.1889 0.84630 -0.50851 0.15871 0.99875 21.7836 0.90015 -0.41834 0.12133 0.99955 12.7336 0.92241 -0.37256 0.10181 0.99942 5.5826 0.93422 -0.34538 0.08908 0.99947 15.0458 0.94351 -0.32221 0.07725 0.99956 14.7303 0.95228 -0.29847 0.06379 1.00013 15.0072 0.95682 -0.28521 0.05618 0.99967 11.7882 -0.007 0.002 16.6955 30.75 33.969 0.001874 3.2938
34.663 0.58676 -0.69346 0.41812 0.99841 14.1883 0.84631 -0.50852 0.15863 0.99873 21.7835 0.90016 -0.41834 0.12128 0.99954 12.7327 0.92241 -0.37257 0.10174 0.99941 5.5801 0.93423 -0.34539 0.08903 0.99945 15.0444 0.94351 -0.32221 0.07721 0.99954 14.7268 0.95229 -0.29847 0.06376 1.00013 15.0051 0.95682 -0.28521 0.05615 0.99967 11.7849 -0.006 0.002 16.6952 30.72 34.000 0.0018767 3.2908
44.570 0.58659 -0.69294 0.41922 0.99853 14.1755 0.84629 -0.50830 0.15944 0.99895 21.7754 0.90016 -0.41817 0.12186 0.99978 12.7226 0.92242 -0.37241 0.10220 0.99964 5.5743 0.93424 -0.34526 0.08940 0.99969 15.0367 0.94352 -0.32212 0.07751 0.99975 14.7155 0.95229 -0.29840 0.06399 1.00029 14.9982 0.95682 -0.28516 0.05635 0.99981 11.7744 -0.007 0.002 16.6919 30.72 33.969 0.0018933 3.3196
54.475 0.58657 -0.69252 0.41994 0.99860 14.1648 0.84631 -0.50812 0.15994 0.99911 21.7735 0.90016 -0.41803 0.12230 0.99996 12.7178 0.92243 -0.37229 0.10260 0.99981 5.5718 0.93424 -0.34516 0.08977 0.99985 15.0339 0.94352 -0.32204 0.07786 0.99990 14.7093 0.95229 -0.29834 0.06429 1.00042 14.9935 0.95682 -0.28510 0.05662 0.99992 11.7640 -0.005 0.001 16.6822 30.72 33.938 0.0018738 3.3412
64.379 0.58705 -0.69198 0.42017 0.99822 14.4094 0.84633 -0.50789 0.16053 0.99838 21.9729 0.90015 -0.41792 0.12280 0.99914 12.8851 0.92239 -0.37225 0.10307 0.99898 5.6434 0.93421 -0.34514 0.09021 0.99906 15.2389 0.94349 -0.32202 0.07827 0.99911 14.9515 0.95227 -0.29832 0.06464 0.99975 15.2378 0.95681 -0.28507 0.05692 0.99936 12.0183 0.017 0.021 16.9638 30.72 33.969 0.0017164 3.3601
Loading