# Copyright 2019 United Kingdom Research and Innovation# Copyright 2019 The University of Manchester## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.## Authors:# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txtimportnumpyasnpimportosfromcil.frameworkimportAcquisitionData,AcquisitionGeometry,ImageData,ImageGeometryfromcil.versionimportversionimportdatetimefromcil.ioimportutilitiesh5pyAvailable=Truetry:importh5pyexcept:h5pyAvailable=False
[docs]classNEXUSDataWriter(object):''' Create a writer for NEXUS files. Parameters ---------- data: AcquisitionData, ImageData, The dataset to write to file file_name: os.path or string, default None The file name to write compression: str, {'uint8', 'uint16', None}, default None The lossy compression to apply, default None will not compress data. uint8 or unit16 will compress to 8 and 16 bit dtypes respectively. '''def__init__(self,data=None,file_name=None,compression=None):self.data=dataself.file_name=file_nameif((dataisnotNone)and(file_nameisnotNone)):self.set_up(data=data,file_name=file_name,compression=compression)
[docs]defset_up(self,data=None,file_name=None,compression=None):''' Set up the writer data: AcquisitionData, ImageData, The dataset to write to file file_name: os.path or string, default None The file name to write compression: int, default 0 The lossy compression to apply, default 0 will not compress data. 8 or 16 will compress to 8 and 16 bit dtypes respectively. '''self.data=dataself.file_name=file_nameifself.file_nameisNone:raiseException('Path to write file is required.')else:self.file_name=os.path.abspath(file_name)ifself.dataisNone:raiseException('Data to write is required.')ifnotself.file_name.endswith('nxs')andnotself.file_name.endswith('nex'):self.file_name+='.nxs'# Deal with compressionself.compress=utilities.get_compress(compression)self.dtype=utilities.get_compressed_dtype(data,compression)self.compression=compressionifnot((isinstance(self.data,ImageData))or(isinstance(self.data,AcquisitionData))):raiseException('Writer supports only following data types:\n'+' - ImageData\n - AcquisitionData')# check that h5py library is installedif(h5pyAvailable==False):raiseException('h5py is not available, cannot write NEXUS files.')
[docs]defwrite(self):''' write dataset to disk '''# check filename and data have been set:ifself.file_nameisNone:raiseTypeError('Path to nexus file to write to is required.')ifself.dataisNone:raiseTypeError('Data to write out must be set.')# if the folder does not exist, create the folderifnotos.path.isdir(os.path.dirname(self.file_name)):os.mkdir(os.path.dirname(self.file_name))ifself.compressisTrue:scale,offset=utilities.get_compression_scale_offset(self.data,self.compression)# create the filewithh5py.File(self.file_name,'w')asf:# give the file some important attributesf.attrs['file_name']=self.file_namef.attrs['cil_version']=versionf.attrs['file_time']=str(datetime.datetime.utcnow())f.attrs['creator']=np.string_('NEXUSDataWriter.py')f.attrs['NeXus_version']='4.3.0'f.attrs['HDF5_Version']=h5py.version.hdf5_versionf.attrs['h5py_version']=h5py.version.version# create the NXentry groupnxentry=f.create_group('entry1/tomo_entry')nxentry.attrs['NX_class']='NXentry'#create empty data entryds_data=f.create_dataset('entry1/tomo_entry/data/data',shape=self.data.shape,dtype=self.dtype)ifself.compress:ds_data.attrs['scale']=scaleds_data.attrs['offset']=offsetforiinrange(self.data.shape[0]):ds_data[i:(i+1)]=self.data.array[i]*scale+offsetelse:ds_data.write_direct(self.data.array)# set up dataset attributesif(isinstance(self.data,ImageData)):ds_data.attrs['data_type']='ImageData'else:ds_data.attrs['data_type']='AcquisitionData'foriinrange(self.data.number_of_dimensions):ds_data.attrs['dim{}'.format(i)]=self.data.dimension_labels[i]if(isinstance(self.data,AcquisitionData)):# create group to store configurationf.create_group('entry1/tomo_entry/config')f.create_group('entry1/tomo_entry/config/source')f.create_group('entry1/tomo_entry/config/detector')f.create_group('entry1/tomo_entry/config/rotation_axis')ds_data.attrs['geometry']=self.data.geometry.config.system.geometryds_data.attrs['dimension']=self.data.geometry.config.system.dimensionds_data.attrs['num_channels']=self.data.geometry.config.channels.num_channelsf.create_dataset('entry1/tomo_entry/config/detector/direction_x',(self.data.geometry.config.system.detector.direction_x.shape),dtype='float32',data=self.data.geometry.config.system.detector.direction_x)f.create_dataset('entry1/tomo_entry/config/detector/position',(self.data.geometry.config.system.detector.position.shape),dtype='float32',data=self.data.geometry.config.system.detector.position)ifself.data.geometry.config.system.geometry=='cone':f.create_dataset('entry1/tomo_entry/config/source/position',(self.data.geometry.config.system.source.position.shape),dtype='float32',data=self.data.geometry.config.system.source.position)else:f.create_dataset('entry1/tomo_entry/config/ray/direction',(self.data.geometry.config.system.ray.direction.shape),dtype='float32',data=self.data.geometry.config.system.ray.direction)f.create_dataset('entry1/tomo_entry/config/rotation_axis/position',(self.data.geometry.config.system.rotation_axis.position.shape),dtype='float32',data=self.data.geometry.config.system.rotation_axis.position)ds_data.attrs['num_pixels_h']=self.data.geometry.config.panel.num_pixels[0]ds_data.attrs['pixel_size_h']=self.data.geometry.config.panel.pixel_size[0]ds_data.attrs['panel_origin']=self.data.geometry.config.panel.originifself.data.geometry.config.system.dimension=='3D':f.create_dataset('entry1/tomo_entry/config/detector/direction_y',(self.data.geometry.config.system.detector.direction_y.shape),dtype='float32',data=self.data.geometry.config.system.detector.direction_y)f.create_dataset('entry1/tomo_entry/config/rotation_axis/direction',(self.data.geometry.config.system.rotation_axis.direction.shape),dtype='float32',data=self.data.geometry.config.system.rotation_axis.direction)ds_data.attrs['num_pixels_v']=self.data.geometry.config.panel.num_pixels[1]ds_data.attrs['pixel_size_v']=self.data.geometry.config.panel.pixel_size[1]angles=f.create_dataset('entry1/tomo_entry/config/angles',(self.data.geometry.config.angles.angle_data.shape),dtype='float32',data=self.data.geometry.config.angles.angle_data)angles.attrs['angle_unit']=self.data.geometry.config.angles.angle_unitangles.attrs['initial_angle']=self.data.geometry.config.angles.initial_angleelse:# ImageDatads_data.attrs['voxel_num_x']=self.data.geometry.voxel_num_xds_data.attrs['voxel_num_y']=self.data.geometry.voxel_num_yds_data.attrs['voxel_num_z']=self.data.geometry.voxel_num_zds_data.attrs['voxel_size_x']=self.data.geometry.voxel_size_xds_data.attrs['voxel_size_y']=self.data.geometry.voxel_size_yds_data.attrs['voxel_size_z']=self.data.geometry.voxel_size_zds_data.attrs['center_x']=self.data.geometry.center_xds_data.attrs['center_y']=self.data.geometry.center_yds_data.attrs['center_z']=self.data.geometry.center_zds_data.attrs['channels']=self.data.geometry.channelsds_data.attrs['channel_spacing']=self.data.geometry.channel_spacing