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)