diff --git a/_test/test_sqw/test_save.m b/_test/test_sqw/test_save.m index 5a96a54b7d..e8f1339d51 100644 --- a/_test/test_sqw/test_save.m +++ b/_test/test_sqw/test_save.m @@ -344,7 +344,7 @@ function test_save_upgrade_automatically_filebacked(obj) test_obj.save(targ_file); ldr = sqw_formats_factory.instance().get_loader(targ_file); - assertTrue(isa(ldr,'faccess_sqw_v4')); + assertTrue(isa(ldr,'faccess_sqw_v4_1')); rec = ldr.get_sqw(); ldr.delete(); @@ -366,7 +366,7 @@ function test_save_upgrade_automatically_filebacked_large_page(obj) test_obj.save(targ_file); ldr = sqw_formats_factory.instance().get_loader(targ_file); - assertTrue(isa(ldr,'faccess_sqw_v4')); + assertTrue(isa(ldr,'faccess_sqw_v4_1')); rec = ldr.get_sqw(); ldr.delete(); @@ -389,7 +389,7 @@ function test_save_tmp_moves_to_new_file_upgrades_all_bar_pix(obj) assertFalse(isfile(source_to_move)); ldr = sqw_formats_factory.instance().get_loader(targ_file); - assertTrue(isa(ldr,'faccess_sqw_v4')); + assertTrue(isa(ldr,'faccess_sqw_v4_1')); rec = ldr.get_sqw(); ldr.delete(); assertEqualToTol(rec,test_obj,'ignore_str',true,'tol',[4*eps('single'),4*eps('single')]); @@ -413,7 +413,7 @@ function test_save_permanent_creates_new_file(obj) assertTrue(isfile(source_for_fb)); ldr = sqw_formats_factory.instance().get_loader(targ_file); - assertTrue(isa(ldr,'faccess_sqw_v4')); + assertTrue(isa(ldr,'faccess_sqw_v4_1')); rec = ldr.get_sqw(); ldr.delete(); diff --git a/_test/test_sqw_class/test_mushroom_sqw.m b/_test/test_sqw_class/test_mushroom_sqw.m index ccdcd841a6..6c135323b4 100644 --- a/_test/test_sqw_class/test_mushroom_sqw.m +++ b/_test/test_sqw_class/test_mushroom_sqw.m @@ -95,7 +95,7 @@ function test_gen_sqw(obj) 2, [2*pi,2*pi,2*pi], [90,90,90], [0,0,1], [0,-1,0],0,0,0,0,0); ldr = sqw_formats_factory.instance().get_loader(sqw_file); - assertTrue(isa(ldr,'faccess_sqw_v4')); + assertTrue(isa(ldr,'faccess_sqw_v4_1')); sqo = read_sqw(sqw_file); diff --git a/_test/test_sqw_file/sqw_binfile_common_tester.m b/_test/test_sqw_file/sqw_binfile_common_tester.m index 5928e82c17..82eda00c01 100644 --- a/_test/test_sqw_file/sqw_binfile_common_tester.m +++ b/_test/test_sqw_file/sqw_binfile_common_tester.m @@ -14,7 +14,7 @@ methods % initialize the loader, to be ready to read or write the data function obj = init(obj,accessor) - if nargin<2 || ~(isa(accessor,'sqw_binfile_common') || isa(accessor,'faccess_sqw_v4')) + if nargin<2 || ~(isa(accessor,'sqw_binfile_common') || isa(accessor,'faccess_sqw_v4_1')) error('HORACE:sqw_binfile_common_tester:invalid_argument', ... 'init can be called only with a version of an file accessor') end diff --git a/_test/test_sqw_file/test_faccess_sqw_v2.m b/_test/test_sqw_file/test_faccess_sqw_v2.m index c506f308fa..be4ed0ef9d 100644 --- a/_test/test_sqw_file/test_faccess_sqw_v2.m +++ b/_test/test_sqw_file/test_faccess_sqw_v2.m @@ -268,14 +268,15 @@ function test_empty_init_does_nothing(~) tob = faccess_sqw_v2(tf); tob = tob.upgrade_file_format(); - assertTrue(isa(tob,'faccess_sqw_v4')); + assertTrue(isa(tob,'faccess_sqw_v4_1')); sqw1 = tob.get_sqw(); tob.delete(); + to = sqw_formats_factory.instance().get_loader(tf); - assertTrue(isa(to,'faccess_sqw_v4')); + assertTrue(isa(to,'faccess_sqw_v4_1')); sqw2 = to.get_sqw(); to.delete(); @@ -296,13 +297,13 @@ function test_empty_init_does_nothing(~) tob = faccess_sqw_v2(tf); tob = tob.upgrade_file_format(); - assertTrue(isa(tob,'faccess_sqw_v4')); + assertTrue(isa(tob,'faccess_sqw_v4_1')); sqw1 = tob.get_sqw(); tob.delete(); to = sqw_formats_factory.instance().get_loader(tf); - assertTrue(isa(to,'faccess_sqw_v4')); + assertTrue(isa(to,'faccess_sqw_v4_1')); sqw2 = to.get_sqw(); to.delete(); @@ -327,7 +328,7 @@ function test_empty_init_does_nothing(~) tob = tob.put_sqw(); tobV4 = tob.upgrade_file_format(); - assertTrue(isa(tobV4,'faccess_sqw_v4')); + assertTrue(isa(tobV4,'faccess_sqw_v4_1')); sqw1 = tobV4.get_sqw(); tobV4.delete(); @@ -339,7 +340,7 @@ function test_empty_init_does_nothing(~) to = sqw_formats_factory.instance().get_loader(tf); - assertTrue(isa(to,'faccess_sqw_v4')); + assertTrue(isa(to,'faccess_sqw_v4_1')); sqw2 = to.get_sqw(); to.delete(); diff --git a/_test/test_sqw_file/test_faccess_sqw_v3_21.m b/_test/test_sqw_file/test_faccess_sqw_v3_21.m index c856e3561c..5e162e8e9d 100644 --- a/_test/test_sqw_file/test_faccess_sqw_v3_21.m +++ b/_test/test_sqw_file/test_faccess_sqw_v3_21.m @@ -87,7 +87,7 @@ function delete(obj) % format, containing pixel range ldr = ldr.upgrade_file_format(); - assertTrue(isa(ldr,'faccess_sqw_v4')); + assertTrue(isa(ldr,'faccess_sqw_v4_1')); pix_range1 = ldr.get_pix_range(); % 3e-7 -- conversion from double to single diff --git a/_test/test_sqw_file/test_sqw_formats_factory.m b/_test/test_sqw_file/test_sqw_formats_factory.m index 20be1014cf..aeea603cc1 100644 --- a/_test/test_sqw_file/test_sqw_formats_factory.m +++ b/_test/test_sqw_file/test_sqw_formats_factory.m @@ -150,7 +150,7 @@ function test_selection_v1(obj) function obj= test_pref_access(obj) dob = sqw(); ld1 = sqw_formats_factory.instance().get_pref_access(dob); - assertTrue(isa(ld1,'faccess_sqw_v4')); + assertTrue(isa(ld1,'faccess_sqw_v4_1')); dob = d1d(); ld2 = sqw_formats_factory.instance().get_pref_access(dob); diff --git a/_test/test_utilities_herbert/test_disp2str.m b/_test/test_utilities_herbert/test_disp2str.m index 93d5817873..da43e403a6 100644 --- a/_test/test_utilities_herbert/test_disp2str.m +++ b/_test/test_utilities_herbert/test_disp2str.m @@ -12,13 +12,13 @@ function test_truncate_returns_provided(~) ss = disp2str(1:100,60,'constrained'); ss = splitlines(ss); assertEqual(ss{2},... - '1 2 3 4 5 6 7 constrained') + '1 2 3 4 5 6 7 constrained') end function test_truncate_returns_default(~) ss = disp2str(1:100,60); ss = splitlines(ss); assertEqual(ss{2}, ... - '1 2 3 4 5 6 7 ...truncated.') + '1 2 3 4 5 6 7 ...truncated.') end function test_cell(~) pat = {1,2,3}; diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/dump_sqw_fields.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/dump_sqw_fields.m new file mode 100644 index 0000000000..8929a30f81 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/dump_sqw_fields.m @@ -0,0 +1,5 @@ +function obj = dump_sqw_fields(obj,mod_sqw,varargin) +%DUMP_SQW_FIELDS given initalized sqw file accessor, store specified parts +% of input sqw object back into existing sqw file. +error('HORACE:faccess_sqw_v4_1:not_implemented', ... + 'This method is not yet implememted. See Re #1320') diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/faccess_sqw_v4_1.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/faccess_sqw_v4_1.m new file mode 100644 index 0000000000..39681715ee --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/faccess_sqw_v4_1.m @@ -0,0 +1,314 @@ +classdef faccess_sqw_v4_1 < binfile_v4_common & sqw_file_interface + % Class to access Horace sqw files written by Horace v4.1 + % this class differs from the previous v4 by reading and + % writing pixel data from/to file in a compressed form. Other than + % that the accessors are the same; processing of non-pixel data and in particular + % image data are unaltered in form from v4. + % + % Most of the class properties and methods are inherited from + % binfile_v4_common + % class but this class provides classical faccess interface. + % + % Usage: + % 1) + %>>sqw_access = faccess_sqw_v4_1(filename) + % or + % 2) + %>>sqw_access = faccess_sqw_v4_1(sqw_object,filename) + %--------------------------------------------------------------- + % + % 1)------------------------------------------------------------ + % First form initializes accessor to existing sqw file where + % filename :: the name of existing dnd file. + % + % Throws if file with filename is missing or is not written in sqw v4_1 + % format. + % + % To avoid attempts to initialize this accessor using incorrect sqw file, + % access to existing sqw files should be organized using sqw format factory + % namely: + % + % >> accessor = sqw_formats_factory.instance().get_loader(filename) + % + % so that if the sqw file with filename is a v1 or v2 sqw file, the sqw format factory will + % return an instance of the appropriate faccess class, initialized for reading that type of file. + % The initialized object allows to use all get/read methods described by horace_binfile_interface. + % + % 2)------------------------------------------------------------ + % Second form used to initialize the operation of writing new or updating existing sqw file. + % where: + % sqw_object:: existing fully initialized sqw object in memory. + % filename :: the name of a new or existing dnd object on disc + % + % Update mode is initialized if the file with name filename exists and can be updated, + % i.e. has the same number of dimensions, binning and axis. In this case you can modify + % dnd metadata. + % + % if existing file can not be updated, it will be open in write mode. + % If file with filename does not exist, the object will be open in write mode. + % + % Initialized faccess_sqw_v4 object allows to use write/update methods of dnd_format_interface + % and all read methods if the proper information already exists in the file. + % + % + % + properties(Constant,Access=protected) + % list of data blocks, this class maintains + sqw_blocks_list_ = {data_block('','main_header'),... + data_block('','detpar'),... + data_block('data','metadata'),dnd_data_block(),... + data_block('experiment_info','instruments'),... + data_block('experiment_info','samples'),... + data_block('experiment_info','expdata'),... + data_block('pix','metadata'),pix_data_block()} + end + properties(Dependent) + % return the number of fields that the pixel data stored on hdd has + num_pix_fields + end + properties(Access=private) + clear_caches_ = true; + end + %====================================================================== + % ACCESSORS & constructor + methods + function obj=faccess_sqw_v4_1(varargin) + % constructor to build sqw reader/writer version 4_1 + % + % Usage: + % ld = faccess_sqw_v4_1() % initialize empty sqw reader/writer + % version 4.1. + % The class should be populated later + % using init method + % ld = faccess_sqw_v4_1(filename) % initialize sqw reader/writer + % version 4.1 to load sqw file version 4.1. + % Throw error if the file version is not + % sqw version 4.1. + % ld = faccess_sqw_v4_1(sqw_object,[filename]) % initialize sqw + % reader/writer version 4 + % to save sqw object provided. The name + % of the file to save the object should + % be provided either separately or as the second + % argument of the constructor in this + % form. + % + obj = obj@binfile_v4_common(varargin{:}); + end + % + function npf = get.num_pix_fields(~) + % returns the number of pix fields stored on disk i.e. the compressed + % number 4, not the number of pix fields to which the data will be + % decompressed on read into memory + npf = 9; % when the file changes have been made, will be 4; + % THIS ALL FOR THE FUTURE DIFFERENT PIX FORMAT + % persistent mbb_cache; + % if obj.clear_caches_ + % mbb_cache = []; + % end + % if isempty(mbb_cache) + % if obj.bat_.initialized + % mbb_cache = obj.get_sqw_block('bl_pix_metadata'); + % npf = mbb_cache.num_pix_fields; + % else + % npf = 9; % default number of num_pix_fields + % end + % else + % npf = mbb_cache.num_pix_fields; + % end + end + end + %====================================================================== + % Main interface + methods + function [obj,file_exist,old_ldr] = set_file_to_update(obj,filename) + % open existing file for update its format and/or data blocks + % stored in it. + % Inputs: + % + if ~exist('filename','var') + filename = obj.full_filename; + end + % CM_TODO + [obj,file_exist,old_ldr] = set_file_to_update@horace_binfile_interface(obj,filename,nargout); + if ~old_ldr.sqw_type + error('HORACE:faccess_sqw_v4_1:invalid_argument', ... + 'Can not update file %s containing dnd object using sqw accessor', ... + filename) + end + end + %================================================================== + % retrieve the whole or partial sqw object from properly initialized sqw file + [sqwobj,varargout] = get_sqw(obj,varargin) + [mn_hdr,obj] = get_main_header(obj,varargin); + [expinf,pos] = get_exp_info(obj,varargin); + [detpar,obj] = get_detpar(obj,varargin); + [pix,obj] = get_pix(obj,varargin); + [pix,obj] = get_raw_pix(obj,varargin); + % read pixels at the given indices + pix = get_pix_at_indices(obj,indices); + % read pixels in the given index ranges + pix = get_pix_in_ranges(obj,pix_starts,pix_ends,skip_validation,keep_precision); + %------------------------------------------------------------------ + [pix_range,obj] = get_pix_range(obj,varargin) + [meta,obj] = get_pix_metadata(obj); + [dat_range,obj] = get_data_range(obj,varargin) + [samp,obj] = get_sample(obj,varargin) + [inst,obj] = get_instrument(obj,varargin) + %================================================================== + obj = dump_sqw_fields(obj,mod_sqw,varargin); + % common write interface for v4/v4.1 + obj = put_main_header(obj,varargin); + obj = put_headers(obj,varargin); + obj = put_det_info(obj,varargin); + obj = put_pix(obj,varargin); + obj = put_raw_pix(obj,pix_data,pix_idx,varargin); + obj = put_num_pixels(obj,num_pixels); + obj = put_sqw(obj,varargin); + % + obj = put_instruments(obj,varargin); + obj = put_samples(obj,varargin); + obj = put_pix_metadata(ob,pix_class_or_metadata) + end + %====================================================================== + % Old, interface + methods + function hd = head(obj,varargin) + % Return the information which describes sqw file in a standard form + % + [ok,mess,full_data] = parse_char_options(varargin,'-full'); + if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',mess); + end + hd =head@binfile_v4_common(obj,varargin{:}); + + hd = obj.shuffle_fields_form_sqw_head(hd,full_data); + end + + % ----------------------------------------------------------------- + end + %---------------------------------------------------------------------- + methods(Access=protected) + % Given initialized sqw object in memory, initialized BAT and sqw file + % written in old file format, write everything in memory to proper places + % in file keeping pixels data on their original place. + obj = update_sqw_keep_pix(obj) + + function npix = get_npixels(obj) + pix_data_bl = obj.bat_.blocks_list{end}; + npix = pix_data_bl.npixels; + end + function [obj,missinig_fields] = copy_contents(obj,other_obj,varargin) + % Copy information, relevant to new file format from the old file format + % and update the information, which can be updated. + % + % Optional: + % '-upgrade_range' -- upgrade pixel data range in case if + % it is not defined. May be long operation + % as scans over the whole data file. + [ok,mess,upgrade_range,argi] = parse_char_options(varargin,'-upgrade_range'); + if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',mess); + end + [obj,missinig_fields] = copy_contents@binfile_v4_common(obj,other_obj,varargin{:}); + if ~upgrade_range + if ~PixelDataBase.do_filebacked(other_obj.npixels) || ... + obj.faccess_version == other_obj.faccess_version + return; + end + end + [obj,missinig_fields] = copy_contents_(obj,other_obj,upgrade_range,argi{:}); + end + function other_obj = do_class_dependent_updates(~,other_obj,upgrade_range,varargin) + % Function does nothing when old object and new objects are recent + % file version objects or stores fields which may change when + % upgrade_range is true + if ~upgrade_range + return; + end + other_obj = other_obj.put_all_blocks('ignore_blocks','bl_pix_data_wrap'); + end + + function dt = get_data_type(~) + % overloadable accessor for the class datatype function + dt = 'a'; + end + function bll = get_data_blocks(~) + % Return list of data blocks, defined on this class + % main bat of data_blocks getter. Protected for possibility to + % overload + bll = faccess_sqw_v4_1.sqw_blocks_list_; + end + function is_sqw = get_sqw_type(~) + % Main part of get.sqw_type accessor + % return true if the loader is intended for processing sqw file + % format and false otherwise + is_sqw = true; + end + % + function obj_type = get_format_for_object(~) + % main part of the format_for_object getter, specifying for + % what class saving the file format is intended + obj_type = 'sqw'; + end + function cd = get_creation_date(obj) + % main accessor for creation date for sqw object + % The creation data is defined in main header + % + if obj.bat_.initialized + if ~isempty(obj.sqw_holder) + if isa(obj.sqw_holder,'sqw') || is_sqw_struct(obj.sqw_holder) + mh = obj.sqw_holder.main_header; + elseif isa(obj.sqw_holder,"DnDBase") + mh = obj.sqw_holder; + else + mh = obj.get_main_header(); + end + else + mh = obj.get_main_header(); + end + cd = mh.creation_date; + else + cd = get_creation_date@binfile_v4_common(obj); + end + end + function pos = get_pix_position(obj) + pix_block = obj.bat_.blocks_list{end}; + % pix + pos = pix_block.pix_position; + end + function npix = get_npix(obj) + pix_data_bl = obj.bat_.blocks_list{end-1}; % block responsible for pix metadata; + npix = pix_data_bl.npix; + end + % + function obj=init_from_sqw_obj(obj,varargin) + % initalize faccessor using sqw object as input + % + % initialize binfile_v4 interface + obj = init_from_sqw_obj@binfile_v4_common(obj,varargin{:}); + % intialize sqw_file_interface. + % sqw holder now contains sqw object by definition + obj.num_contrib_files_ = obj.sqw_holder.main_header.nfiles; + end + function obj=init_from_sqw_file(obj,varargin) + % initalize faccessor using sqw file as input + % + % initialize binfile_v4 interface + obj = init_from_sqw_file@binfile_v4_common(obj,varargin{:}); + % intialize sqw_file_interface. + nfil_bl = obj.bat_.blocks_list{1}; % block responsible for main header + [~,mhb] = nfil_bl.get_sqw_block(obj.file_id_); + obj.num_contrib_files_ = mhb.nfiles; + end + + end + %====================================================================== + % SERIALIZABLE INTERFACE MAINLY INHERITED FROM binfile_v4_common + %====================================================================== + methods + function flds = saveableFields(obj) + flds = saveableFields@binfile_v4_common(obj); + flds = [flds(:)','num_contrib_files']; + end + end +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/faccess_sqw_v4_1.m.bak b/horace_core/sqw/file_io/@faccess_sqw_v4_1/faccess_sqw_v4_1.m.bak new file mode 100644 index 0000000000..2b87170488 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/faccess_sqw_v4_1.m.bak @@ -0,0 +1,314 @@ +classdef faccess_sqw_v4_1 < binfile_v4_common & sqw_file_interface + % Class to access Horace sqw files written by Horace v4.1 + % this class differs from the previous v4 by reading and + % writing pixel data from/to file in a compressed form. Other than + % that the accessors are the same; processing of non-pixel data and in particular + % image data are unaltered in form from v4. + % + % Most of the class properties and methods are inherited from + % binfile_v4_common + % class but this class provides classical faccess interface. + % + % Usage: + % 1) + %>>sqw_access = faccess_sqw_v4_1(filename) + % or + % 2) + %>>sqw_access = faccess_sqw_v4_1(sqw_object,filename) + %--------------------------------------------------------------- + % + % 1)------------------------------------------------------------ + % First form initializes accessor to existing sqw file where + % filename :: the name of existing dnd file. + % + % Throws if file with filename is missing or is not written in sqw v4_1 + % format. + % + % To avoid attempts to initialize this accessor using incorrect sqw file, + % access to existing sqw files should be organized using sqw format factory + % namely: + % + % >> accessor = sqw_formats_factory.instance().get_loader(filename) + % + % so that if the sqw file with filename is a v1 or v2 sqw file, the sqw format factory will + % return an instance of the appropriate faccess class, initialized for reading that type of file. + % The initialized object allows to use all get/read methods described by horace_binfile_interface. + % + % 2)------------------------------------------------------------ + % Second form used to initialize the operation of writing new or updating existing sqw file. + % where: + % sqw_object:: existing fully initialized sqw object in memory. + % filename :: the name of a new or existing dnd object on disc + % + % Update mode is initialized if the file with name filename exists and can be updated, + % i.e. has the same number of dimensions, binning and axis. In this case you can modify + % dnd metadata. + % + % if existing file can not be updated, it will be open in write mode. + % If file with filename does not exist, the object will be open in write mode. + % + % Initialized faccess_sqw_v4 object allows to use write/update methods of dnd_format_interface + % and all read methods if the proper information already exists in the file. + % + % + % + properties(Constant,Access=protected) + % list of data blocks, this class maintains + sqw_blocks_list_ = {data_block('','main_header'),... + data_block('','detpar'),... + data_block('data','metadata'),dnd_data_block(),... + data_block('experiment_info','instruments'),... + data_block('experiment_info','samples'),... + data_block('experiment_info','expdata'),... + data_block('pix','metadata'),pix_data_block()} + end + properties(Dependent) + % return the number of fields that the pixel data stored on hdd has + num_pix_fields + end + properties(Access=private) + clear_caches_ = true; + end + %====================================================================== + % ACCESSORS & constructor + methods + function obj=faccess_sqw_v4_1(varargin) + % constructor to build sqw reader/writer version 4_1 + % + % Usage: + % ld = faccess_sqw_v4_1() % initialize empty sqw reader/writer + % version 4.1. + % The class should be populated later + % using init method + % ld = faccess_sqw_v4_1(filename) % initialize sqw reader/writer + % version 4.1 to load sqw file version 4.1. + % Throw error if the file version is not + % sqw version 4.1. + % ld = faccess_sqw_v4_1(sqw_object,[filename]) % initialize sqw + % reader/writer version 4 + % to save sqw object provided. The name + % of the file to save the object should + % be provided either separately or as the second + % argument of the constructor in this + % form. + % + obj = obj@binfile_v4_common(varargin{:}); + end + % + function npf = get.num_pix_fields(~) + % returns the number of pix fields stored on disk i.e. the compressed + % number 4, not the number of pix fields to which the data will be + % decompressed on read into memory + npf = 4; + % THIS ALL FOR THE FUTURE DIFFERENT PIX FORMAT + % persistent mbb_cache; + % if obj.clear_caches_ + % mbb_cache = []; + % end + % if isempty(mbb_cache) + % if obj.bat_.initialized + % mbb_cache = obj.get_sqw_block('bl_pix_metadata'); + % npf = mbb_cache.num_pix_fields; + % else + % npf = 9; % default number of num_pix_fields + % end + % else + % npf = mbb_cache.num_pix_fields; + % end + end + end + %====================================================================== + % Main interface + methods + function [obj,file_exist,old_ldr] = set_file_to_update(obj,filename) + % open existing file for update its format and/or data blocks + % stored in it. + % Inputs: + % + if ~exist('filename','var') + filename = obj.full_filename; + end + % CM_TODO + [obj,file_exist,old_ldr] = set_file_to_update@horace_binfile_interface(obj,filename,nargout); + if ~old_ldr.sqw_type + error('HORACE:faccess_sqw_v4_1:invalid_argument', ... + 'Can not update file %s containing dnd object using sqw accessor', ... + filename) + end + end + %================================================================== + % retrieve the whole or partial sqw object from properly initialized sqw file + [sqwobj,varargout] = get_sqw(obj,varargin) + [mn_hdr,obj] = get_main_header(obj,varargin); + [expinf,pos] = get_exp_info(obj,varargin); + [detpar,obj] = get_detpar(obj,varargin); + [pix,obj] = get_pix(obj,varargin); + [pix,obj] = get_raw_pix(obj,varargin); + % read pixels at the given indices + pix = get_pix_at_indices(obj,indices); + % read pixels in the given index ranges + pix = get_pix_in_ranges(obj,pix_starts,pix_ends,skip_validation,keep_precision); + %------------------------------------------------------------------ + [pix_range,obj] = get_pix_range(obj,varargin) + [meta,obj] = get_pix_metadata(obj); + [dat_range,obj] = get_data_range(obj,varargin) + [samp,obj] = get_sample(obj,varargin) + [inst,obj] = get_instrument(obj,varargin) + %================================================================== + obj = dump_sqw_fields(obj,mod_sqw,varargin); + % common write interface for v4/v4.1 + obj = put_main_header(obj,varargin); + obj = put_headers(obj,varargin); + obj = put_det_info(obj,varargin); + obj = put_pix(obj,varargin); + obj = put_raw_pix(obj,pix_data,pix_idx,varargin); + obj = put_num_pixels(obj,num_pixels); + obj = put_sqw(obj,varargin); + % + obj = put_instruments(obj,varargin); + obj = put_samples(obj,varargin); + obj = put_pix_metadata(ob,pix_class_or_metadata) + end + %====================================================================== + % Old, interface + methods + function hd = head(obj,varargin) + % Return the information which describes sqw file in a standard form + % + [ok,mess,full_data] = parse_char_options(varargin,'-full'); + if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',mess); + end + hd =head@binfile_v4_common(obj,varargin{:}); + + hd = obj.shuffle_fields_form_sqw_head(hd,full_data); + end + + % ----------------------------------------------------------------- + end + %---------------------------------------------------------------------- + methods(Access=protected) + % Given initialized sqw object in memory, initialized BAT and sqw file + % written in old file format, write everything in memory to proper places + % in file keeping pixels data on their original place. + obj = update_sqw_keep_pix(obj) + + function npix = get_npixels(obj) + pix_data_bl = obj.bat_.blocks_list{end}; + npix = pix_data_bl.npixels; + end + function [obj,missinig_fields] = copy_contents(obj,other_obj,varargin) + % Copy information, relevant to new file format from the old file format + % and update the information, which can be updated. + % + % Optional: + % '-upgrade_range' -- upgrade pixel data range in case if + % it is not defined. May be long operation + % as scans over the whole data file. + [ok,mess,upgrade_range,argi] = parse_char_options(varargin,'-upgrade_range'); + if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',mess); + end + [obj,missinig_fields] = copy_contents@binfile_v4_common(obj,other_obj,varargin{:}); + if ~upgrade_range + if ~PixelDataBase.do_filebacked(other_obj.npixels) || ... + obj.faccess_version == other_obj.faccess_version + return; + end + end + [obj,missinig_fields] = copy_contents_(obj,other_obj,upgrade_range,argi{:}); + end + function other_obj = do_class_dependent_updates(~,other_obj,upgrade_range,varargin) + % Function does nothing when old object and new objects are recent + % file version objects or stores fields which may change when + % upgrade_range is true + if ~upgrade_range + return; + end + other_obj = other_obj.put_all_blocks('ignore_blocks','bl_pix_data_wrap'); + end + + function dt = get_data_type(~) + % overloadable accessor for the class datatype function + dt = 'a'; + end + function bll = get_data_blocks(~) + % Return list of data blocks, defined on this class + % main bat of data_blocks getter. Protected for possibility to + % overload + bll = faccess_sqw_v4_1.sqw_blocks_list_; + end + function is_sqw = get_sqw_type(~) + % Main part of get.sqw_type accessor + % return true if the loader is intended for processing sqw file + % format and false otherwise + is_sqw = true; + end + % + function obj_type = get_format_for_object(~) + % main part of the format_for_object getter, specifying for + % what class saving the file format is intended + obj_type = 'sqw'; + end + function cd = get_creation_date(obj) + % main accessor for creation date for sqw object + % The creation data is defined in main header + % + if obj.bat_.initialized + if ~isempty(obj.sqw_holder) + if isa(obj.sqw_holder,'sqw') || is_sqw_struct(obj.sqw_holder) + mh = obj.sqw_holder.main_header; + elseif isa(obj.sqw_holder,"DnDBase") + mh = obj.sqw_holder; + else + mh = obj.get_main_header(); + end + else + mh = obj.get_main_header(); + end + cd = mh.creation_date; + else + cd = get_creation_date@binfile_v4_common(obj); + end + end + function pos = get_pix_position(obj) + pix_block = obj.bat_.blocks_list{end}; + % pix + pos = pix_block.pix_position; + end + function npix = get_npix(obj) + pix_data_bl = obj.bat_.blocks_list{end-1}; % block responsible for pix metadata; + npix = pix_data_bl.npix; + end + % + function obj=init_from_sqw_obj(obj,varargin) + % initalize faccessor using sqw object as input + % + % initialize binfile_v4 interface + obj = init_from_sqw_obj@binfile_v4_common(obj,varargin{:}); + % intialize sqw_file_interface. + % sqw holder now contains sqw object by definition + obj.num_contrib_files_ = obj.sqw_holder.main_header.nfiles; + end + function obj=init_from_sqw_file(obj,varargin) + % initalize faccessor using sqw file as input + % + % initialize binfile_v4 interface + obj = init_from_sqw_file@binfile_v4_common(obj,varargin{:}); + % intialize sqw_file_interface. + nfil_bl = obj.bat_.blocks_list{1}; % block responsible for main header + [~,mhb] = nfil_bl.get_sqw_block(obj.file_id_); + obj.num_contrib_files_ = mhb.nfiles; + end + + end + %====================================================================== + % SERIALIZABLE INTERFACE MAINLY INHERITED FROM binfile_v4_common + %====================================================================== + methods + function flds = saveableFields(obj) + flds = saveableFields@binfile_v4_common(obj); + flds = [flds(:)','num_contrib_files']; + end + end +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_data_range.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_data_range.m new file mode 100644 index 0000000000..028fc502e4 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_data_range.m @@ -0,0 +1,11 @@ +function [data_range,obj] = get_data_range(obj,varargin) +% get [2x9] array of min/max ranges of the pixels contributing +% into an object. Empty for DND object +sqh = obj.sqw_holder; +obj.sqw_holder_ = []; +[obj,metadata] = obj.get_sqw_block('bl_pix_metadata',varargin{:}); +data_range = metadata.data_range; +if ~isempty(sqh) + sqh.pix.metadata = metadata; + obj.sqw_holder_ = sqh; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_detpar.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_detpar.m new file mode 100644 index 0000000000..bb5fa0c44f --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_detpar.m @@ -0,0 +1,7 @@ +function [detpar,obj] = get_detpar(obj,varargin) +% return detectors container, stored in sqw file +% Usage: +%>>detpar = obj.get_detpar() % Returns detectors block +% stored in the file +[obj,detpar] = obj.get_sqw_block('bl__detpar',varargin{:}); + diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_exp_info.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_exp_info.m new file mode 100644 index 0000000000..97c117fad1 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_exp_info.m @@ -0,0 +1,56 @@ +function [exper,obj] = get_exp_info(obj,varargin) +% Get full data header or headers for sqw file written in format v4_1 +% +% If instrument and sample are present in the file (not the empty +% structures) it loads instruments and samples from the file and attaches +% them to the header(s) +% +% Usage: +%>>exp_info = obj.get_exp_info(); -- get header number 1 +%>>exp_info = obj.get_exp_info(1); -- get header number 1 +%>>exp_info = obj.get_exp_info(number); -- get header with specified number +%>>exp_info = obj.get_exp_info(numbers);-- where numbers are array of numbers +% return headers with these numbers +% +%>>exp_info = obj.get_exp_info('-all'); +%>>exp_info = obj.get_exp_info('-no_samp_inst'); % do not set up sample and instrument to header +% even if they are defined in the file, except the basic sample and inst, +% defined in version 2 +% +% First three forms return single header, first two return header number 1. + +%NOTE: +% The sample number corresponds to the header number. +% TODO: Clarify, % should it be run_id? +% +[argi,samp_inst_number] = parse_get_inst_sample_arg_(obj,varargin{:}); +% after that, the only parameters may +[ok,mess,no_isamp_inst,argi]= parse_char_options(argi,{'-no_sampinst'}); +if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',mess); +end +% at this stage, arguments can only describe initialization +[obj,exp_data] = obj.get_sqw_block('bl_experiment_info_expdata',argi{:}); +if no_isamp_inst + if ~isinf(samp_inst_number) + exp_data = exp_data(samp_inst_number); + end + % leave detector arrays to be filled in from detpar + exper = Experiment([],IX_null_inst(),IX_null_sample,exp_data); + return; +end + +[obj,Inst] = obj.get_sqw_block('bl_experiment_info_instruments'); +[obj,samp] = obj.get_sqw_block('bl_experiment_info_samples'); +[obj,detpar]=obj.get_sqw_block('bl__detpar'); + +n_instances = numel(exp_data); +detpar = obj.convert_old_det_forms(detpar,n_instances); +if ~isinf(samp_inst_number) + exp_data = exp_data(samp_inst_number); + Inst = Inst(samp_inst_number); + samp = samp(samp_inst_number); + detpar = detpar(samp_inst_number); +end +exper = Experiment(detpar,Inst,samp,exp_data); + diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_instrument.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_instrument.m new file mode 100644 index 0000000000..1f2b0be8de --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_instrument.m @@ -0,0 +1,20 @@ +function [inst,obj] = get_instrument(obj,varargin) +% return instruments container stored in file or some part of +% this container, containing particular instrument +% Usage: +%>>inst = obj.get_instrument() % Returns first unique instrument, +% present in the file +%>>inst = obj.get_instrument(number) % Returns instrument with +% number, specified as input. +%>>inst = obj.get_instrument('-all') % Returns unique object +% container with all instruments stored in the file +%NOTE: +% The instrument number (option 2) corresponds to the header number. +% TODO: Clarify, should it be run_id? +% +[argi,instr_number] = parse_get_inst_sample_arg_(obj,varargin{:}); +[inst,obj] = obj.get_block_data('bl_experiment_info_instruments',argi{:}); +if ~isinf(instr_number) + inst = inst(instr_number); +end + diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_main_header.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_main_header.m new file mode 100644 index 0000000000..a457dad442 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_main_header.m @@ -0,0 +1,27 @@ +function [head,obj] = get_main_header(obj,varargin) +% Return main sqw file header class stored in the file, +% the loader is initialized with. +% +% Usage: +%>>[head,obj] = obj.get_main_header() % Returns the header class +% present in sqw file. Modifies the name of the sqw file, the +% header has been build for to the current name of the file +%>>[head,obj] = obj.get_main_header('-keep_original') % Returns the header class +% present in sqw file, keeps the original file name. + +% +[ok,mess,keep_original,verbatim,argi] = parse_char_options(varargin,... + {'-keep_original','-verbatim'}); +if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',... + mess) +end +keep_original = keep_original||verbatim; +% +[obj,head] = obj.get_sqw_block('bl__main_header',argi{:}); + +if ~keep_original + [fp,fn,fext] = fileparts(obj.full_filename); + head.filename = [fn,fext]; + head.filepath = fp; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix.m new file mode 100644 index 0000000000..581ca7aa2c --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix.m @@ -0,0 +1,35 @@ +function pix = get_pix(obj,varargin) +% read full or partial pixel information using properly initialized +% sqw file information +% Usage: +% assuming that file accessor is properly initiated +%>> pix = obj.get_pix(); -- try to read and return all pixels +% stored in the file (may fail due to insufficient +% memory) +% pix = obj.get_pix(npix_lo); +% pix = obj.get_pix(npix_lo,npix_high); -- +% -- try to read pixels from pixel N npix_lo +% to the end of pixels or from npix_lo +% to the pixel N npix_hi +% pix = obj.get_pix(___,'-raw_output') -- return raw pixel data and do not +% wrap pixels into PixelData class + +[obj,nothing_to_do,npix_lo,npix_hi,raw_output] = parse_get_pix_arguments_(obj,varargin{:}); +if nothing_to_do + if raw_output + pix = zeros(9,0); + else + pix = PixelDataBase.create(); + end + return +end +pix = get_pix_(obj,npix_lo,npix_hi); +if raw_output + return; +end +if isempty(pix) + pix = PixelDataBase.create(); +else + pix = PixelDataBase.create(pix); + pix.full_filename = obj.full_filename; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_at_indices.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_at_indices.m new file mode 100644 index 0000000000..6f8f1362b2 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_at_indices.m @@ -0,0 +1,34 @@ +function pix = get_pix_at_indices(obj, indices) +%GET_PIX_AT_INDICES Read pixels from file at the given pixel indices. +% The "indices" array must contain integers greater than 0 and be monotonically +% increasing. +% +if indices(end) > obj.npixels + error('HORACE:validate_ranges:invalid_argument', ... + ['Cannot retrieve given pixel indices. ' ... + 'Maximum index (%i) greater than number of pixels (%i).'], ... + indices(end), obj.npixels); +end + +if ~obj.is_activated('read') + obj = obj.activate('read'); +end + +PIXEL_SIZE = obj.pixel_size; % bytes +N_PIXEL_FIELDS = obj.num_pix_fields; % number of pix columns + +[read_sizes, seek_sizes] = get_read_and_seek_sizes(indices(:)'); + +% Position file reader at start of pixel array +do_fseek(obj.file_id_, obj.pix_position, 'bof'); + +% Assigning pixel blocks to a cell array and combining after appears to be +% marginally faster than pre-allocating a large array and assigning to it +blocks = cell(1, numel(read_sizes)); +for block_num = 1:numel(read_sizes) + do_fseek(obj.file_id_, seek_sizes(block_num)*PIXEL_SIZE, 'cof'); + read_size = [N_PIXEL_FIELDS, read_sizes(block_num)]; + blocks{block_num} = do_fread(obj.file_id_, read_size, 'float32'); +end +pix = [blocks{:}]; + diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_in_ranges.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_in_ranges.m new file mode 100644 index 0000000000..e66df2a895 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_in_ranges.m @@ -0,0 +1,120 @@ +function pix = get_pix_in_ranges(obj, pix_starts, pix_bl_sizes, ... + skip_validation,keep_precision) +%%GET_PIX_IN_RANGES read pixels in the specified ranges +% +% Read blocks of pixels, which start from pix_starts positions and occupy +% pix_bl_sizes sizes +% +% skip_validation -- if present and true, +% For performance reasons, there is no validation +% performed on input arguments, but the input arrays +% should have equal length and for all i we should have: +% +% pix_bl_sizes(i) > 0 +% pix_starts(i + 1) > pix_starts(i) +% pix_starts(i + 1) >= pix_starts(i)+pix_bl_sizes(i)+1 % not verified in +% any case but +% should be +% +% >> pix = get_pix_in_ranges([1, 12, 25], [6, 1, 3]) +% pix = +% [9x10] double array % pixels 1-6,12,25-27 +% +% Input: +% ------ +% pix_starts Indices of the starts of pixel ranges [Nx1 or 1xN array]. +% pix_bl_sizes The sizes of the blocks of pixels to read [Nx1 or 1xN array]. +% skip_validation Do not validate input array (optional, default = false) [bool] +% +% Output: +% ------- +% pix Raw pixel array [9xN double]. +% +skip_validation = exist('skip_validation', 'var') && skip_validation; +if ~skip_validation + validate_ranges(pix_starts, pix_bl_sizes); +end +if ~exist('keep_precision','var') + keep_precision = true; +end +if keep_precision + format = '*float32'; +else + format = 'float32'; +end + +%NUM_BYTES_IN_FLOAT = 4; +%PIXEL_SIZE = NUM_BYTES_IN_FLOAT*PixelDataBase.DEFAULT_NUM_PIX_FIELDS; % bytes + +% This decreases no. of calls needed to read data - big speed increase. +% Should be done elsewhere +%[pix_starts, pix_bl_sizes] = merge_adjacent_ranges(pix_starts, pix_bl_sizes); + +% TODO: verify if there are any performance benefits from commented +% code or the currently enabled code. (see +% [#686](https://github.com/pace-neutrons/Horace/issues/686)) + +% Position file reader at start of first block of pixels to read +%first_seek_pos = obj.pix_pos_ + (pix_starts(1) - 1)*PIXEL_SIZE; +%do_fseek(obj.file_id_, first_seek_pos, 'bof'); + +% blocks = cell(1, numel(pix_starts)); +% for i = 1:numel(pix_starts) +% seek_pos = obj.pix_pos_ + (pix_starts(i) - 1)*PIXEL_SIZE; +% do_fseek(obj.file_id_, seek_pos, 'bof'); +% num_pix_to_read = pix_ends(i) - pix_starts(i) + 1; +% +% read_size = [PixelDataBase.DEFAULT_NUM_PIX_FIELDS, num_pix_to_read]; +% %blocks{i} = fread(obj.file_id_, read_size, '*float32'); +% blocks{i} = fread(obj.file_id_,read_size, '*float32'); +% +% try +% seek_size = (pix_starts(i + 1) - pix_ends(i) - 1)*PIXEL_SIZE; +% catch ME +% if strcmpi(ME.identifier, 'MATLAB:badsubscript') +% % we've read in the final block, no more seeking to do +% break +% end +% end +% do_fseek(obj.file_id_, seek_size, 'cof'); +%end +%PIXEL_SIZE = obj.pixel_size; % bytes +N_PIXEL_FIELDS = obj.num_pix_fields; + +blocks = arrayfun(@(pix_start,bl_size)(read_block(obj, ... + N_PIXEL_FIELDS,pix_start,bl_size,format)),... + pix_starts,pix_bl_sizes,'UniformOutput',false); +pix = [blocks{:}]; + +end % function + +function block = read_block(obj,NUM_FIELS,pix_start,block_size,format) +seek_pos = obj.pix_position + (pix_start - 1)*obj.pixel_size; +do_fseek(obj.file_id_, seek_pos, 'bof'); +read_size = [NUM_FIELS,block_size]; +block = fread(obj.file_id_,read_size, format); +[f_message,n_err] = ferror(obj.file_id_); +if n_err ~=0 + error('HORACE:sqw_binfile_common:io_error',f_message) +end + +end +% ----------------------------------------------------------------------------- +function [starts, ends] = merge_adjacent_ranges(starts, ends) +%%MERGE_ADJACENT_RANGES merge ranges starting in 'starts' and ending in +% 'ends' that are adjacent +% e.g. +% >> starts = [1, 10, 45, 79, 86] +% >> ends = [5, 44, 67, 85, 90] +% >> merge_adjacent_ranges(starts, ends) +% ans = +% [1, 10, 79] +% [5, 67, 90] + +% Find indices where end of one range and start of next differ by one +offsets = starts(2:end) - ends(1:(end - 1)); +idxs_to_del = find(offsets == 1); +% Delete the indices such that those ranges are merged +starts(idxs_to_del + 1) = []; +ends(idxs_to_del) = []; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_metadata.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_metadata.m new file mode 100644 index 0000000000..24fafba04c --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_metadata.m @@ -0,0 +1,11 @@ +function [metadata,obj] = get_pix_metadata(obj,varargin) +% get full pixel metadata class +% +% +sqh = obj.sqw_holder; +obj.sqw_holder_ = []; +[obj,metadata] = obj.get_sqw_block('bl_pix_metadata',varargin{:}); +if ~isempty(sqh) + sqh.pix.metadata = metadata; + obj.sqw_holder_ = sqh; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_range.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_range.m new file mode 100644 index 0000000000..c4653af6d3 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_pix_range.m @@ -0,0 +1,11 @@ +function [pix_range,obj] = get_pix_range(obj,varargin) +% get [2x4] array of min/max ranges of the pixels contributing +% into an object. Empty for DND object +sqh = obj.sqw_holder; +obj.sqw_holder_ = []; +[obj,metadata] = obj.get_sqw_block('bl_pix_metadata',varargin{:}); +pix_range = metadata.pix_range; +if ~isempty(sqh) + sqh.pix.metadata = metadata; + obj.sqw_holder_ = sqh; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_raw_pix.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_raw_pix.m new file mode 100644 index 0000000000..582c28c5db --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_raw_pix.m @@ -0,0 +1,20 @@ +function pix = get_raw_pix(obj,varargin) +% read full or partial pixel information using propertly initalized +% sqw file information +% Usage: +% assuming that file accessor is properly initiated +%>> pix = obj.get_pix(); -- try to read and return all pixels +% stored in the file (may fail due to insufficient +% memory) +% pix = obj.get_pix(npix_lo); +% pix = obj.get_pix(npix_lo,npix_high); -- +% -- try to read pixels from pixel N npix_lo +% to the end of pixels or from npix_lo +% to the pixel N npix_hi + +[obj,nothing_to_do,npix_lo,npix_hi] = parse_get_pix_arguments_(obj,varargin{:}); +if nothing_to_do + pix = zeros(9,0); + return +end +pix = get_pix_(obj,npix_lo,npix_hi); diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_sample.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_sample.m new file mode 100644 index 0000000000..49c9baeeae --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_sample.m @@ -0,0 +1,20 @@ +function [samp,obj] = get_sample(obj,varargin) +% return samples container stored in file or some part of +% this container, containing particular sample +% Usage: +%>>inst = obj.get_sample() % Returns first unique sample, +% present in the file +%>>inst = obj.get_sample(number) % Returns sample with +% number, specified as input. +%>>inst = obj.get_sample('-all') % Returns +% unique object container with all samples stored +% in the file +%NOTE: +% The sample number corresponds to the header number. +% TODO: Clarify, % should it be run_id? +% +[argi,samp_number] = parse_get_inst_sample_arg_(obj,varargin{:}); +[samp,obj] = obj.get_block_data('bl_experiment_info_samples',argi{:}); +if ~isinf(samp_number) + samp = samp(samp_number); +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_sqw.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_sqw.m new file mode 100644 index 0000000000..b54127394b --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/get_sqw.m @@ -0,0 +1,143 @@ +function [sqw_object,varargout] = get_sqw(obj, varargin) +% Load an sqw object from sqw file on disk +% +% >> sqw_object = obj.get_sqw() +% >> sqw_object = obj.get_sqw(infile) +% >> sqw_object = obj.get_sqw('-h') +% >> sqw_object = obj.get_sqw('-his') +% >> sqw_object = obj.get_sqw('-keep_original') +% >> sqw_object = obj.get_sqw('-hisverbatim') +% >> sqw_object = obj.get_sqw('-nopix') +% >> sqw_object = obj.get_sqw('-file_backed') +% +% Input: +% -------- +% +% infile If present, the file name, or file identifier of an open file, +% from which to read data. If absent, the accessor (obj) should be +% initialized. +% +% Keyword Arguments: +% ------------------ +% Optional: Specify what parts of sqw to read and how to tread output +% +% '-h' - header block without instrument and sample information, and +% - data block fields: filename, filepath, title, alatt, angdeg,... +% uoffset,u_to_rlu,ulen,ulabel,iax,iint,pax,p,dax[,img_db_range] +% (If the file was written from a structure of type 'b' or 'b+', then +% img_db_range does not exist, and the output field will not be created) +% '-his' - header block in full i.e. without instrument and sample information, and +% - data block fields as for '-h' +% '-hverbatim' - Same as '-h' except that the file name as stored in the main_header and +% data sections are returned as stored, not constructed from the +% value of fopen(fid). This is needed in some applications where +% data is written back to the file with a few altered fields. +% '-hisverbatim' - Similarly as for '-his' +% '-nopix' Pixel information not read (only meaningful for sqw data type 'a') +% '-legacy' Return result in legacy format, e.g. 4 +% fields, namely: main_header, header, +% detpar and data +% '-noupgrade' or - if it is old file format, do not do +% '-norange' expensive calculations, necessary for +% upgrading file format to recent version +% '-file_backed' request the resulting sqw object to be file backed. +% +% +% Default: read all fields of whatever is the sqw data type contained in the file +% and return constructed sqw object +% +% Output: +% -------- +% fully formed sqw object +% +% +% Original author: T.G.Perring +% +opts = horace_binfile_interface.parse_get_sqw_args(varargin{:}); + + +sqw_skel = struct('main_header',[],'experiment_info',[],'detpar',[], ... + 'data',[],'pix',[]); + +if opts.head || opts.his + skip_blocks = {'bl__det_par','bl_data_nd_data',... + 'bl_pix_metadata','bl_pix_data_wrap'}; +else + skip_blocks = {'bl_pix_metadata','bl_pix_data_wrap'}; +end +[obj,sqw_skel] = obj.get_all_blocks(sqw_skel,'ignore_blocks',skip_blocks); + +if ~(opts.head || opts.his) + % detpar-independent inputs + sqw_skel.data = DnDBase.dnd(sqw_skel.data.metadata,sqw_skel.data.nd_data); + + % detpar inputs + detpar = sqw_skel.detpar; + if ~isempty(detpar) + n_instances = numel(sqw_skel.experiment_info.expdata); + sqw_skel.detpar = obj.convert_old_det_forms(detpar,n_instances); + + end + sqw_skel.experiment_info = Experiment(sqw_skel.detpar, ... + sqw_skel.experiment_info.instruments, ... + sqw_skel.experiment_info.samples,sqw_skel.experiment_info.expdata); +end + + +if opts.nopix + sqw_skel = rmfield(sqw_skel,'pix'); +else + if opts.noupgrade || opts.norange + argi = {'-norange'}; + else + argi = {}; + end + if opts.force_pix_location + if opts.file_backed + sqw_skel.pix = PixelDataFileBacked(obj,argi{:}); + else + sqw_skel.pix = PixelDataMemory(obj,argi{:}); + end + else + if opts.file_backed + argi = [argi(:),'-filebacked']; + end + sqw_skel.pix = PixelDataBase.create(obj,argi{:}); + end +end + +if opts.legacy + if nargout == 1 + sqw_object = sqw_skel; + elseif nargout == 2 + sqw_object = sqw_skel; + varargout{1} = obj; + else + sqw_object = sqw_skel.main_header; + varargout{1} = sqw_skel.experiment_info; + % (1) no tests for this block found, so cannot ascertain what the + % output arguments should be + % (2) although detpar has a meaning for proper sqw objects + % (dependent variable access to detector_arrays) it has no meaning + % in the context of a skeleton struct mirroring an sqw. The + % assignment of an empty value attempts to check this in the hope + % that something will eventually fail as a result. + % CM + varargout{2} = []; + varargout{3} = sqw_skel.data; + if isfield(sqw_skel,'pix') + varargout{4} = sqw_skel.pix; + else + varargout{4} = []; + end + end + return +elseif opts.head || opts.his + sqw_object = sqw_skel; + sqw_object.num_pixels = sqw_skel.pix.npix; +else + sqw_object = sqw(sqw_skel); +end +if nargout > 1 + varargout{1} = obj; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/copy_contents_.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/copy_contents_.m new file mode 100644 index 0000000000..884e7dee3e --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/copy_contents_.m @@ -0,0 +1,55 @@ +function [obj,missinig_fields] = copy_contents_(obj,other_obj,upgrade_range,varargin) +% Copy information, relevant to new file format from the old file format +% and update the information, which can be updated. + + +% Fix and freeze the position of the pixels data block +pix_data_block = obj.bat_.get_data_block('bl_pix_data_wrap'); +pix_data_block.pix_position = other_obj.pix_position; +pix_data_block.locked = true; % this can not be false, +% but some issue with old classes and outdated files can +% make if false. Let's do it explicitly -- here we lock +% pixel block +% this defines the block size +pix_data_block.npixels = other_obj.npixels; +% allocate space in new data block +obj.bat_ = obj.bat_.set_data_block(pix_data_block); +sqw_obj = other_obj.get_sqw('-norange'); +mh = sqw_obj.main_header; +if ~mh.creation_date_defined + sqw_obj.creation_date = datetime('now'); +end + +% build data range as if it has not been stored with +% majority of old data files +% +if ~sqw_obj.pix.is_range_valid() + %log_level = config_store.instance().get_value('hor_config','log_level'); + if upgrade_range + hc = hor_config; + log_level = hc.log_level; + if log_level > 0 + fprintf(2,['\n*** Recalculating actual data range missing in file %s:\n', ... + '*** This is one-off operation occurring during upgrade from file format version %d to file format version %d\n',... + '*** Do not interrupt this operation after the page count completion, as the input data file may become corrupted\n'],... + obj.full_filename,other_obj.faccess_version,obj.faccess_version); + end + [pix,unique_pix_id] = sqw_obj.pix.recalc_data_range(); + sqw_obj.pix = pix; + sqw_obj = update_pixels_run_id(sqw_obj,unique_pix_id); + end +end +% define number of contributing files, which is stored in sqw +% object header, but necessary for sqw_file_interface (not any +% more but historically to be able to recover headers) +obj.num_contrib_files_ = sqw_obj.main_header.nfiles; + +if upgrade_range + % clear disk location of all data blocks except the locked + obj.bat_ = obj.bat_.clear_unlocked_blocks(); +end +% as pix data block position already allocated, +obj.bat_ = obj.bat_.place_undocked_blocks(sqw_obj,true); + +obj.sqw_holder_ = sqw_obj; +missinig_fields = 'data_in_memory_write_result'; diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/get_pix_.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/get_pix_.m new file mode 100644 index 0000000000..e5258e0634 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/get_pix_.m @@ -0,0 +1,65 @@ +function pix = get_pix_(obj,npix_lo,npix_hi) +% read full or partial pixel information using properly initialized +% sqw file information +% Usage: +% assuming that file accessor is properly initiated +%>> pix = obj.get_pix(); -- try to read and return all pixels +% stored in the file (may fail due to insufficient +% memory) +% pix = obj.get_pix(npix_lo); +% pix = obj.get_pix(npix_lo,npix_high); -- +% -- try to read pixels from pixel N npix_lo +% to the end of pixels or from npix_lo +% to the pixel N npix_hi + +% +% CODE HERE EXPECTS pixel value to be 4 bytes long +% +if ischar(obj.num_contrib_files) + error('HORACE:sqw_binfile_common:runtime_error',... + 'get_pix method called from un-initialized loader') +end + +if ~obj.is_activated('read') + obj = obj.activate('read'); +end +pix_width = obj.num_pix_fields; +npix_tot = obj.npixels; +if isempty(npix_tot) % dnd object + pix = zeros(pix_width,0); + return +end + + +% *** T.G.Perring 5 Sep 2018: Change code so that npix_lo=npix_hi+1 is allowed; this will result in no +% pixels being read +if npix_lo> npix_hi+1 % replaces the following line + %if npix_lo> npix_hi + error('HORACE:sqw_binfile_common:invalid_argument',... + 'requested number of min pixel %d is bigger then number of max pixel: %d',... + npix_lo,npix_lo); +end + +stride = (npix_lo-1)*pix_width*4; +size = npix_hi-npix_lo+1; + +try + do_fseek(obj.file_id_,obj.pix_position+stride,'bof'); +catch ME + exc = MException('HORACE:sqw_binfile_common:io_error',... + 'get_pix: Can not move to the beginning of the pixel block requested'); + throw(exc.addCause(ME)) +end + + +if size>0 + pix = fread(obj.file_id_,[pix_width,size],'float32'); + [mess,res] = ferror(obj.file_id_); + if res ~= 0 + error('HORACE:sqw_binfile_common:io_error',... + 'get_pix: Error reading the pixel block requested: %s',mess); + end +else + % *** T.G.Perring 5 Sep 2018: allow for size=0 + pix = zeros(pix_width,0); +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/parse_get_inst_sample_arg_.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/parse_get_inst_sample_arg_.m new file mode 100644 index 0000000000..37ed8ca984 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/parse_get_inst_sample_arg_.m @@ -0,0 +1,40 @@ +function [argi,instr_number] = parse_get_inst_sample_arg_(obj,varargin) +% Parse get_instrument or get_sample input parameters and return +% the parameters, defining the treatment of the instrument/sample container +% +% ignores and returns the other parameters, assuming that those parameters define +% sqw source to read +% Outputs: +% argi -- the cellarray of parameters, not recognized as input of get_sample/instrument function +% instr_number +% -- number of instrument or sample to read or array of such numbers. Inf if one needs to read +% all instruments or samples + +if isempty(varargin) + argi = {}; + instr_number = 1; + return; +end + +is_num = cellfun(@(x)isnumeric(x),varargin); +if any(is_num) + instr_number = [varargin{is_num}]; + if any(instr_number>obj.num_contrib_files) + invalid = instr_number>obj.num_contrib_files; + error('HORACE:sqw:invalid_argument', ... + 'Number(s) of the requested component(s): %s exceeds the total number of contributed runs: %d', ... + disp2str(instr_number(invalid)),obj.num_contrib_files); + end + argi = varargin(~is_num); + if isempty(argi) + return + end +else + argi = varargin; + instr_number = 1; +end +is_par = cellfun(@(x)(ischar(x)||isstring(x))&&strcmp(x,'-all'),argi); +if any(is_par) + instr_number = inf; + argi = argi(~is_par); +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/parse_get_pix_arguments_.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/parse_get_pix_arguments_.m new file mode 100644 index 0000000000..f3d1b528c8 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/private/parse_get_pix_arguments_.m @@ -0,0 +1,52 @@ +function [obj,nothing_to_do,npix_lo,npix_hi,raw_output] = parse_get_pix_arguments_(obj,varargin) +% + + +if ischar(obj.num_contrib_files) + error('HORACE:faccess_sqw_v4_1:runtime_error',... + 'get_pix method called from un-initialized loader') +end + +[ok,mess,raw_output,argi] = parse_char_options(varargin,'-raw_output'); +if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',mess) +end + + +if ~obj.is_activated('read') + obj = obj.activate('read'); +end +nothing_to_do = false; + +npix_tot = obj.npixels; +if isempty(npix_tot) % dnd object + nothing_to_do = true; + npix_lo=0; + npix_hi=0; + return +end + +nargi = numel(argi); +if nargi >0 + npix_lo = argi{1}; + if nargi > 1 + npix_hi = argi{2}; + else + npix_hi = npix_tot; + end +else + npix_lo = 1; + npix_hi = npix_tot; +end + +if npix_lo < 1 + warning('HORACE:faccess_sqw_v4_1:invalid_argument',... + 'get_pix: min pixel number requested smaller than 1, using 1') + npix_lo = 1; +end +if npix_hi > npix_tot + warning('HORACE:faccess_sqw_v4_1:invalid_argument',... + ['Max number of pixels requested is bigger than the total number of pixels: %d\n',... + ' Max number of pixels to read is reset to max number of pixels available'],npix_tot); + npix_hi = npix_tot; +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_det_info.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_det_info.m new file mode 100644 index 0000000000..26e00ca0af --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_det_info.m @@ -0,0 +1,10 @@ +function obj = put_det_info(obj,varargin) +% Store information about sqw object detectors +% +% the main sqw data to take detpar from are either attached to +% sqw object contained in obj.sqw_holder property or provided +% as first input parameter +% +% + +obj = obj.put_block_data('bl__detpar',varargin{:}); \ No newline at end of file diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_headers.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_headers.m new file mode 100644 index 0000000000..8e136d1174 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_headers.m @@ -0,0 +1,43 @@ +function obj = put_headers(obj,varargin) +% put or replace whole experiment info in a binary sqw file v4_1 + +% +%Usage: +%>>obj.put_header(); +%>>obj.put_headers(header_num); +%>>obj.put_headers('-update'); %-- redundant property, not used any more +%>>obj.put_headers('-no_sampinst'); % do not store instrument or sample + +%>>obj.put_header(___,new_source_for_update) +% where new_source_for_update could be modified sqw object or Experiment +% +% To work correctly, the file accessor have to be initialized by correct sqw v4_1 file +% +% Theoretically, it can be initialized on the fly if the input file is +% provided but this mode have not been tested. +% + +% Ignore input arguments, possibly left from previous interface +[ok,mess,~,no_samp_inst,argi] = parse_char_options(varargin,{'-update','-no_sampinst'}); +if ~ok + error('HORACE:put_headers:invalid_argument',mess); +end +numarg = arrayfun(@(x)isnumeric(x),argi); +if any(numarg) + argi = argi(~numarg); +end +head_provided = cellfun(@(x)(isa(x,'Experiment')||isa(x,'sqw')||is_sqw_struct(x)), ... + argi); +% avoid side effects from subsequent calls +keep_holder = obj.sqw_holder_; +if any(head_provided) + obj.sqw_holder_ = argi{head_provided}; + argi = argi(~head_provided); +end +% +if ~no_samp_inst + obj = obj.put_block_data('bl_experiment_info_instruments',argi{:}); + obj = obj.put_block_data('bl_experiment_info_samples'); +end +obj = obj.put_block_data('bl_experiment_info_expdata'); +obj.sqw_holder_ = keep_holder; diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_instruments.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_instruments.m new file mode 100644 index 0000000000..0623f0d0dc --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_instruments.m @@ -0,0 +1,7 @@ +function obj = put_instruments(obj,varargin) +% Store instruments container to the binary file +% +% the main sqw data with instruments are either attached +% to obj.sqw_holder or provided as input parameters. +% +obj = obj.put_block_data('bl_experiment_info_instruments',varargin{:}); \ No newline at end of file diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_main_header.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_main_header.m new file mode 100644 index 0000000000..1ac21d6f1c --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_main_header.m @@ -0,0 +1,18 @@ +function obj = put_main_header(obj,varargin) +% Save or replace main sqw header into properly initialized +% binary sqw file +%Usage: +%>>obj.put_main_header(); -- store sqw obect main_header information +% in sqw binary file. +% +% Optional: operations are not well tested yet. +%>>obj.put_main_header('-update'); % redundant opiton not used any more +% +%>>obj = obj.put_header(sqw_obj); +% -- stores header for sqw object, provided as +% input. +% +% the main sqw data are either attached to sqw_hanle or provided as +% input parameters -- not well tested yet. +% +obj = obj.put_block_data('bl__main_header',varargin{:}); \ No newline at end of file diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_num_pixels.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_num_pixels.m new file mode 100644 index 0000000000..35034b6485 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_num_pixels.m @@ -0,0 +1,19 @@ +function obj = put_num_pixels(obj, num_pixels) +%PUT_NUM_PIXELS Store num_pixels in the appropriate position of the pixel data +%block. +% +% Inputs: +% obj -- initialized f-accessor object, containing proper block allocation +% table with defined pixels block (containing correct number of pixels +% to be in the target file and number of pixel rows (9, nothing else was tested)) +% +% num_pixels -- Number of pixels to set +% + +pdb = obj.bat_.blocks_list{end}; +pdb.npixels = num_pixels; +pdb.put_data_header(obj.file_id_); + +obj.bat_.blocks_list{end} = pdb; +obj.bat_.put_bat(obj.file_id_); +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_pix.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_pix.m new file mode 100644 index 0000000000..3afd711175 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_pix.m @@ -0,0 +1,188 @@ +function obj = put_pix(obj,varargin) +% Save or replace pixels information within binary sqw file +% +%Usage: +%>>obj = obj.put_pix(); +%>>obj = obj.put_pix(sqw_obj); +%>>obj = obj.put_pix(pix_obj); +% +% Optional: +% '-update' -- update existing data rather then (over)writing new file +% (deprecated, ignored, update occurs automatically if proper file is +% provided) +% '-nopix' -- do not write pixels +% '-reserve' -- if applied together with nopix, pixel information is not +% written but the space dedicated for pixels is filled in with zeros. +% If -nopix is not used, the option is ignored. +% '-hold_pix_place' +% -- if present, arrange writing pix_metadata and place for pixel +% data but do not write pixels themselves +% +[ok,mess,~,nopix,reserve,hold_pix_place,argi] = parse_char_options(varargin,{'-update','-nopix','-reserve','-hold_pix_place'}); +if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument',... + 'faccess_sqw_v4_1-put_pix: %s',mess); +end + +if ~obj.is_activated('write') + obj = obj.activate('write'); +end + + +if ~isempty(argi) % parse inputs which may or may not contain any + % combination of 3 following input parameters: + sqw_pos = cellfun(@(x) isa(x,'sqw') || isstruct(x), argi); + numeric_pos = cellfun(@(x) isnumeric(x) && ~isempty(x), argi); + + unknown = ~(sqw_pos|numeric_pos); + if any(unknown) + if isempty(argi{1}) + disp('unknown empty input '); + else + disp(['unknown input: ',argi{unknown}]); + end + error('SQW_BINFILE_COMMON:invalid_argument',... + 'put_pixel: the routine accepts only sqw object and/or low and high numbers for pixels to save'); + end + + if any(sqw_pos) + input_obj = argi{sqw_pos}; + else + input_obj = []; + end + + if ~isempty(input_obj) + if isa(input_obj,'sqw') + input_obj = input_obj.pix; + end + elseif isempty(numeric_pos) + input_obj = argi{numeric_pos}; + else + input_obj = obj.sqw_holder_.pix; + end + +else + input_obj = obj.sqw_holder_.pix; +end + +if isnumeric(input_obj) + num_pixels = size(input_obj,2); +else + num_pixels = input_obj.num_pixels; +end + + +if ~(isa(input_obj,'MultipixBase') || (~isnumeric(input_obj) && input_obj.is_filebacked)) + obj = obj.put_sqw_block('bl_pix_metadata',input_obj); + obj = obj.put_sqw_block('bl_pix_data_wrap',input_obj); + return; +end +metadata = input_obj.metadata; +if metadata.is_corrected + % Data will be written aligned so metadata should also state that + % data are aligned. metadata can not grow here, as it will try to place + % them behind pixels which have not been written yet. And they should + % not grow. + metadata.alignment_matr = eye(3); + obj = obj.put_sqw_block('bl_pix_metadata',metadata); + % Get pixel data block position to place the block in new place + % as pixel_metadata probably have changed their size + % MATLAB SPECIFIC issue, as it can not write behind end of file unless + % you start writing at the last +1 byte position. + bat = obj.bat_; + pdb = bat.blocks_list{end}; + fseek(obj.file_id_,0,'eof'); + real_eof = ftell(obj.file_id_); + % block position counted from 0 + if pdb.position> real_eof % change + % position of pixel data block calculated earlier because MATLAB + % can not write after current EOF. + for i=1:bat.n_blocks + bat.blocks_list{i}.locked = true; + end + pdb.locked = false; + bat.blocks_list{end} = pdb; + bat = bat.clear_unlocked_blocks(); + bat = bat.place_undocked_blocks(input_obj,false); + bat = bat.put_bat(obj.file_id_); + for i=1:bat.n_blocks-1 + bat.blocks_list{i}.locked = false; + end + pdb = bat.blocks_list{end}; + % lock pixel data block in-place not to move it in a future + pdb.locked = true; + bat.blocks_list{end} = pdb; + obj.bat_ = bat; + end +else + obj = obj.put_sqw_block('bl_pix_metadata',metadata); + % get block responsible for writing pix_data + pdb = obj.bat_.blocks_list{end}; +end +if nopix && ~reserve + pdb.npix = 0; +end + +% write pixel data block information; number of dimensions and number of pixels +pdb.put_data_header(obj.file_id_); +if hold_pix_place + return; +end + + +% write pixels themselves +try + do_fseek(obj.file_id_,obj.pix_position,'bof'); +catch ME + exc = MException('HORACE:put_pix:io_error',... + 'Error moving to the start of the pixels info'); + throw(exc.addCause(ME)) +end + +if nopix && reserve + % size of buffer to hold pixel information + block_size= config_store.instance().get_value('hor_config','mem_chunk_size'); + + if block_size >= num_pixels + res_data = single(zeros(9,num_pixels)); + fwrite(obj.file_id_,res_data,'float32'); + else + written = 0; + res_data = single(zeros(9,block_size)); + while written < num_pixels + fwrite(obj.file_id_,res_data,'float32'); + written = written+block_size; + if written+block_size > num_pixels + block_size = num_pixels-written; + res_data = single(zeros(9,block_size)); + end + end + end + clear res_data; + return; +end + +if num_pixels == 0 + return % nothing to do. +end + +if isa(input_obj,'PixelDataBase') % write pixels stored in other file + n_pages = input_obj.num_pages; + for i = 1:n_pages + input_obj.page_num = i; + pix_data = input_obj.data; + + try + fwrite(obj.file_id_, single(pix_data), 'float32'); + obj.check_write_error(obj.file_id_); + catch ME + exc = MException('HORACE:put_pix:io_error',... + sprintf('Error writing input pixels for page N%d out of %d',i,n_pages)); + throw(exc.addCause(ME)) + end + + end +else % pixel data array. As it is in memory, write it as a sigle block + fwrite(obj.file_id_, single(input_obj), 'float32'); + obj.check_write_error(obj.file_id_); +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_pix_metadata.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_pix_metadata.m new file mode 100644 index 0000000000..83b7ed02fd --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_pix_metadata.m @@ -0,0 +1,9 @@ +function obj = put_pix_metadata(obj,pix_class) +%PUT_PIX_METADATA store pixel metadata containing in pix_class using fully +% instance of file-accessor +if ~(isa(pix_class,'PixelDataBase') || isa(pix_class,'pix_metadata') || isa(pix_class,'sqw')) + error('HORACE:faccess_sqw_v4_1:invalid_argument',... + 'This method accepts only class, containing PixelData or pix_metadata as input. In fact input class is: %s',... + class(pix_class)); +end +obj = obj.put_sqw_block('bl_pix_metadata',pix_class); diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_raw_pix.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_raw_pix.m new file mode 100644 index 0000000000..d6ffbb5dd3 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_raw_pix.m @@ -0,0 +1,58 @@ +function obj= put_raw_pix(obj,pix_data,pix_idx,varargin) +%PUT_RAW_PIX Store pixel data in the specified position of the pixel data +%block. +% +% Inputs: +% obj -- initialized f-accessor object, containing proper block allocation +% table with defined pixels block (containing correct number of pixels +% to be in the target file and number of pixel rows (9, nothing else was tested)) +% +% pix_data +% -- array of pixel data. Normally 9xNpix but can be different if different +% pixel format is selected (not tested). +% pix_idx +% -- the position in the pixel array to put the data block in. Has to point +% to the position after last pixel written +% or inside the pixel array (for overwriting existing pixels on disk); +% +% Method used by file-accessor for modifying or writing new block of pixel +% data in the binary data file or in a loop writing the pixels in a new binary file. +if nargin <3 + pix_idx = 1; +end +if size(pix_data,2) == 0 + return; +end + +if ~obj.is_activated('write') + obj = obj.activate('write'); +end +if pix_idx == 1 + % this will work properly if number of pixels is known initially and + % stored in BAT, i.e. during overwriting. If you write pages one after + % another appending to file, this will not write correct number of + % pixels. + % Do not forget to update number of pixels (put_num_pixels) after using + % this method in algorithm, which changes number of pixels. + pdb = obj.bat_.blocks_list{end}; + pdb.put_data_header(obj.file_id_); +end + +pos = obj.pix_position + (pix_idx-1)*obj.get_filepix_size; + +try + do_fseek(obj.file_id_,pos,'bof'); +catch ME + exc = MException('HORACE:put_raw_pix:io_error',... + 'Error moving to the start of the pixels block data at index: %d',pix_idx); + throw(exc.addCause(ME)) +end + +try + fwrite(obj.file_id_, single(pix_data), 'float32'); + obj.check_write_error(obj.file_id_); +catch ME + exc = MException('HORACE:put_raw_pix:io_error',... + 'Error writing input pixels array indices: %d',pix_idx); + throw(exc.addCause(ME)) +end diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_samples.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_samples.m new file mode 100644 index 0000000000..c22a7b3299 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_samples.m @@ -0,0 +1,6 @@ +function obj = put_samples(obj,varargin) +% Store samples info to the binary datafiles +% +% the main sqw data are either attached to sqw_hanle or provided as input parameters +% +obj = obj.put_block_data('bl_experiment_info_samples',varargin{:}); \ No newline at end of file diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_sqw.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_sqw.m new file mode 100644 index 0000000000..995bed3930 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/put_sqw.m @@ -0,0 +1,103 @@ +function obj = put_sqw(obj,varargin) +% Save sqw data into new binary file or fully overwrite an existing file +% +% Usage: +% obj = obj.put_sqw() Put sqw object which have been already +% initialized with this file-accessor and is +% assigned to obj.sqw_holder; +% obj = obj.put_sqw(sqw_obj) Put sqw object provided as input of the +% method. The file to put object should be +% already set. +% obj = obj.put_sqw(sqw_obj,filename) +% Put sqw object provided as input of the +% method to the file provided as second parameter. +% +% Options: +% '-update' -- write to existing sqw file. Currently deprecated and does nothing. +% +% TODO: Check if existing file contains sqw object, +% as currently such file is silently overwritten. +% '-verbatim' -- do not change filenames and file-path-es, stored in +% current sqw object headers to the name and path +% of the current file to write data into +% '-nopix' -- do not store pixel array within the sqw object. +% Write sqw object with empty pixels record +% '-hold_pix_place' +% -- do not store pixels array within the sqw object but +% write all pixel metadata and prepare pixel data block +% for writing +% +% + +[ok,mess,~,verbatim,nopix,reserve,hold_pix,argi]=parse_char_options(varargin, ... + {'-update','-verbatim','-nopix','-reserve','-hold_pix_place'}); +if ~ok + error('HORACE:faccess_sqw_v4_1:invalid_argument', ... + mess); +end + + +if ~isempty(argi) + is_sqw = cellfun(@(x) isa(x,'sqw'), argi); + if any(is_sqw) + if sum(is_sqw) > 1 + error('HORACE:sqw_binfile_common:invalid_argument',... + 'only one sqw object can be provided as input for put_sqw'); + end + obj.sqw_holder = argi{is_sqw}; + argi = argi(~is_sqw); + end +end + +if ~obj.sqw_holder.main_header.creation_date_defined ||... + isempty(obj.sqw_holder.main_header.filename) + cd = datetime('now'); + sqw_obj = obj.sqw_holder; + sqw_obj.creation_date= cd; + if ~verbatim + sqw_obj.full_filename = obj.full_filename; + verbatim = true; % disable repeated if ~verbatim below + end + obj.sqw_holder = sqw_obj; +end + +if ~(obj.sqw_holder.pix.is_filebacked || nopix) + obj = obj.put_all_blocks(); + return; +end + +if ~verbatim + sqw_obj = obj.sqw_holder; + sqw_obj.full_filename =obj.full_filename; + obj.sqw_holder = sqw_obj; +end + +if nopix && ~(reserve||hold_pix) % Modify writeable object to contain no pixels + sqw_obj = obj.sqw_holder; + old_pix = sqw_obj.pix; + sqw_obj.pix = PixelDataMemory(); + if ~verbatim + sqw_obj.full_filename = obj.full_filename; + end + obj.sqw_holder = sqw_obj; + obj = obj.put_all_blocks(); + sqw_obj.pix = old_pix; + obj.sqw_holder = sqw_obj; + return; +end + +if reserve + argi = [argi(:),'-reserve']; +end +if hold_pix + argi = [argi(:),'-hold_pix_place']; +end + +if nopix + argi = [argi(:),'-nopix']; +end + +obj = obj.put_all_blocks('ignore_blocks',{'bl_pix_metadata','bl_pix_data_wrap'}); + + +obj=obj.put_pix(argi{:}); diff --git a/horace_core/sqw/file_io/@faccess_sqw_v4_1/update_sqw_keep_pix.m b/horace_core/sqw/file_io/@faccess_sqw_v4_1/update_sqw_keep_pix.m new file mode 100644 index 0000000000..d3bb075b24 --- /dev/null +++ b/horace_core/sqw/file_io/@faccess_sqw_v4_1/update_sqw_keep_pix.m @@ -0,0 +1,16 @@ +function obj = update_sqw_keep_pix(obj) +% Given initialized sqw object in memory, initialized BAT and sqw file +% written in old file format, write everything in memory to proper places +% in file keeping pixels data on their original place +% +% Usage: +% obj = obj.update_sqw_keep_pix() +% Put sqw object which have been already initialized at sqw holder + + + +obj = obj.put_all_blocks('ignore_blocks','bl_pix_data_wrap'); +% get the block responsible for pixel position +pix_block = obj.bat_.blocks_list{end}; +% put again the information about pixel block size and shape +pix_block.put_data_header(obj.file_id_); \ No newline at end of file diff --git a/horace_core/sqw/file_io/@sqw_formats_factory/sqw_formats_factory.m b/horace_core/sqw/file_io/@sqw_formats_factory/sqw_formats_factory.m index 2dfb4ebb05..0fdec567ac 100644 --- a/horace_core/sqw/file_io/@sqw_formats_factory/sqw_formats_factory.m +++ b/horace_core/sqw/file_io/@sqw_formats_factory/sqw_formats_factory.m @@ -36,6 +36,7 @@ % Add all new file readers which inherit from sqw_file_interface and horace_binfile_interface % to this list in the order of expected frequency of their appearance. supported_accessors_ = { ... + faccess_sqw_v4_1(),... faccess_sqw_v4(),... faccess_dnd_v4(),... faccess_sqw_v3_3(), ... @@ -60,7 +61,7 @@ % array. % number of loader in the list of loaders above to use for saving % class, defined by written_types_ string. - access_to_type_ind_ = {2,1,1,2,2,2,2,2,2}; + access_to_type_ind_ = {3,1,1,3,3,3,3,3,3}; types_map_ ; end properties(Dependent)