diff --git a/ncempy/data/au_145mm_68kx_microprobe_01.h5 b/ncempy/data/au_145mm_68kx_microprobe_01.h5 new file mode 100755 index 0000000..53d8730 Binary files /dev/null and b/ncempy/data/au_145mm_68kx_microprobe_01.h5 differ diff --git a/ncempy/data/au_145mm_68kx_microprobe_01_data_000001.h5 b/ncempy/data/au_145mm_68kx_microprobe_01_data_000001.h5 new file mode 100755 index 0000000..b731bde Binary files /dev/null and b/ncempy/data/au_145mm_68kx_microprobe_01_data_000001.h5 differ diff --git a/ncempy/data/au_145mm_68kx_microprobe_01_master.h5 b/ncempy/data/au_145mm_68kx_microprobe_01_master.h5 new file mode 100755 index 0000000..cd3c4e1 Binary files /dev/null and b/ncempy/data/au_145mm_68kx_microprobe_01_master.h5 differ diff --git a/ncempy/io/__init__.py b/ncempy/io/__init__.py index fbc954a..8bfc32d 100644 --- a/ncempy/io/__init__.py +++ b/ncempy/io/__init__.py @@ -1,12 +1,12 @@ +from pathlib import Path + from . import dm from . import ser from . import emd from . import mrc from . import emdVelox from . import smv - -from pathlib import Path - +from . import dectris def read(filename, dsetNum=0): """ @@ -40,11 +40,27 @@ def read(filename, dsetNum=0): out = ser.serReader(filename) elif suffix in ('.dm3', '.dm4'): out = dm.dmReader(filename) - elif suffix in ('.emd', '.h5', '.hdf5'): + elif suffix in ('.h5', '.hdf5'): + try: + # Try Berkeley EMD + out = emd.emdReader(filename, dsetNum) + except emd.NoEmdDataSets: + # Try Dectris Arina + out = dectris.dectrisReader(filename) + except: + print('ncempy.read: Unknown H5 file.') + raise + elif suffix == '.emd': try: + # Try Berkeley EMD out = emd.emdReader(filename, dsetNum) except emd.NoEmdDataSets: - out = emdVelox.emdVeloxReader(filename, dsetNum) + # Try Velox EMD + try: + out = emdVelox.emdVeloxReader(filename, dsetNum) + except KeyError: + print('ncempy.read: Unknown EMD file.') + raise elif suffix in ('.mrc', '.rec', '.st', '.ali'): out = mrc.mrcReader(filename) elif suffix in ('.smv', '.img'): diff --git a/ncempy/io/dectris.py b/ncempy/io/dectris.py new file mode 100644 index 0000000..6ec6b0c --- /dev/null +++ b/ncempy/io/dectris.py @@ -0,0 +1,198 @@ +""" +This module provides an interface to Dectris Arina data sets +""" + +from pathlib import Path +import h5py +import numpy as np +import hdf5plugin + +class fileDECTRIS: + """ Class to represent Dectris Arina data sets + + Attributes + ---------- + raw_shape : list + The shape of the raw data. This is three-dimensional: [num_frames, frameY, frameX]. + data_shape : list + The four-dimensional shape of the dataset. By default, the + scanned region is square. + file_hdl : h5py.File + The h5py file handle which provides direct access to the underlying hdf5 file structure. + data_type : numpy.dtype + The data type of the values in the data set. + """ + def __init__(self, filename, bad_pixels=None, verbose=False): + """ Initialize a data set by opening the master file and determining the file size + + Parameters + ---------- + filename : str or pathlib.Path or file object + The HDF5 master file to open. + verbose : bool, default False + If True, prints out debugging information + """ + + self._verbose = verbose + self.raw_shape = [0, 0, 0] # shape of data on disk + self.data_shape = [0, 0, 0, 0] # the shape of the final 4D dataset + self.file_hdl = None + self.data_dtype = None + self.bad_pixel_value = bad_pixels + + # Pixels to remove automatically + # self.bad_pixels = ((49, 75), (93,118), (95,119), (108, 57)) # NCEM bad pixels + + if hasattr(filename, 'read'): + try: + self.file_path = Path(filename.name) + self.file_name = self.file_path.name + except AttributeError: + self.file_path = None + self.file_name = None + else: + # check filename type, change to pathlib.Path + if isinstance(filename, str): + filename = Path(filename) + elif isinstance(filename, Path): + pass + else: + raise TypeError('Filename is supposed to be a string or pathlib.Path or file object') + self.file_path = Path(filename) + self.file_name = self.file_path.name + + # Try opening the file + try: + self.file_hdl = h5py.File(filename, 'r') + assert self.file_hdl['/entry/data'] + except: + print('Error opening file: "{}"'.format(filename)) + raise + + # if this is a HDF5 file + if self.file_hdl: + # Find the initial shape of the data set + for v in self.file_hdl['/entry/data'].values(): + self.raw_shape[0] = self.raw_shape[0] + v.shape[0] + self.raw_shape[1] = v.shape[1] + self.raw_shape[2] = v.shape[2] + self.data_dtype = v.dtype + + def __del__(self): + """ Destructor for EMD file object. + + """ + # close the file + # if(not self.file_hdl.closed): + self.file_hdl.close() + + def __enter__(self): + """Implement python's with statement for context managers. + + """ + return self + + def __exit__(self, exception_type, exception_value, traceback): + """Implement python's with statement fr context managers. + and close the file via __del__() + """ + self.__del__() + return None + + def getDataset(self, remove_bad_pixels=False, assume_shape=None): + """ Read the data from the HDF5 files + + Parameters + ---------- + remove_bad_pixels : bool, default False + If True, _remove_bad_pixels function is called after the data is loaded. + assume_shape : tuple, optional + If this is set, then this tuple is used as the scanning shape overriding + the assumption of a square real space scanning grid + """ + # Pre allocate space + data = np.zeros(self.raw_shape, dtype=self.data_dtype) + # Read in the data in all linked files + ii = 0 + for v in self.file_hdl['/entry/data'].values(): + data[ii:ii+v.shape[0]] = v[:] + ii += v.shape[0] + + if assume_shape: + self.data_shape = (assume_shape[0], assume_shape[1], + data.shape[1], data.shape[2]) + else: + # Reshape assuming square + shape_square = int((data.shape[0])**0.5) + assert data.shape[0] == shape_square**2 + self.data_shape = (shape_square, shape_square, + data.shape[1], data.shape[2]) + data = data.reshape(self.data_shape) + if remove_bad_pixels: + self._remove_bad_pixels() + + data_out = {} + data_out['data'] = data + return data_out + + def getMetadata(self): + """ The dectris Arina files sometimes output an extra file with + metadata in it. This checks for that file and reads the meta data + if if exists. The units are assumed to be nanometers. + + Returns + ------- + : dict + Meta data as a dictionary + + """ + + filename_parts = self.file_path.stem.split('_') + metadata_file_path = self.file_path.parent / Path('_'.join(filename_parts[0:-1])).with_suffix('.h5') + if metadata_file_path.exists(): + try: + metadata = {} + with h5py.File(metadata_file_path, 'r') as f0: + for k,v in f0["STEM Metadata"].attrs.items(): + metadata[k] = v + + pixel_size0 = metadata["Pixel Size"] # convert to ncempy standard + metadata['pixelSize'] = (pixel_size0, pixel_size0) + metadata['pixelUnit'] = ('n_m', 'n_m') + + return metadata + except: + raise + + def remove_bad_pixels(self, data, value=0, bad_pixels=None): + """ Some pixels are known to be very high or very low. This function will replace the + pixel values. + + Parameters + ---------- + data : numpy.ndarray + The 4D-STEM data set + value : int or float + The value to replace the bad pixels by. + bad_pixels : numpy.ndarray + A m by 2 ndarray where m is the number of bad pixels and the locations + are specified in order for frame axis 2 and 3. + + """ + if bad_pixels: + self.bad_pixels = bad_pixels + for bad in self.bad_pixels: + data[:, :, bad[0], bad[1]] = value + +def dectrisReader(file_name): + if isinstance(file_name, str): + file_name = Path(file_name) + + with fileDECTRIS(file_name) as f1: # open the file and init the class + im1 = f1.getDataset() # read in the dataset + md = f1.getMetadata() + if md: + extra_metadata = {'pixelSize': md['pixelSize'], 'pixelUnit':md['pixelUnit'], 'filename': f1.file_name} + im1.update(extra_metadata) + return im1 + \ No newline at end of file diff --git a/ncempy/io/dm.py b/ncempy/io/dm.py index ff4eb18..17694d4 100644 --- a/ncempy/io/dm.py +++ b/ncempy/io/dm.py @@ -386,7 +386,7 @@ def parseHeader(self): else: # this file only contains tags (such as a GTG file) self.thumbnail = False - def getMetadata(self, index): + def getMetadata(self, index, metadata_keys=None): """ Get the useful metadata in the file. This parses the allTags dictionary and retrieves only the useful information about hte experimental parameters. This is a (useful) subset of the information contains in the allTags attribute. @@ -394,11 +394,21 @@ def getMetadata(self, index): Note: some DM files contain extra information called the Tecnai Microscope Info. This is added to the metadata dictionary as a string. + The "good" keys include: + ['Calibrations', 'Acquisition', 'DataBar', 'EELS', 'Meta Data', 'Microscope Info', '4Dcamera Parameters', 'Session Info'] + To extract other metadata not found in the above list use the metadata_keys input to this function. Parameters ---------- index : int The number of the dataset to get the metadata from. + metadata_keys : list or tuple + Extra keys in a list or tuple to extract from the DM tags as metadata. + + Returns + ------- + : dict + A subset of the DM tags returned as a dictionary with useful meatdata about the experiment. """ # The first dataset is usually a thumbnail. Test for this and skip the thumbnail automatically # metadata indexing starts at 1 but the index keyword starts at 0 @@ -416,6 +426,11 @@ def getMetadata(self, index): # Most of the useful keys. Two other keys Tecnai.Microscope Info is treated specially below good_keys = ['Calibrations', 'Acquisition', 'DataBar', 'EELS', 'Meta Data', 'Microscope Info', '4Dcamera Parameters', 'Session Info'] + # Add extra keys in case the user wants to extract other metadata + if metadata_keys: + assert isinstance(metadata_keys, (list, tuple)) + good_keys.extend(metadata_keys) + # Determine useful meta data prefix1 = '.ImageList.{}.ImageTags.'.format(index) prefix2 = '.ImageList.{}.ImageData.'.format(index) @@ -891,7 +906,6 @@ def writeTags(self, new_folder_path_for_tags=None): # Change output path if new_folder_path_for_tags: - print('choosing different path') out_directory = Path(new_folder_path_for_tags) else: out_directory = self.file_path.parent diff --git a/ncempy/io/emd.py b/ncempy/io/emd.py index 9600ec5..cbae541 100644 --- a/ncempy/io/emd.py +++ b/ncempy/io/emd.py @@ -511,7 +511,7 @@ def put_comment(self, msg, timestamp=None): # create timestamp if missing if not timestamp: - timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S (UTC)') + timestamp = datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S (UTC)') else: # try to convert given timestamp to string try: @@ -527,7 +527,38 @@ def put_comment(self, msg, timestamp=None): else: # create new entry self.comments.attrs[timestamp] = msg + + def getMetadata(self, group): + """Get the useful metdata (experimental information) available in the file. These + are the attrs of the user, microscope, and sample groups. The metdata is returned + as a dictionary. + Parameters + ---------- + group: h5py._hl.group.Group or int + Reference to the HDF5 group to load. If int is used then the item corresponding to self.list_emds + is loaded + + Returns + ------- + : dict + A dictionary of meta data keys and values. + """ + meta_data = {} + try: + meta_data.update(self.user.attrs) + except AttributeError: + pass + try: + meta_data.update(self.microscope.attrs) + except AttributeError: + pass + try: + meta_data.update(self.sample.attrs) + except AttributeError: + pass + + return meta_data def defaultDims(data, pixel_size=None, pixel_unit=None): """ A helper function that can generate a properly setup dim tuple diff --git a/ncempy/io/emdVelox.py b/ncempy/io/emdVelox.py index 22a00fd..169ec83 100644 --- a/ncempy/io/emdVelox.py +++ b/ncempy/io/emdVelox.py @@ -134,24 +134,32 @@ def __str__(self): return out def _find_groups(self): - """ Find all groups that contain image data. + """ Find all groups that contain data: spectrum data, image data, and spectrum image. Note ---- - This currently only finds images. + Previously, this only found images. """ try: - # Get all of the groups in the Image group - self.list_data = list(self._file_hdl['Data/Image'].values()) + # Get all of the groups in the Spectrum, Image, and SpectrumImage groups + # TODO: Is there an order in which Spectrums and Images should be included? (e.g. by detector name or element name?) + self.list_data = list(self._file_hdl['Data/Spectrum'].values()) + list(self._file_hdl['Data/Image'].values()) + list(self._file_hdl['Data/SpectrumImage'].values()) except: self.list_data = [] raise self.list_emds = self.list_data # make a copy to match the Berkeley EMD attribute - + def get_dataset(self, group, memmap=False): """ Get the data from a group and the associated metadata. + This is a convenience function and calls getDataset + """ + return self.getDataset(group, memmap=memmap) + + def getDataset(self, group, memmap=False): + """ Get the data from a group and the associated metadata. + Parameters ---------- group : HDF5 dataset or int @@ -187,22 +195,33 @@ def get_dataset(self, group, memmap=False): return data, metaData def parseMetaData(self, group): - """ Parse metadata in a data group. Determines the pixelSize and - detector name. The EMDVelox data sets have extensive metadata - stored as a JSON type string. + """ Convenience function that calls _parseMetadata. + This function should not be directly used. Please use + getMetadata instead.""" + return self._parseMetadata(group) + + def _parseMetadata(self, group): + """ Parse metadata in a data group. The EMDVelox data sets have + extensive metadata stored as a JSON type string. This function + converts it to a dictionary. All converted metadata is stored in + the metadataJSON parameter. + + For historical reasons this also returns a dicitonary with some + useful metadata. + + For better metadata output please use the getMetadata function. Parameters ---------- - group : h5py.Group or int - The h5py group to load the metadata from which is easily retrived from the list_data attribute. - If input is an int then the - group corresponding to list_data attribute is used. The string metadata is loaded - and parsed by the json module into a dictionary. + group : h5py.Group or int + The h5py group to load the metadata from which is easily retrived from the list_data attribute. + If input is an int then the group corresponding to list_data attribute is used. The string + metadata is loaded and parsed by the json module into a dictionary. Returns ------- - md : dict - The JSON information in the file returned as a python dictionary. + : dict + The JSON information in the file returned as a python dictionary. """ try: @@ -244,8 +263,40 @@ def parseMetaData(self, group): md['dwellTime'] = 0 return md + + def getMetadata(self, group): + """ Reads important metadata from Velox EMD files. - + Parameters + ---------- + group : h5py.Group or int + The h5py group to load the metadata from which is easily retrived from the list_data attribute. + If input is an int then the group corresponding to list_data attribute is used. The string + metadata is loaded and parsed by the json module into a dictionary. + """ + self._parseMetadata(group) + meta_data = {} + + keys_to_ignore = ('EnergyFilter', 'Vacuum', 'GasInjectionSystems', 'SharedProperties') + # previously: useful_keys = ('Optics', 'Stage', 'Scan', 'BinaryResult') + for kk in self.metaDataJSON.keys(): + if kk not in keys_to_ignore and kk != 'CustomProperties': + meta_data.update(self.metaDataJSON[kk]) + + # handle CustomProperties separately + for kk, vv in self.metaDataJSON['CustomProperties'].items(): + try: + if isinstance(vv, dict): + if vv['type'] == 'string': + meta_data[kk] = str(vv['value']) + elif vv['type'] == 'double': + meta_data[kk] = float(vv['value']) + else: + meta_data[kk] = vv['value'] + except: + pass + return meta_data + def emdVeloxReader(filename, dsetNum=0): """ A simple helper function to read in the data and metadata in a structured format similar to the other ncempy readers. diff --git a/ncempy/io/mrc.py b/ncempy/io/mrc.py index 20c0cfc..15d1778 100644 --- a/ncempy/io/mrc.py +++ b/ncempy/io/mrc.py @@ -388,6 +388,20 @@ def getMemmap(self): return mm + def getMetadata(self): + meta_data = {} + + # Save most useful metaData + meta_data.update({'pixelSize': self.voxelSize, 'voxelSize': self.voxelSize, + 'cellAngles': self.cellAngles, 'axisOrientations': self.axisOrientations}) + if hasattr(self, 'FEIinfo'): + # add in the special FEIinfo if it exists + try: + meta_data.update(self.FEIinfo) + except TypeError: + pass + return meta_data + def _applyAxisOrientations(self, arrayIn): """ This is untested and unused. diff --git a/ncempy/io/ser.py b/ncempy/io/ser.py index d854376..73c553c 100644 --- a/ncempy/io/ser.py +++ b/ncempy/io/ser.py @@ -471,6 +471,28 @@ def getDataset(self, index, verbose=False): return dataset, meta + def getMetadata(self): + """Retrieve meta data on experimental parmaeters and settings from + the file. This is global metdata for the entire set of images in + the SER file. Metadata such as the pixel size needs to be retrived + for each image separately using getDataset. + + """ + meta_data = {} + + # Add extra meta data from the EMI file if it exists + if self._emi is not None: + meta_data.update(self._emi) + + meta_data.update(self.head) # some header data for the ser file + + # Clean the dictionary + for k, v in meta_data.items(): + if isinstance(v, bytes): + meta_data[k] = v.decode('UTF8') + + return meta_data + def _getTag(self, index, verbose=False): """Retrieve tag from data file. @@ -950,36 +972,11 @@ def read_emi(filename): # dict to store _emi stuff _emi = {} - # need anything readable from to - # collect = False - # data = b'' - # for line in f_emi: - # if b'' in line: - # collect = True - # if collect: - # data += line.strip() - # if b'' in line: - # collect = False - - # close the file - # f_emi.close() - metaStart = emi_data.find(b'') metaEnd = emi_data.find(b'') # need to add len('') = 13 to encompass this final tag root = ET.fromstring(emi_data[metaStart:metaEnd + 13]) - # strip of binary stuff still around - # data = data.decode('ascii', errors='ignore') - # matchObj = re.search('(.+?)' + data + '') - # single items _emi['Uuid'] = root.findtext('Uuid') _emi['AcquireDate'] = root.findtext('AcquireDate') diff --git a/ncempy/io/smv.py b/ncempy/io/smv.py index a8d2958..b6434a4 100644 --- a/ncempy/io/smv.py +++ b/ncempy/io/smv.py @@ -177,6 +177,14 @@ def parseHeader(self): raise(f'File data type not supported: {val}') def getDataset(self): + """Read the data from the file + + Returns + ------- + : dict + A dictionary containng the data in a dictionary with the key 'data' + + """ self.readHeader() self.parseHeader() @@ -186,6 +194,20 @@ def getDataset(self): data_out = {} data_out['data'] = data return data_out + + def getMetadata(self): + """Reads the metadata from the file + + Returns + ------- + : dict + A dicitons contained useful experimental metadata. + """ + self.readHeader() + meta_data = {} + meta_data.update(self.header_info) + meta_data.update(self.custom_info) + return meta_data def smvWriter(out_path, dp, camera_length=110, lamda=0.0197, pixel_size=0.01, beam_center=None, binned_by=1, newline=None, custom_header=None): diff --git a/ncempy/test/test_io.py b/ncempy/test/test_io.py index 863ee49..c56fbc0 100644 --- a/ncempy/test/test_io.py +++ b/ncempy/test/test_io.py @@ -102,6 +102,7 @@ def test_read(data_location): """Test the general reader function""" all_files = Path(data_location).glob('*.*') for file in all_files: - file_dict = nio.read(file) - if file_dict: - assert 'data' in file_dict + if file.stem not in ('au_145mm_68kx_microprobe_01_data_000001', 'au_145mm_68kx_microprobe_01'): + file_dict = nio.read(file) + if file_dict: + assert 'data' in file_dict diff --git a/ncempy/test/test_io_dectris.py b/ncempy/test/test_io_dectris.py new file mode 100644 index 0000000..eb5311a --- /dev/null +++ b/ncempy/test/test_io_dectris.py @@ -0,0 +1,49 @@ +""" +Tests for the basic functionality of the dectris io module. +""" + +import pytest + +import time +from pathlib import Path +import tempfile +import numpy as np + +import ncempy.io.dectris + + +class Testdectris: + """ + Test the dectris io module + """ + + @pytest.fixture + def data_location(self): + # Get the location of the test data files + test_path = Path(__file__).resolve() + root_path = test_path.parents[1] + return root_path / Path('data') + + def test_read_data(self, data_location): + file_path = data_location / Path('au_145mm_68kx_microprobe_01_master.h5') + with ncempy.io.dectris.fileDECTRIS(file_path) as f0: + dd = f0.getDataset() + assert 'data' in dd + + def test_read_metadata(self, data_location): + file_path = data_location / Path('au_145mm_68kx_microprobe_01_master.h5') + with ncempy.io.dectris.fileDECTRIS(file_path) as f0: + md = f0.getMetadata() + if md: + assert 'pixelSize' in md + + def test_str_input(self, data_location): + file_path = data_location / Path('au_145mm_68kx_microprobe_01_master.h5') + with ncempy.io.dectris.fileDECTRIS(str(file_path)) as f0: + assert f0.raw_shape[1] == 192 + + def test_dectrisReader(self, data_location): + import ncempy + out = ncempy.read(data_location / Path('au_145mm_68kx_microprobe_01_master.h5')) + assert 'pixelSize' in out + assert 'data' in out \ No newline at end of file diff --git a/ncempy/test/test_io_dm.py b/ncempy/test/test_io_dm.py index 8a5237d..5188fca 100644 --- a/ncempy/test/test_io_dm.py +++ b/ncempy/test/test_io_dm.py @@ -182,9 +182,13 @@ def test_file_object(self, data_location): def test_metadata(self, data_location): file_name = data_location / Path('08_carbon.dm3') - #file_name = '/mnt/nvme1/percius/microED/2023.05.15/scan666.dm4' with ncempy.io.dm.fileDM(file_name) as dm0: - print(file_name) _ = dm0.getMetadata(0) - print(_['Calibrations Brightness Scale']) + assert _['Acquisition Device Name'] == 'EF-CCD' + + def test_custom_metadata(self, data_location): + file_name = data_location / Path('08_carbon.dm3') + with ncempy.io.dm.fileDM(file_name) as dm0: + _ = dm0.getMetadata(0, metadata_keys=['Dimensions',]) + assert _['Dimensions 1'] == 2048 diff --git a/ncempy/test/test_io_emd.py b/ncempy/test/test_io_emd.py index 4f87734..46f6b56 100644 --- a/ncempy/test/test_io_emd.py +++ b/ncempy/test/test_io_emd.py @@ -119,8 +119,8 @@ def test_file_object(self, data_location): # Test fileEMD class input with file object file_name = data_location / Path('Acquisition_18.emd') fid = open(file_name, 'rb') - emd0 = ncempy.io.emd.fileEMD(fid) - assert hasattr(emd0, 'file_hdl') + with ncempy.io.emd.fileEMD(fid) as emd0: + assert hasattr(emd0, 'file_hdl') def test_memmap(self, data_location): emd1 = ncempy.io.emd.fileEMD(data_location / Path('Acquisition_18.emd')) @@ -190,3 +190,12 @@ def test_no_emds(self, temp_file, data_location): ncempy.io.emd.emdReader(data_location / Path('STEM HAADF-DF4-DF2-BF Diffraction Micro.emd')) except ncempy.io.emd.NoEmdDataSets: pass + + def test_metadata(self, data_location): + f = data_location / Path('Acquisition_18.emd') + import h5py + # Create a data set with missing attributes in the dim vectors + with ncempy.io.emd.fileEMD(f, readonly=True) as f0: + md = f0.getMetadata(0) + + assert md['binning'] == 4 diff --git a/ncempy/test/test_io_emdVelox.py b/ncempy/test/test_io_emdVelox.py index d82a843..b6fe4ba 100644 --- a/ncempy/test/test_io_emdVelox.py +++ b/ncempy/test/test_io_emdVelox.py @@ -24,12 +24,10 @@ def data_location(self): def test_readEMDVelox(self, data_location): dd0 = ncempy.io.emdVelox.emdVeloxReader(data_location / Path('STEM HAADF-DF4-DF2-BF Diffraction Micro.emd'), dsetNum=0) - print(dd0['data'].ndim) assert dd0['data'].ndim == 2 dd2 = ncempy.io.emdVelox.emdVeloxReader(data_location / Path('STEM HAADF-DF4-DF2-BF Diffraction Micro.emd'), dsetNum=2) - print(dd2['data'].ndim) assert dd2['data'].ndim == 2 def test_read_emd_stem(self, data_location): @@ -37,7 +35,6 @@ def test_read_emd_stem(self, data_location): with ncempy.io.emdVelox.fileEMDVelox(data_location / Path('STEM HAADF Diffraction Micro.emd')) as emd0: dd, md = emd0.get_dataset(0) assert dd.ndim == 2 - print(round(md['pixelSize'][0], ndigits=4)) assert md['pixelSizeUnit'][0] == 'nm' assert md['pixelUnit'][0] == 'nm' @@ -63,8 +60,14 @@ def test_read_emd_diffraction(self, data_location): assert md['pixelUnit'][0] == '1/m' def test_file_object(self, data_location): - # Test fileSER class input with file object + """Test fileEMDVelox class input with file object""" file_name = data_location / Path('STEM HAADF-DF4-DF2-BF Diffraction Micro.emd') fid = open(file_name, 'rb') emd0 = ncempy.io.emdVelox.fileEMDVelox(fid) assert hasattr(emd0, '_file_hdl') + + def test_metadata(self, data_location): + file_path = data_location / Path('STEM HAADF Diffraction Micro.emd') + with ncempy.io.emdVelox.fileEMDVelox(file_path) as emd0: + md = emd0.getMetadata(0) + assert md['AccelerationVoltage'] == '300000' diff --git a/ncempy/test/test_io_mrc.py b/ncempy/test/test_io_mrc.py index ba0ec14..23b161c 100644 --- a/ncempy/test/test_io_mrc.py +++ b/ncempy/test/test_io_mrc.py @@ -39,3 +39,9 @@ def test_file_object(self, temp_file): with open(temp_file, 'rb') as f0: mrc0 = ncempy.io.mrc.fileMRC(f0) assert hasattr(mrc0, 'fid') + + # def test_metadata(self): + # file_path = Path('/mnt/NAS-NCEM_Data/TitanX/KateG/KateG/Greenlee_20190413_FeFeO_KGBox4G2/tiltSeries_20190413_FeFeO_neg70to65.mrc') + # with ncempy.io.mrc.fileMRC(file_path) as f0: + # md = f0.getMetadata() + # md['tilt_axis']) diff --git a/ncempy/test/test_io_ser.py b/ncempy/test/test_io_ser.py index aba72c8..b17d716 100644 --- a/ncempy/test/test_io_ser.py +++ b/ncempy/test/test_io_ser.py @@ -64,3 +64,9 @@ def test_file_object(self, data_location): fid = open(file_name, 'rb') ser0 = ncempy.io.ser.fileSER(fid) assert hasattr(ser0, '_file_hdl') + + def test_metdata(self, data_location): + file_name = data_location / Path('16_STOimage_1.ser') + with ncempy.io.ser.fileSER(file_name) as f0: + md = f0.getMetadata() + assert md['High tension [kV]'] == 80 diff --git a/ncempy/test/test_io_smv.py b/ncempy/test/test_io_smv.py index c8f50ba..d5cf9e4 100644 --- a/ncempy/test/test_io_smv.py +++ b/ncempy/test/test_io_smv.py @@ -86,4 +86,10 @@ def test_custom_header(self, temp_file): d = ncempy.io.smv.smvReader(temp_file) with ncempy.io.smv.fileSMV(temp_file) as f0: f0.readHeader() - assert f0.custom_info['4DCAMERA_scan'] == 10 \ No newline at end of file + assert f0.custom_info['4DCAMERA_scan'] == 10 + + def test_metadata(self, data_location): + file_path = data_location / Path('biotin_smv.img') + with ncempy.io.smv.fileSMV(file_path) as f0: + md = f0.getMetadata() + assert md['SIZE1'] == 2048 \ No newline at end of file diff --git a/setup.py b/setup.py index b43b889..4271ab3 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='1.12', + version='1.15', description='openNCEM\'s Python Package', long_description=long_description, @@ -55,9 +55,11 @@ # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], # What does your project relate to? @@ -76,7 +78,7 @@ # requirements files see: # https://packaging.python.org/en/latest/requirements.html - install_requires=['numpy>=2', 'scipy', 'matplotlib', 'h5py>=3'], + install_requires=['numpy>=2', 'scipy', 'matplotlib', 'h5py>=3', 'hdf5plugin'], # List additional groups of dependencies here (e.g. development # dependencies). You can install these using the following syntax,