Task 695: .STO File Format
Task 695: .STO File Format
1. List of Properties for .STO File Format
Based on the specifications of the .STO file format (as used in OpenSim for storing simulation data, such as forces, kinematics, etc.), the intrinsic properties are the header fields that define the structure and metadata of the file. The format is text-based with a header followed by tab-delimited data. The properties are:
- Name: The name by which the file is referred (first line of the header, e.g., "Actuation_force").
- version: The version number of the file format (e.g., 1; optional in older files).
- inDegrees: Indicates if angular data is in degrees (yes/no; optional, defaults to no for radians in old files).
- nRows: The number of rows of data in the file (excluding header and labels).
- nColumns: The number of columns of data (including the time column).
- Column Labels: A list of labels for each column (e.g., time, force_x, etc.; one line after endheader).
- Data: The tab-delimited data matrix (nRows x nColumns; not a "property" per se, but can be dumped as part of reading).
These properties are extracted from the header lines before "endheader". The time column is always the first and may have non-uniform spacing.
2. Two Direct Download Links for .STO Files
- https://raw.githubusercontent.com/opensim-org/opensim-models/master/Models/Gait2354_Simbody/OutputReference/ResultsRRA/subject01_walk1_RRA_Actuation_force.sto
- https://raw.githubusercontent.com/opensim-org/opensim-models/master/Models/Gait2354_Simbody/OutputReference/ResultsRRA/subject01_walk1_RRA_Kinematics_q.sto
3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .STO File Dump
This is an HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post. It creates a drag-and-drop area where a user can drop a .STO file, parses it, and dumps the properties to the screen.
Drag and drop a .STO file here
4. Python Class for .STO File Handling
import os
class STOFile:
def __init__(self, filepath=None):
self.name = None
self.version = None
self.in_degrees = 'no'
self.n_rows = None
self.n_columns = None
self.column_labels = []
self.data = [] # List of lists for data rows
if filepath:
self.read(filepath)
def read(self, filepath):
with open(filepath, 'r') as f:
lines = f.readlines()
self.name = lines[0].strip() if lines else 'Unknown'
i = 1
while i < len(lines):
line = lines[i].strip()
if line.startswith('version='):
self.version = int(line.split('=')[1])
elif line.startswith('inDegrees='):
self.in_degrees = line.split('=')[1].lower()
elif line.startswith('nRows='):
self.n_rows = int(line.split('=')[1])
elif line.startswith('nColumns='):
self.n_columns = int(line.split('=')[1])
elif line == 'endheader':
# Next line is column labels
if i + 1 < len(lines):
self.column_labels = lines[i + 1].strip().split()
# Remaining lines are data
for j in range(i + 2, len(lines)):
row = lines[j].strip().split()
if row:
self.data.append([float(val) for val in row])
break
i += 1
def print_properties(self):
print(f"Name: {self.name}")
print(f"Version: {self.version}")
print(f"inDegrees: {self.in_degrees}")
print(f"nRows: {self.n_rows}")
print(f"nColumns: {self.n_columns}")
print(f"Column Labels: {', '.join(self.column_labels)}")
def write(self, filepath):
with open(filepath, 'w') as f:
f.write(f"{self.name}\n")
if self.version is not None:
f.write(f"version={self.version}\n")
f.write(f"inDegrees={self.in_degrees}\n")
f.write(f"nRows={len(self.data)}\n")
f.write(f"nColumns={len(self.column_labels)}\n")
f.write("endheader\n")
f.write('\t'.join(self.column_labels) + '\n')
for row in self.data:
f.write('\t'.join(map(str, row)) + '\n')
# Example usage:
# sto = STOFile('example.sto')
# sto.print_properties()
# sto.write('new.sto')
5. Java Class for .STO File Handling
import java.io.*;
import java.util.*;
public class STOFile {
private String name;
private Integer version;
private String inDegrees = "no";
private Integer nRows;
private Integer nColumns;
private List<String> columnLabels = new ArrayList<>();
private List<List<Double>> data = new ArrayList<>();
public STOFile(String filepath) throws IOException {
if (filepath != null) {
read(filepath);
}
}
public void read(String filepath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line = reader.readLine();
name = (line != null) ? line.trim() : "Unknown";
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("version=")) {
version = Integer.parseInt(line.split("=")[1]);
} else if (line.startsWith("inDegrees=")) {
inDegrees = line.split("=")[1].toLowerCase();
} else if (line.startsWith("nRows=")) {
nRows = Integer.parseInt(line.split("=")[1]);
} else if (line.startsWith("nColumns=")) {
nColumns = Integer.parseInt(line.split("=")[1]);
} else if (line.equals("endheader")) {
// Next line is column labels
line = reader.readLine();
if (line != null) {
columnLabels = Arrays.asList(line.trim().split("\\s+"));
}
// Read data
while ((line = reader.readLine()) != null) {
String[] parts = line.trim().split("\\s+");
if (parts.length > 0) {
List<Double> row = new ArrayList<>();
for (String part : parts) {
row.add(Double.parseDouble(part));
}
data.add(row);
}
}
break;
}
}
}
}
public void printProperties() {
System.out.println("Name: " + name);
System.out.println("Version: " + version);
System.out.println("inDegrees: " + inDegrees);
System.out.println("nRows: " + nRows);
System.out.println("nColumns: " + nColumns);
System.out.println("Column Labels: " + String.join(", ", columnLabels));
}
public void write(String filepath) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
writer.write(name + "\n");
if (version != null) {
writer.write("version=" + version + "\n");
}
writer.write("inDegrees=" + inDegrees + "\n");
writer.write("nRows=" + data.size() + "\n");
writer.write("nColumns=" + columnLabels.size() + "\n");
writer.write("endheader\n");
writer.write(String.join("\t", columnLabels) + "\n");
for (List<Double> row : data) {
writer.write(row.stream().map(Object::toString).collect(Collectors.joining("\t")) + "\n");
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// STOFile sto = new STOFile("example.sto");
// sto.printProperties();
// sto.write("new.sto");
// }
}
6. JavaScript Class for .STO File Handling
class STOFile {
constructor(filepath = null) {
this.name = null;
this.version = null;
this.inDegrees = 'no';
this.nRows = null;
this.nColumns = null;
this.columnLabels = [];
this.data = []; // Array of arrays for data rows
if (filepath) {
this.read(filepath); // Note: Async read requires promises or callbacks
}
}
async read(filepath) {
try {
const response = await fetch(filepath);
const content = await response.text();
const lines = content.split('\n').map(line => line.trim());
this.name = lines[0] || 'Unknown';
let i = 1;
while (i < lines.length) {
if (lines[i].startsWith('version=')) {
this.version = parseInt(lines[i].split('=')[1]);
} else if (lines[i].startsWith('inDegrees=')) {
this.inDegrees = lines[i].split('=')[1].toLowerCase();
} else if (lines[i].startsWith('nRows=')) {
this.nRows = parseInt(lines[i].split('=')[1]);
} else if (lines[i].startsWith('nColumns=')) {
this.nColumns = parseInt(lines[i].split('=')[1]);
} else if (lines[i] === 'endheader') {
if (i + 1 < lines.length) {
this.columnLabels = lines[i + 1].split(/\s+/).filter(label => label);
}
for (let j = i + 2; j < lines.length; j++) {
const row = lines[j].split(/\s+/).filter(val => val).map(parseFloat);
if (row.length > 0) {
this.data.push(row);
}
}
break;
}
i++;
}
} catch (error) {
console.error('Error reading file:', error);
}
}
printProperties() {
console.log(`Name: ${this.name}`);
console.log(`Version: ${this.version}`);
console.log(`inDegrees: ${this.inDegrees}`);
console.log(`nRows: ${this.nRows}`);
console.log(`nColumns: ${this.nColumns}`);
console.log(`Column Labels: ${this.columnLabels.join(', ')}`);
}
write(filepath) {
// Note: In browser, writing files requires user interaction (e.g., download link)
// For Node.js, use fs module
const content = [
this.name,
this.version !== null ? `version=${this.version}` : '',
`inDegrees=${this.inDegrees}`,
`nRows=${this.data.length}`,
`nColumns=${this.columnLabels.length}`,
'endheader',
this.columnLabels.join('\t'),
...this.data.map(row => row.join('\t'))
].filter(line => line).join('\n');
// Example for browser: create download
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filepath;
a.click();
URL.revokeObjectURL(url);
}
}
// Example usage (async):
// const sto = new STOFile();
// await sto.read('example.sto');
// sto.printProperties();
// sto.write('new.sto');
7. C Class (Implemented as Struct with Functions in C++ for Class-Like Behavior)
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
struct STOFile {
std::string name;
int version = -1; // -1 indicates unknown
std::string inDegrees = "no";
int nRows = -1;
int nColumns = -1;
std::vector<std::string> columnLabels;
std::vector<std::vector<double>> data;
STOFile(const std::string& filepath = "") {
if (!filepath.empty()) {
read(filepath);
}
}
void read(const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error opening file: " << filepath << std::endl;
return;
}
std::string line;
if (std::getline(file, line)) {
name = line;
} else {
name = "Unknown";
}
bool headerEnded = false;
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string key;
iss >> key;
if (key.find("version=") == 0) {
sscanf(key.c_str(), "version=%d", &version);
} else if (key.find("inDegrees=") == 0) {
inDegrees = key.substr(10);
} else if (key.find("nRows=") == 0) {
sscanf(key.c_str(), "nRows=%d", &nRows);
} else if (key.find("nColumns=") == 0) {
sscanf(key.c_str(), "nColumns=%d", &nColumns);
} else if (key == "endheader") {
headerEnded = true;
// Next line: column labels
if (std::getline(file, line)) {
std::istringstream labelIss(line);
std::string label;
while (labelIss >> label) {
columnLabels.push_back(label);
}
}
// Read data
while (std::getline(file, line)) {
if (line.empty()) continue;
std::istringstream dataIss(line);
std::vector<double> row;
double val;
while (dataIss >> val) {
row.push_back(val);
}
if (!row.empty()) {
data.push_back(row);
}
}
break;
}
}
file.close();
}
void printProperties() const {
std::cout << "Name: " << name << std::endl;
std::cout << "Version: " << (version != -1 ? std::to_string(version) : "Unknown") << std::endl;
std::cout << "inDegrees: " << inDegrees << std::endl;
std::cout << "nRows: " << nRows << std::endl;
std::cout << "nColumns: " << nColumns << std::endl;
std::cout << "Column Labels: ";
for (size_t i = 0; i < columnLabels.size(); ++i) {
std::cout << columnLabels[i];
if (i < columnLabels.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
}
void write(const std::string& filepath) const {
std::ofstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error writing file: " << filepath << std::endl;
return;
}
file << name << "\n";
if (version != -1) {
file << "version=" << version << "\n";
}
file << "inDegrees=" << inDegrees << "\n";
file << "nRows=" << data.size() << "\n";
file << "nColumns=" << columnLabels.size() << "\n";
file << "endheader\n";
for (const auto& label : columnLabels) {
file << label << "\t";
}
file << "\n";
for (const auto& row : data) {
for (size_t i = 0; i < row.size(); ++i) {
file << row[i];
if (i < row.size() - 1) file << "\t";
}
file << "\n";
}
file.close();
}
};
// Example usage:
// int main() {
// STOFile sto("example.sto");
// sto.printProperties();
// sto.write("new.sto");
// return 0;
// }