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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat: Add Oxford h5oina support by rhysgt · Pull Request #156 · MechMicroMan/DefDAP · GitHub
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
168 changes: 156 additions & 12 deletions defdap/file_readers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,15 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd
from abc import ABC, abstractmethod
import pathlib
import re

from typing import TextIO, Dict, List, Callable, Any, Type, Optional

import h5py
import numpy as np
from numpy.lib.recfunctions import structured_to_unstructured
import pandas as pd

from defdap.crystal import Phase
from defdap.quat import Quat
from defdap.utils import Datastore
Expand DownExpand Up@@ -56,11 +57,14 @@ def __init__(self) -> None:
self.data_format = None

@staticmethod
def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]':
def get_loader(
data_type: str, file_name: pathlib.Path
) -> 'Type[EBSDDataLoader]':
if data_type is None:
data_type = {
'.crc': 'oxfordbinary',
'.cpr': 'oxfordbinary',
'.h5oina': 'oxfordh5',
'.ctf': 'oxfordtext',
'.ang': 'edaxang',
}.get(file_name.suffix, 'oxfordbinary')
Expand All@@ -70,6 +74,7 @@ def get_loader(data_type: str, file_name: pathlib.Path) -> 'Type[EBSDDataLoader]
loader = {
'oxfordbinary': OxfordBinaryLoader,
'oxfordtext': OxfordTextLoader,
'oxfordh5': Oxfordh5Loader,
'edaxang': EdaxAngLoader,
'pythondict': PythonDictLoader,
}[data_type]
Expand DownExpand Up@@ -234,6 +239,134 @@ def parse_phase() -> Phase:
self.check_data()


class Oxfordh5Loader(EBSDDataLoader):
def load(self, file_name: pathlib.Path, dataset = None) -> None:
"""Read an Oxford Instruments ``.h5oina`` orientation file.

Parameters
----------
file_name : pathlib.Path
Path to file.
dataset : str (raw or processed), optional
Dataset to load. If None, defaults to raw data.

"""
# Open data file and read in metadata
if not file_name.is_file():
raise FileNotFoundError(f"Cannot open file {file_name}")

file = h5py.File(file_name)

# This header contains all the information in the map that does not
# change with processing
raw_header = file['1']['EBSD']['Header']
shape = (int(raw_header['Y Cells'][0]), int(raw_header['X Cells'][0]))
self.loaded_metadata['shape'] = shape
self.loaded_metadata['step_size'] = float(raw_header['X Step'][0])
self.loaded_metadata['acquisition_rotation'] = Quat.from_euler_angles(
*raw_header['Specimen Orientation Euler'][0]
)

# Check if `Data Processing` dataset exists in the h5
if 'Data' in file['1']['Data Processing'] and dataset is None:
print('\n\tMultiple datasets in h5 file, defaulting to raw data.')
print(
'\tProcessed data can be accessed by passing `processed` to '
'the `dataset` argument.'
)

# Handle `raw` or `processed` selection
if dataset is None or dataset == 'raw':
root = file['1']['EBSD']
if dataset == 'processed':
if 'Data Processing' not in file['1']:
raise ValueError('No processed data in h5 file.')
if 'Data' not in file['1']['Data Processing']:
raise ValueError('No processed data in h5 file.')
root = file['1']['Data Processing']

# Phase data from relevant dataset
for phase_data in root['Header']['Phases'].values():
phase = Phase(
phase_data['Phase Name'][0].decode(),
phase_data['Laue Group'][0],
phase_data['Space Group'][0],
np.concatenate([
phase_data['Lattice Dimensions'][0],
phase_data['Lattice Angles'][0]
]))
self.loaded_metadata['phases'].append(phase)

self.check_metadata()

