Read in-situ metadata from *.csv
Python Script
Script to import data from a CSV file, creating an in-situ plot.
Preview
# -*- coding: latin-1 -*-
'''
Script to import data from a CSV file, creating an in-situ plot
Run the script, then select a csv file via the provided UI
If data from the same time period is already open in DigitalMicrograph,
it will be automatically synced with plot produced by this script.
The plot is interactive, so clicking within the plot will update the playback
position of all concurrent in-situ datasets open in the same workspace.
The interactive plot is not automatically saved to disk, but can be
saved as a DM4 file for later use.
Here is an example of what a CSV might look like. For this code to work as-is
the CSV must have this format, with metadata in column 1 and data in subsequent columns.
Any one of End Date/Time, Time Interval, or Duration are sufficient
Metadata Info Temperature (°C)
Time Column? (0:False 1:True) 50.004257
0 50.004406
Start Date (YYYY-MM-DD) 50.004654
2023-10-18 50.00338
Start Time (Hrs:min:sec.msec) 50.00135
10:05:07.002 49.999134
End Date (YYYY-MM-DD) 49.997536
"" 49.997089
End Time (Hrs:min:sec:msec) 49.998394
"" 49.999859
Time Interval (s) 50.001385
"" 50.002232
Duration (s) 50.003418
2480.5 50.004368
Device Type (string) 50.005623
Heating2 50.006329
Device Name (string) 50.005596
Wildfire Holder Test 50.003571
Code written by Ben Miller
Last Updated May 2025
'''
import pandas as pd
import numpy as np
import os, sys
import re
import tkinter as tk
from pathlib import PureWindowsPath
import tkinter.filedialog as tkfd
from datetime import datetime, timedelta
# Function to get file location using file dialog
def GetSelectedFile():
"""
Function to Get File Location with a File Dialog
Returns:
listOfFiles (list of strings): List of files selected by user
"""
sys.argv.extend(['-a', ' '])
root = tk.Tk()
root.withdraw() # Use to hide tkinter window
currdir = os.getcwd()
listOfFiles = list(tkfd.askopenfilenames(parent=root, title='Choose CSV File'))
if len(listOfFiles) > 0:
os.chdir(os.path.dirname(listOfFiles[0]))
root.destroy()
return listOfFiles
# Function to compute variables based on provided inputs
def compute_variables(start_time_str, N, end_time_str=None, time_interval=None, duration=None):
"""
Compute the necessary variables for plotting based on provided inputs.
Args:
start_time_str (str): Start time in string format.
N (int): Number of data points.
end_time_str (str, optional): End time in string format. Defaults to None.
time_interval (float, optional): Time interval between data points. Defaults to None.
duration (float, optional): Duration of the experiment. Defaults to None.
Returns:
tuple: Computed start_time, end_time, time_interval, and duration.
"""
# Check which variables are provided
provided_vars = {k: v for k, v in locals().items() if v is not None and k not in ['start_time_str', 'N']}
if len(provided_vars) == 3:
print("You have provided all three variables. Please specify which one to ignore.")
ignore_var = input(f"Which variable to ignore ({', '.join(provided_vars.keys())})? ")
provided_vars.pop(ignore_var)
# Convert start_time and end_time from strings to datetime objects
start_time = datetime.strptime(start_time_str, '%Y-%m-%d %H:%M:%S.%f')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S.%f') if end_time_str else np.nan
# Compute missing variables based on provided ones
if len(provided_vars) == 2:
if 'end_time_str' in provided_vars and 'duration' in provided_vars:
time_interval = provided_vars['duration'] / N
elif 'end_time_str' in provided_vars and 'time_interval' in provided_vars:
duration = provided_vars['time_interval'] * N
elif 'duration' in provided_vars and 'time_interval' in provided_vars:
end_time = start_time + timedelta(seconds=provided_vars['duration'])
if len(provided_vars) == 1:
print("provided 1")
if 'end_time_str' in provided_vars:
print("Provided end")
duration = (end_time - start_time).total_seconds()
time_interval = duration / N
elif 'duration' in provided_vars:
end_time = start_time + timedelta(seconds=provided_vars['duration'])
time_interval = provided_vars['duration'] / N
elif 'time_interval' in provided_vars:
duration = provided_vars['time_interval'] * N
end_time = start_time + timedelta(seconds=duration)
print((start_time, end_time, time_interval, duration))
return start_time, end_time, time_interval, duration
# Function to get system ticks from time
def system_ticks_from_time(start_time):
"""
Get system ticks from the provided start time.
Args:
start_time (datetime): Start time.
Returns:
str: System ticks corresponding to the start time.
"""
date_str, time_str = str(start_time).split()
dm_script = 'string date = "' + date_str + '"\n string time = "' + time_str + '"\n'
dm_script += '''
string ParseDateFormat = "%Y-%m-%d"
string parseTimeFormat = "%H:%M:%S"
string ExpStartTICKS = CHRONO_GetSystemClockTicksStringFromDateAndTime(date,time,ParseDateFormat,parseTimeFormat)
GetPersistentTagGroup().TagGroupSetTagAsString("Python_temp:Start_Ticks",ExpStartTICKS)'''
DM.ExecuteScriptString(dm_script)
bool, ExpStartTICKS = DM.GetPersistentTagGroup().GetTagAsString("Python_temp:Start_Ticks")
return ExpStartTICKS
# Function to set in-situ tagging for the plot
def set_IS_tagging(plot, info):
"""
Set in-situ tagging for the plot based on provided info.
Args:
plot (object): Plot object.
info (list): List of metadata information.
"""
info = info.copy()
for i, item in enumerate(info[:20]):
try:
if np.isnan(item): info[i] = None
except: pass
try:
info[i] = float(info[i])
except: pass
has_time_col = int(info[1])
if has_time_col: return # Not yet implemented
start_time = info[3] + " " + str(info[5])
try:
end_time = info[7] + " " + str(info[9])
except: end_time = None
time_interval = info[11]
duration = info[13]
device_type = info[15]
device_name = info[17]
N = len(plot.GetNumArray())
print((start_time, N, end_time, time_interval, duration))
start_time, end_time, time_interval, duration = compute_variables(start_time, N, end_time, time_interval, duration)
ExpStartTICKS = system_ticks_from_time(start_time)
print(type(time_interval))
plot.SetDimensionScale(0,time_interval)
plot.SetDimensionUnitString(0,"s")
plot.GetTagGroup().SetTagAsString("In-situ:Recorded:Sync:Experiment Start (System-clock tick)", ExpStartTICKS)
plot.GetTagGroup().SetTagAsString("In-situ:Recorded:Experiment Start Time", str(start_time))
plot.GetTagGroup().SetTagAsFloat("In-situ:Recorded:Duration (s)", duration)
plot.GetTagGroup().SetTagAsLong("In-situ:Recorded:# Frames", N)
plot.GetTagGroup().SetTagAsFloat("In-situ:Recorded:Sync:Linear:Frame Rate (fps)", 1 / time_interval)
plot.GetTagGroup().SetTagAsString("In-situ:Databar Info:Device Name", device_name)
plot.GetTagGroup().SetTagAsString("In-situ:Databar Info:Device Type", device_type)
plot.GetTagGroup().SetTagAsString("Meta Data:Function", "InSitu profile")
# Function to extract content within parentheses
def extract_parentheses_content(text):
"""
Extract content within parentheses from the provided text.
Args:
text (str): Text containing parentheses.
Returns:
str: Content within parentheses.
"""
match = re.search(r'', text)
if match: return match.group(1)
else: return None
# Main script execution
# Read the CSV file
filepath = GetSelectedFile()[0]
df = pd.read_csv(filepath)
csv_info = df.iloc[:, 0]
# Check the number of columns
num_columns = df.shape[1]
# Extract all columns except the first one
arrays = [df.iloc[:, i].values for i in range(1, num_columns)]
# Convert to numpy arrays
numpy_arrays = [np.array(arr) for arr in arrays]
# Plot the numpy arrays as in-situ plots in DigitalMicrograph compatible with the IS Player
for idx, array in enumerate(numpy_arrays, start=1):
plot = DM.CreateImage(array)
set_IS_tagging(plot,csv_info)
#Set Data name and units from header row in CSV
name = df.columns[idx].split("(")[0].strip()
plotpath = os.path.join(os.path.dirname(filepath),"Imported_IS_Plots",name+".dm4")
if not os.path.exists(os.path.dirname(plotpath)): os.makedirs(os.path.dirname(plotpath))
unit = extract_parentheses_content(df.columns[idx])
if unit is not None: plot.SetIntensityUnitString(unit) #note that DM has a problem displaying the degree symbol
#show the plot temporarily so we can more easily add the Slice Label with a bit of DM script
plot.ShowImage()
#set lineplot slice name (used for databars) and axis labels and save the plot
dm_legend_names = ('ImageDisplay disp = GetFrontImage().ImageGetImageDisplay( 0 )\n'
'ImageSetDimensionLabel( GetFrontImage(), 0, "Time" )\n'
'ImageSetIntensityLabel( GetFrontImage(), "'+name+'" )\n'
'disp.ImageDisplaySetSliceLabelByID(disp.ImageDisplayGetSliceIDByIndex( 0 ),"'+name+'")\n')
DM.ExecuteScriptString(dm_legend_names)
plot_doc = plot.GetOrCreateImageDocument()
plot_doc.SaveToFile('Gatan Format',plotpath)
DM.Sleep(0.1)
#close the plot and then re-display it so it syncs correctly with concurrent IS data
plot_doc.Close(False)
doc = DM.NewImageDocumentFromFile(plotpath)
doc.Show()
print("Done")