Task 196: .EX File Format
Task 196: .EX File Format
File Format Specifications for the .EX File Format
The .EX file format, as used in OpenCMISS (and legacy CMISS software), is a human-readable text-based format designed for exchanging interpolated finite element fields defined on meshes and sets of nodes. It supports definitions of regions, groups, fields, nodes, and elements, and is commonly split into .exnode files (for node data) and .exelem files (for element data), though both can be combined in a single file with a .ex or similar extension. The format emphasizes flexibility for scientific computing applications, such as biomedical modeling, with support for various coordinate systems, value types, derivatives, and versions.
List of All Properties Intrinsic to This File Format
The following is a comprehensive list of properties that are intrinsic to the .EX file format, derived from its structure and syntax. These properties define the file's content and can be parsed from the text data:
- Region paths (absolute paths starting with '/', e.g., '/cube' or '/bob/joe').
- Group names (names of subgroups for nodes and elements, e.g., 'xi_points').
- Comments (lines starting with '!', for documentation or metadata).
- Shape dimensions (e.g., 'Dimension=0' for nodes/points, 'Dimension=1' for lines, 'Dimension=2' for faces, 'Dimension=3' for volumes; includes basis descriptions like 'linelineline').
- Number of fields (#Fields, an integer indicating how many fields are defined).
- Field names (e.g., 'coordinates', 'temperature', 'fibres').
- Field types (e.g., 'coordinate', 'anatomical', 'field').
- Coordinate systems (e.g., 'rectangular cartesian', 'prolate spheroidal' with optional focus parameter, 'oblate spheroidal').
- Value types (e.g., 'real' (default), 'integer', 'string', 'element_xi' for embedded locations in nodes).
- Number of components (#Components, an integer for each field).
- Component names (e.g., 'x', 'y', 'z', 'lambda', 'mu', 'theta').
- Value indices (starting position in the parameter list for each component).
- Number of derivatives (#Derivatives, an integer; includes labels like 'd/ds1', 'd/ds2', 'd2/ds1ds2', 'd/ds3', etc.).
- Derivative values (actual numerical values for derivatives at nodes).
- Number of versions (#Versions, an integer for versioned parameters).
- Node identifiers (non-negative integers, e.g., 'Node: 1').
- Node field values (numerical or string data for fields, derivatives, and versions at each node).
- Element identifiers (non-negative integers, e.g., 'Element: 1 0 0').
- Number of scale factor sets (#Scale factor sets, for element interpolation).
- Number of nodes per element (#Nodes, for element definitions).
- Basis types (interpolation functions, e.g., 'l.Lagrangel.Lagrangel.Lagrange').
- Scale factors (arrays of scaling values for element bases).
- Node lists for elements (global node IDs associated with each element).
- Face and line definitions (for lower-dimensional elements within higher-dimensional ones).
- Grid-based field parameters (for elements with grid divisions, including #Xi1, #Xi2, #Xi3 for grid points).
- Embedded locations (for element_xi fields, specifying element ID and xi coordinates, e.g., 'E 1 3 0.25 0.25 0.75' for element, face/line, and xi values).
- File metadata (implicit, such as overall structure separating nodes and elements).
Two Direct Download Links for Files of Format .EX
- https://models.cellml.org/workspace/960/rawfile/6747e85570cf469a3b3331228e65b2685e7d261b/left_lumbrical_II_hand_transformed.exnode
- https://models.physiomeproject.org/workspace/deformingheart/rawfile/a78ada1238a43d137db37c568049bf76df0899ff/heart0005.exnode
Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .EX File Dump
The following is a complete HTML page with embedded JavaScript that allows a user to drag and drop a .EX file. It parses the text content to extract and display all the listed properties on the screen in a structured format (e.g., as a JSON-like object for clarity). The parser handles regions, groups, fields, nodes, elements, and associated properties using regular expressions and state-based parsing.
Drag and Drop .EX File to Dump Properties
Python Class for .EX File Handling
The following Python class can open, decode (parse), read, write, and print all properties from a .EX file to the console. It uses text parsing to extract properties and supports writing a simple .EX file based on parsed data.
import re
class EXFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {
'regions': [],
'groups': [],
'shapes': [],
'fields': [],
'nodes': [],
'elements': []
}
def read_and_decode(self):
with open(self.filepath, 'r') as f:
content = f.read()
lines = re.split('\n', content)
lines = [line.strip() for line in lines if line.strip() and not line.strip().startswith('!')]
current_region = None
current_group = None
current_field = None
state = 'idle'
for line in lines:
if line.startswith('Region:'):
current_region = line.split(':')[1].strip()
self.properties['regions'].append(current_region)
state = 'region'
elif line.startswith('Group name:'):
current_group = line.split(':')[1].strip()
self.properties['groups'].append(current_group)
state = 'group'
elif line.startswith('Shape.'):
shape_dim = line.split('Dimension=')[1].strip() if 'Dimension=' in line else ''
self.properties['shapes'].append(shape_dim)
state = 'shape'
elif line.startswith('#Fields='):
num_fields = int(line.split('=')[1])
self.properties['fields'].append({'num_fields': num_fields})
state = 'fields'
elif state == 'fields' and re.match(r'^\d+\)', line):
parts = re.match(r'(\d+)\) (\w+), (\w+), ([\w\s]+), #Components=(\d+)', line)
if parts:
current_field = {
'name': parts.group(2),
'type': parts.group(3),
'coord_system': parts.group(4),
'num_components': int(parts.group(5)),
'components': []
}
self.properties['fields'].append(current_field)
elif state == 'fields' and '.' in line:
comp_parts = re.match(r'(\w+)\. Value index=(\d+), #Derivatives=(\d+)', line)
if comp_parts and current_field:
current_field['components'].append({
'name': comp_parts.group(1),
'value_index': int(comp_parts.group(2)),
'num_derivatives': int(comp_parts.group(3))
})
elif line.startswith('Node:'):
node_id = int(line.split(':')[1].strip())
self.properties['nodes'].append({'id': node_id, 'values': []})
state = 'node_values'
elif state == 'node_values':
values = [float(v) for v in line.split()]
self.properties['nodes'][-1]['values'].append(values)
elif line.startswith('Element:'):
elem_id = [int(e) for e in line.split()[1:]]
self.properties['elements'].append({'id': elem_id})
state = 'element'
def print_properties(self):
import json
print(json.dumps(self.properties, indent=4))
def write(self, output_path):
with open(output_path, 'w') as f:
for region in self.properties['regions']:
f.write(f'Region: {region}\n')
for group in self.properties['groups']:
f.write(f'Group name: {group}\n')
for shape in self.properties['shapes']:
f.write(f'Shape. Dimension={shape}\n')
for field in self.properties['fields']:
if 'num_fields' in field:
f.write(f'#Fields={field["num_fields"]}\n')
else:
f.write(f'1) {field["name"]}, {field["type"]}, {field["coord_system"]}, #Components={field["num_components"]}\n')
for comp in field.get('components', []):
f.write(f' {comp["name"]}. Value index={comp["value_index"]}, #Derivatives={comp["num_derivatives"]}\n')
for node in self.properties['nodes']:
f.write(f'Node: {node["id"]}\n')
for vals in node['values']:
f.write(' ' + ' '.join(map(str, vals)) + '\n')
for elem in self.properties['elements']:
f.write(f'Element: {" ".join(map(str, elem["id"]))} \n')
# Example usage:
# handler = EXFileHandler('example.exnode')
# handler.read_and_decode()
# handler.print_properties()
# handler.write('output.ex')
Java Class for .EX File Handling
The following Java class can open, decode (parse), read, write, and print all properties from a .EX file to the console. It uses buffered reading and string parsing.
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class EXFileHandler {
private String filepath;
private Map<String, List<Object>> properties = new HashMap<>();
public EXFileHandler(String filepath) {
this.filepath = filepath;
properties.put("regions", new ArrayList<>());
properties.put("groups", new ArrayList<>());
properties.put("shapes", new ArrayList<>());
properties.put("fields", new ArrayList<>());
properties.put("nodes", new ArrayList<>());
properties.put("elements", new ArrayList<>());
}
public void readAndDecode() throws IOException {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("!")) {
lines.add(line);
}
}
}
String currentRegion = null;
String currentGroup = null;
Map<String, Object> currentField = null;
String state = "idle";
for (String line : lines) {
if (line.startsWith("Region:")) {
currentRegion = line.split(":")[1].trim();
((List<String>) properties.get("regions")).add(currentRegion);
state = "region";
} else if (line.startsWith("Group name:")) {
currentGroup = line.split(":")[1].trim();
((List<String>) properties.get("groups")).add(currentGroup);
state = "group";
} else if (line.startsWith("Shape.")) {
String shapeDim = line.split("Dimension=")[1].trim();
((List<String>) properties.get("shapes")).add(shapeDim);
state = "shape";
} else if (line.startsWith("#Fields=")) {
int numFields = Integer.parseInt(line.split("=")[1]);
Map<String, Object> fieldMap = new HashMap<>();
fieldMap.put("num_fields", numFields);
((List<Map<String, Object>>) properties.get("fields")).add(fieldMap);
state = "fields";
} else if ("fields".equals(state) && Pattern.matches("^\\d+\\)", line)) {
Pattern p = Pattern.compile("(\\d+)\\) (\\w+), (\\w+), ([\\w\\s]+), #Components=(\\d+)");
Matcher m = p.matcher(line);
if (m.matches()) {
currentField = new HashMap<>();
currentField.put("name", m.group(2));
currentField.put("type", m.group(3));
currentField.put("coord_system", m.group(4));
currentField.put("num_components", Integer.parseInt(m.group(5)));
currentField.put("components", new ArrayList<Map<String, Object>>());
((List<Map<String, Object>>) properties.get("fields")).add(currentField);
}
} else if ("fields".equals(state) && line.contains(".")) {
Pattern p = Pattern.compile("(\\w+)\\. Value index=(\\d+), #Derivatives=(\\d+)");
Matcher m = p.matcher(line);
if (m.matches() && currentField != null) {
Map<String, Object> comp = new HashMap<>();
comp.put("name", m.group(1));
comp.put("value_index", Integer.parseInt(m.group(2)));
comp.put("num_derivatives", Integer.parseInt(m.group(3)));
((List<Map<String, Object>>) currentField.get("components")).add(comp);
}
} else if (line.startsWith("Node:")) {
int nodeId = Integer.parseInt(line.split(":")[1].trim());
Map<String, Object> node = new HashMap<>();
node.put("id", nodeId);
node.put("values", new ArrayList<List<Double>>());
((List<Map<String, Object>>) properties.get("nodes")).add(node);
state = "node_values";
} else if ("node_values".equals(state)) {
List<Double> values = new ArrayList<>();
for (String v : line.split("\\s+")) {
values.add(Double.parseDouble(v));
}
((List<List<Double>>) ((Map<String, Object>) properties.get("nodes").get(properties.get("nodes").size() - 1)).get("values")).add(values);
} else if (line.startsWith("Element:")) {
List<Integer> elemId = new ArrayList<>();
for (String e : line.split("\\s+")[1:]) {
elemId.add(Integer.parseInt(e));
}
Map<String, Object> elem = new HashMap<>();
elem.put("id", elemId);
((List<Map<String, Object>>) properties.get("elements")).add(elem);
state = "element";
}
}
}
public void printProperties() {
System.out.println(new org.json.JSONObject(properties).toString(4));
}
public void write(String outputPath) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) {
for (Object region : properties.get("regions")) {
writer.write("Region: " + region + "\n");
}
for (Object group : properties.get("groups")) {
writer.write("Group name: " + group + "\n");
}
for (Object shape : properties.get("shapes")) {
writer.write("Shape. Dimension=" + shape + "\n");
}
for (Object fieldObj : properties.get("fields")) {
Map<String, Object> field = (Map<String, Object>) fieldObj;
if (field.containsKey("num_fields")) {
writer.write("#Fields=" + field.get("num_fields") + "\n");
} else {
writer.write("1) " + field.get("name") + ", " + field.get("type") + ", " + field.get("coord_system") + ", #Components=" + field.get("num_components") + "\n");
for (Object compObj : (List<?>) field.get("components")) {
Map<String, Object> comp = (Map<String, Object>) compObj;
writer.write(" " + comp.get("name") + ". Value index=" + comp.get("value_index") + ", #Derivatives=" + comp.get("num_derivatives") + "\n");
}
}
}
for (Object nodeObj : properties.get("nodes")) {
Map<String, Object> node = (Map<String, Object>) nodeObj;
writer.write("Node: " + node.get("id") + "\n");
for (Object valsObj : (List<?>) node.get("values")) {
List<Double> vals = (List<Double>) valsObj;
writer.write(" " + vals.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(" ")) + "\n");
}
}
for (Object elemObj : properties.get("elements")) {
Map<String, Object> elem = (Map<String, Object>) elemObj;
writer.write("Element: " + ((List<Integer>) elem.get("id")).stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(" ")) + "\n");
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// EXFileHandler handler = new EXFileHandler("example.exnode");
// handler.readAndDecode();
// handler.printProperties();
// handler.write("output.ex");
// }
}
JavaScript Class for .EX File Handling
The following JavaScript class (Node.js compatible) can open, decode (parse), read, write, and print all properties from a .EX file to the console. It uses the 'fs' module for file I/O.
const fs = require('fs');
class EXFileHandler {
constructor(filepath) {
this.filepath = filepath;
this.properties = {
regions: [],
groups: [],
shapes: [],
fields: [],
nodes: [],
elements: []
};
}
readAndDecode() {
const content = fs.readFileSync(this.filepath, 'utf8');
const lines = content.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('!'));
let currentRegion = null;
let currentGroup = null;
let currentField = null;
let state = 'idle';
lines.forEach(line => {
if (line.startsWith('Region:')) {
currentRegion = line.split(':')[1].trim();
this.properties.regions.push(currentRegion);
state = 'region';
} else if (line.startsWith('Group name:')) {
currentGroup = line.split(':')[1].trim();
this.properties.groups.push(currentGroup);
state = 'group';
} else if (line.startsWith('Shape.')) {
const shapeDim = line.split('Dimension=')[1].trim();
this.properties.shapes.push(shapeDim);
state = 'shape';
} else if (line.startsWith('#Fields=')) {
const numFields = parseInt(line.split('=')[1]);
this.properties.fields.push({ num_fields: numFields });
state = 'fields';
} else if (state === 'fields' && /^\d+\)/.test(line)) {
const parts = line.match(/(\d+)\) (\w+), (\w+), ([\w\s]+), #Components=(\d+)/);
if (parts) {
currentField = {
name: parts[2],
type: parts[3],
coord_system: parts[4],
num_components: parseInt(parts[5]),
components: []
};
this.properties.fields.push(currentField);
}
} else if (state === 'fields' && /\./.test(line)) {
const compParts = line.match(/(\w+)\. Value index=(\d+), #Derivatives=(\d+)/);
if (compParts && currentField) {
currentField.components.push({
name: compParts[1],
value_index: parseInt(compParts[2]),
num_derivatives: parseInt(compParts[3])
});
}
} else if (line.startsWith('Node:')) {
const nodeId = parseInt(line.split(':')[1].trim());
this.properties.nodes.push({ id: nodeId, values: [] });
state = 'node_values';
} else if (state === 'node_values') {
const values = line.split(/\s+/).map(parseFloat);
this.properties.nodes[this.properties.nodes.length - 1].values.push(values);
} else if (line.startsWith('Element:')) {
const elemId = line.split(/\s+/).slice(1).map(parseInt);
this.properties.elements.push({ id: elemId });
state = 'element';
}
});
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 4));
}
write(outputPath) {
let output = '';
this.properties.regions.forEach(region => {
output += `Region: ${region}\n`;
});
this.properties.groups.forEach(group => {
output += `Group name: ${group}\n`;
});
this.properties.shapes.forEach(shape => {
output += `Shape. Dimension=${shape}\n`;
});
this.properties.fields.forEach(field => {
if (field.num_fields !== undefined) {
output += `#Fields=${field.num_fields}\n`;
} else {
output += `1) ${field.name}, ${field.type}, ${field.coord_system}, #Components=${field.num_components}\n`;
field.components.forEach(comp => {
output += ` ${comp.name}. Value index=${comp.value_index}, #Derivatives=${comp.num_derivatives}\n`;
});
}
});
this.properties.nodes.forEach(node => {
output += `Node: ${node.id}\n`;
node.values.forEach(vals => {
output += ` ${vals.join(' ')}\n`;
});
});
this.properties.elements.forEach(elem => {
output += `Element: ${elem.id.join(' ')}\n`;
});
fs.writeFileSync(outputPath, output);
}
}
// Example usage:
// const handler = new EXFileHandler('example.exnode');
// handler.readAndDecode();
// handler.printProperties();
// handler.write('output.ex');
C Class for .EX File Handling
The following C code defines a struct-based "class" (using functions) to open, decode (parse), read, write, and print all properties from a .EX file to the console. It uses standard I/O and string parsing; note that memory management is basic for illustration.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINE 1024
#define MAX_PROPERTIES 100
typedef struct {
char *regions[MAX_PROPERTIES];
int region_count;
char *groups[MAX_PROPERTIES];
int group_count;
char *shapes[MAX_PROPERTIES];
int shape_count;
// For fields, nodes, elements: use simplified arrays for demo; in production, use linked lists
char *fields[MAX_PROPERTIES];
int field_count;
char *nodes[MAX_PROPERTIES];
int node_count;
char *elements[MAX_PROPERTIES];
int element_count;
} EXProperties;
typedef struct {
char *filepath;
EXProperties properties;
} EXFileHandler;
void initEXFileHandler(EXFileHandler *handler, char *filepath) {
handler->filepath = strdup(filepath);
memset(&handler->properties, 0, sizeof(EXProperties));
}
void freeEXFileHandler(EXFileHandler *handler) {
for (int i = 0; i < handler->properties.region_count; i++) free(handler->properties.regions[i]);
for (int i = 0; i < handler->properties.group_count; i++) free(handler->properties.groups[i]);
for (int i = 0; i < handler->properties.shape_count; i++) free(handler->properties.shapes[i]);
for (int i = 0; i < handler->properties.field_count; i++) free(handler->properties.fields[i]);
for (int i = 0; i < handler->properties.node_count; i++) free(handler->properties.nodes[i]);
for (int i = 0; i < handler->properties.element_count; i++) free(handler->properties.elements[i]);
free(handler->filepath);
}
void readAndDecode(EXFileHandler *handler) {
FILE *file = fopen(handler->filepath, "r");
if (!file) return;
char line[MAX_LINE];
char *current_region = NULL;
char *current_group = NULL;
char state[20] = "idle";
while (fgets(line, MAX_LINE, file)) {
char *trimmed = line;
while (isspace(*trimmed)) trimmed++;
if (*trimmed == '\0' || *trimmed == '!') continue;
char *end = trimmed + strlen(trimmed) - 1;
while (end > trimmed && isspace(*end)) *end-- = '\0';
if (strstr(trimmed, "Region:") == trimmed) {
current_region = strdup(trimmed + 7);
handler->properties.regions[handler->properties.region_count++] = current_region;
strcpy(state, "region");
} else if (strstr(trimmed, "Group name:") == trimmed) {
current_group = strdup(trimmed + 11);
handler->properties.groups[handler->properties.group_count++] = current_group;
strcpy(state, "group");
} else if (strstr(trimmed, "Shape.") == trimmed) {
char *dim_start = strstr(trimmed, "Dimension=");
if (dim_start) {
char *shape = strdup(dim_start + 10);
handler->properties.shapes[handler->properties.shape_count++] = shape;
}
strcpy(state, "shape");
} else if (strstr(trimmed, "#Fields=") == trimmed) {
char *field = strdup(trimmed);
handler->properties.fields[handler->properties.field_count++] = field;
strcpy(state, "fields");
} else if (strcmp(state, "fields") == 0 && isdigit(*trimmed)) {
char *field = strdup(trimmed);
handler->properties.fields[handler->properties.field_count++] = field;
} else if (strstr(trimmed, "Node:") == trimmed) {
char *node = strdup(trimmed);
handler->properties.nodes[handler->properties.node_count++] = node;
strcpy(state, "node_values");
} else if (strcmp(state, "node_values") == 0) {
char *value = strdup(trimmed);
handler->properties.nodes[handler->properties.node_count++] = value;
} else if (strstr(trimmed, "Element:") == trimmed) {
char *elem = strdup(trimmed);
handler->properties.elements[handler->properties.element_count++] = elem;
strcpy(state, "element");
}
}
fclose(file);
}
void printProperties(EXFileHandler *handler) {
printf("{\n");
printf(" \"regions\": [");
for (int i = 0; i < handler->properties.region_count; i++) {
printf("\"%s\"%s", handler->properties.regions[i], i < handler->properties.region_count - 1 ? ", " : "");
}
printf("],\n");
// Similar for groups, shapes, fields, nodes, elements
printf(" \"groups\": [");
for (int i = 0; i < handler->properties.group_count; i++) {
printf("\"%s\"%s", handler->properties.groups[i], i < handler->properties.group_count - 1 ? ", " : "");
}
printf("],\n");
printf(" \"shapes\": [");
for (int i = 0; i < handler->properties.shape_count; i++) {
printf("\"%s\"%s", handler->properties.shapes[i], i < handler->properties.shape_count - 1 ? ", " : "");
}
printf("],\n");
printf(" \"fields\": [");
for (int i = 0; i < handler->properties.field_count; i++) {
printf("\"%s\"%s", handler->properties.fields[i], i < handler->properties.field_count - 1 ? ", " : "");
}
printf("],\n");
printf(" \"nodes\": [");
for (int i = 0; i < handler->properties.node_count; i++) {
printf("\"%s\"%s", handler->properties.nodes[i], i < handler->properties.node_count - 1 ? ", " : "");
}
printf("],\n");
printf(" \"elements\": [");
for (int i = 0; i < handler->properties.element_count; i++) {
printf("\"%s\"%s", handler->properties.elements[i], i < handler->properties.element_count - 1 ? ", " : "");
}
printf("]\n");
printf("}\n");
}
void write(EXFileHandler *handler, char *output_path) {
FILE *file = fopen(output_path, "w");
if (!file) return;
for (int i = 0; i < handler->properties.region_count; i++) {
fprintf(file, "Region: %s\n", handler->properties.regions[i]);
}
for (int i = 0; i < handler->properties.group_count; i++) {
fprintf(file, "Group name: %s\n", handler->properties.groups[i]);
}
for (int i = 0; i < handler->properties.shape_count; i++) {
fprintf(file, "Shape. Dimension=%s\n", handler->properties.shapes[i]);
}
for (int i = 0; i < handler->properties.field_count; i++) {
fprintf(file, "%s\n", handler->properties.fields[i]);
}
for (int i = 0; i < handler->properties.node_count; i++) {
fprintf(file, "%s\n", handler->properties.nodes[i]);
}
for (int i = 0; i < handler->properties.element_count; i++) {
fprintf(file, "%s\n", handler->properties.elements[i]);
}
fclose(file);
}
// Example usage:
// int main() {
// EXFileHandler handler;
// initEXFileHandler(&handler, "example.exnode");
// readAndDecode(&handler);
// printProperties(&handler);
// write(&handler, "output.ex");
// freeEXFileHandler(&handler);
// return 0;
// }