# Some data is only available and relevant for the raw data, for
# example band contrast
if dataset == 'raw':
raw_data = root['Data']
self.loaded_data.add(
'band_contrast',
np.array(raw_data['Band Contrast']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band contrast',
}
)
self.loaded_data.add(
'band_slope',
np.array(raw_data['Band Slope']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'cmap': 'gray',
'clabel': 'Band slope',
}
)
self.loaded_data.add(
'mean_angular_deviation',
np.array(raw_data['Mean Angular Deviation']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Mean angular deviation',
}
)
self.loaded_data.add(
'pattern_quality',
np.array(raw_data['Pattern Quality']).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Pattern quality',
}
)

# If pattern matching is performed, the cross correlation coefficient
# is useful
if dataset == 'processed' and 'Pattern Matching' in root:
pattern_data = root['Pattern Matching']['Data']
self.loaded_data.add(
'pattern_quality',
np.array(
pattern_data['Cross Correlation Coefficient']
).reshape(shape),
unit='', type='map', order=0,
plot_params={
'plot_colour_bar': True,
'clabel': 'Cross Correlation Coefficient',
}
)

# Get Euler angles from relevant dataset
self.loaded_data.phase = np.array(root['Data']['Phase']).reshape(shape)
self.loaded_data.euler_angle = (
root['Data']['Euler'][:].reshape(shape + (3,)).transpose((2, 0, 1))
)

self.check_data()


class EdaxAngLoader(EBSDDataLoader):
def load(self, file_name: pathlib.Path) -> None:
""" Read an EDAX .ang file.
Expand DownExpand Up@@ -327,7 +460,9 @@ def load(self, file_name: pathlib.Path) -> None:
)
add_phase = 1 if data['phase'].min() == 0 else 0
self.loaded_data.phase = data['phase'].reshape(shape) + add_phase
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)

# flatten the structured dtype
euler_angle = structured_to_unstructured(
Expand DownExpand Up@@ -424,8 +559,10 @@ def parse_line(line: str, group_dict: Dict) -> None:

group_name = group_pat.match(line.strip()).group(1)
group_dict = dict()
read_until_string(cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict))
read_until_string(
cpr_file, '[', comment_char=comment_char,
line_process=lambda l: parse_line(l, group_dict)
)
metadata[group_name] = group_dict

# Create phase objects and move metadata to object metadata dict
Expand DownExpand Up@@ -549,7 +686,8 @@ def load_oxford_crc(self, file_name: pathlib.Path) -> None:
data[['ph1', 'phi', 'ph2']].reshape(shape)).transpose((2, 0, 1))

if self.loaded_metadata['edx']['Count'] > 0:
EDXFields = [key for key in data.dtype.fields.keys() if key.startswith('EDX')]
EDXFields = [key for key in data.dtype.fields.keys()
if key.startswith('EDX')]
for field in EDXFields:
self.loaded_data.add(
field,
Expand DownExpand Up@@ -590,7 +728,9 @@ def load(self, data_dict: Dict[str, Any]) -> None:
unit='', type='map', order=0
)
self.loaded_data.phase = data_dict['phase']
self.loaded_data['phase', 'plot_params']['vmax'] = len(self.loaded_metadata['phases'])
self.loaded_data['phase', 'plot_params']['vmax'] = len(
self.loaded_metadata['phases']
)
self.loaded_data.euler_angle = data_dict['euler_angle']
self.check_data()

Expand DownExpand Up@@ -819,8 +959,12 @@ def load(self, file_name: pathlib.Path) -> None:

# if y descending, flip
if np.all(np.diff(data['y'][:,0])) > 0:
self.loaded_data.coordinate = np.array([data['x'][::-1], data['y'][::-1]])
self.loaded_data.displacement = np.array([data['u'][::-1], data['v'][::-1]])
self.loaded_data.coordinate = np.array(
[data['x'][::-1], data['y'][::-1]]
)
self.loaded_data.displacement = np.array(
[data['u'][::-1], data['v'][::-1]]
)
else:
self.loaded_data.coordinate = np.array([data['x'], data['y']])
self.loaded_data.displacement = np.array([data['u'], data['v']])
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ dependencies = [
"matplotlib_scalebar",
"networkx",
"numba",
"h5py"
]

[project.urls]
Expand Down
Loading