PageRenderTime 31ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/examples/toolboxes/fieldtrip/external/ctf/readCTFds.m

http://brainstream.googlecode.com/
MATLAB | 1003 lines | 660 code | 123 blank | 220 comment | 96 complexity | c66da86708bd494b9a41bf98e5bf91d7 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.0, GPL-3.0, GPL-2.0, LGPL-2.1, LGPL-3.0, BSD-2-Clause
  1. function ds=readCTFds(datasetname)
  2. % ************************************************************************
  3. %
  4. % This program is provided to users of CTF MEG systems as a courtesy only.
  5. % It's operation has not been verified for clinical use.
  6. % Please do not redistribute it without permission from CTF Systems Inc.
  7. %
  8. % ************************************************************************
  9. % readCTFds opens a CTF data set and creates a MATLAB structure with the information
  10. % in the .res4, .infods, .newds, .acq, .hc, .hist, .EEG, bad.segments, BadChannels,
  11. % ClassFile.cls andMarkerFile.mrk files. It confirms the existence of a .meg4 file
  12. % with the correct size.
  13. % See document CTF MEG File Formats, PN900-0088 for a description of the formats of
  14. % dataset files.
  15. % Author : Harold Wilson
  16. % ***************** Revisions and bug fixes **************************************
  17. % Version 1.3 4 October 2007
  18. % 1. readCTFds v1.2 failed when run in MATLAB 7.2, but ran correctly in
  19. % MATLAB 6.5 and 7.3. The failure occurred when calls to fscanf with a
  20. % final '\n' in the format string were followed by fgetl. In MATLAB 6.5 and 7.3,
  21. % this returns the next line of a text file, but in MATLAB 7.2, this
  22. % returns an empty charatcer string.
  23. % Changes were made to subprograms
  24. % - readHc,
  25. % - readClassFile
  26. % - readMarkerFile.
  27. % 2. In v1.1 and 1.2, if any of the head coil positions exceeded 100 cm, readHc
  28. % reported an error and returned hc=struct([]). In v1.3, is reports the error,
  29. % and returns the erroneaous values.
  30. % Version 1.2: 24 April 2007
  31. % - readHc modified to read fMEG .hc files.
  32. % - readCPersist modified to handle extra bytes that appear in some .acq files.
  33. % Version 1.1: 13 April 2007
  34. % readCTFds is modified to handle cases where the data files exceed a total of 2^31-8
  35. % bytes. In these cases files .1_meg4,.2_meg4,... appear in the dataset.
  36. % ***********************************************************************************
  37. % Inputs : datasetname : Complete name of the data set directory. Includes the complete
  38. % path and the .ds extension.
  39. % Output: ds : A structure that gives data set parameters.
  40. % Function calls included in this listing:
  41. % - readRes4
  42. % - readHc
  43. % - readEEG
  44. % - readMarkerFile
  45. % - readClassFile
  46. % - readBadSegments
  47. % - readVirtualChannels
  48. % External functions: - readCPersist Reads infods and acq files.
  49. % - The data directory is datasetname.
  50. % - path is the complete path to the directory including the last file delimiter.
  51. % - baseName is the directory name, less the last file extension.
  52. % - datasetname=[path,baseName,'.ds'];
  53. persistent printWarning multipleMeg4Files
  54. if nargin==0 & nargout==0 % Print a version number
  55. fprintf(['\treadCTFds: Version 1.3 4 October 2007 ',...
  56. 'Reads v4.1 and v4.2 CTF data sets including fMEG .hc files.\n',...
  57. '\tCall: ds=readCTFds(datasetname)\n',...
  58. '\t\tdatasetname = Name of the dataset including the path.\n',...
  59. '\t\tds = Structure containing all dataset information except for data in ',...
  60. 'the .meg4 file.\n\n']);
  61. return
  62. end
  63. MAX_COILS=8;
  64. MAX_BALANCING=50; % max. no. of balancing coefficients
  65. SENSOR_LABEL=31; % No. of characters in sensor labels in the balancing
  66. % coefficient tables
  67. len_sensor_name=32;
  68. % length of sensor coefficient records in bytes. See listing for MEGDefs.h
  69. %senres_lenrec=len_sensor_name+8+2+MAX_BALANCING*SENSOR_LABEL+8*MAX_BALANCING;
  70. % Allowed 8-byte headers for res4 and meg4 files.
  71. res4Headers=strvcat(['MEG41RS',char(0)],['MEG42RS',char(0)]);
  72. meg4Headers=strvcat(['MEG41CP',char(0)],['MEG42CP',char(0)]);
  73. delim=filesep;
  74. ds=struct([]);
  75. % Check that the data set exists.
  76. if exist('datasetname')~=1
  77. fprintf('\nreadCTFds: No input datasetname specified.\n\n');
  78. return
  79. elseif ~ischar(datasetname) | isempty(datasetname) | size(datasetname,1)~=1
  80. fprintf('\nreadCTFds: Dataset name is not a string, or is empty, or is an array.\n\n');
  81. whos datasetname
  82. return
  83. else
  84. % Separate datasetname into a path and the baseName and add extension .ds.
  85. datasetname=deblank(datasetname);
  86. ksep=max([0 findstr(datasetname,delim)]);
  87. baseName=datasetname((ksep+1):length(datasetname));
  88. path=datasetname(1:ksep); % String path is terminated by the file delimiter (or path=[]).
  89. % Remove the last .ds from baseName.
  90. kdot=max(findstr(baseName,'.ds'));
  91. if kdot==(length(baseName)-2)
  92. baseName=baseName(1:(max(kdot)-1));
  93. else
  94. datasetname=[datasetname,'.ds'];
  95. end
  96. clear ksep kdot;
  97. if exist(datasetname)~=7
  98. fprintf('\nreadCTFds: Cannot find dataset %s.\n\n',datasetname);
  99. return
  100. end
  101. end
  102. % Check that the res4 and meg4 files exist.
  103. res4File=[datasetname,delim,baseName,'.res4'];
  104. meg4File=[datasetname,delim,baseName,'.meg4'];
  105. if exist(res4File)~=2 | exist(meg4File)~=2
  106. fprintf('readCTFds: In directory %s, cannot find .res4 and/or .meg4 files.\n',...
  107. datasetname);
  108. return
  109. end
  110. % Check the headers on .meg4, .1_meg4, ...
  111. qFile=0;
  112. meg4Ext='.meg4';
  113. meg4Size=[];
  114. while 1
  115. if qFile>0;
  116. meg4Ext=['.',int2str(qFile),'_meg4'];
  117. meg4File=[datasetname,delim,baseName,meg4Ext];
  118. end
  119. if qFile>1 & isempty(multipleMeg4Files)
  120. fprintf('readCTFds: This dataset has multiple meg4 files.\n');
  121. multipleMeg4Files=1;
  122. end
  123. fid=fopen(meg4File,'r','ieee-be');
  124. if fid<=0;break;end
  125. D=dir([datasetname,delim,baseName,meg4Ext]);
  126. meg4Size=[meg4Size D.bytes];
  127. meg4Header=char(fread(fid,8,'char')');
  128. fclose(fid);
  129. if isempty(strmatch(meg4Header,meg4Headers,'exact'))
  130. fprintf('\nreadCTFds: %s file header=%s Valid header options:',meg4Ext,meg4Header);
  131. for k=1:size(meg4Headers,1);fprintf(' %s',meg4Headers(k,:));end
  132. fprintf('\n\n');
  133. ds=struct([]);
  134. return
  135. end
  136. qFile=qFile+1;
  137. end
  138. Nmeg4=length(meg4Size);
  139. clear D fid qFile;
  140. % Add baseName and path to structure ds.
  141. ds=struct('baseName',baseName,'path',path);
  142. % Read the res4 file
  143. ds.res4=readRes4(res4File,res4Headers,...
  144. MAX_COILS,MAX_BALANCING,SENSOR_LABEL,len_sensor_name);
  145. if isempty(ds.res4);ds=struct([]);return;end
  146. dataBytes=4*ds.res4.no_trials*ds.res4.no_channels*ds.res4.no_samples;
  147. % Assemble ds.meg4.
  148. ds.meg4.fileSize=sum(meg4Size);
  149. ds.meg4.header=meg4Header;
  150. clear meg4Header meg4Size;
  151. % Does the size of the .meg4 file match the size specified by the .res4 file?
  152. if ds.meg4.fileSize~=8*Nmeg4+dataBytes
  153. fprintf(['\nreadCTFds: Data set error : size of meg4 file(s)\n\t\t',...
  154. '%10d bytes (from dir command)\n'],ds.meg4.fileSize);
  155. fprintf('\t\t%10d bytes (from res4 file)\n\n',8*Nmeg4+dataBytes);
  156. return
  157. end
  158. if isempty(printWarning)
  159. fprintf(['\nreadCTFds: You are reading CTF data for use with a software-application tool\n',...
  160. '\tthat is not manufactured by VSM MedTech Ltd. and has not received marketing\n',...
  161. '\tclearance for clinical applications. If CTF MEG data are processed by this tool,\n',...
  162. '\tthey should not be later employed for clinical and/or diagnostic purposes.\n\n']);
  163. printWarning=1;
  164. end
  165. % .infods file
  166. if exist([datasetname,delim,baseName,'.infods'])==2
  167. ds.infods=readCPersist([datasetname,delim,baseName,'.infods']);
  168. end
  169. % .newds file
  170. if exist([datasetname,delim,baseName,'.newds'])==2
  171. fid=fopen([datasetname,delim,baseName,'.newds'],'r','ieee-be');
  172. ds.newds=char(fread(fid,'uint8'))';
  173. fclose(fid);
  174. clear fid;
  175. end
  176. % .acq file
  177. if exist([datasetname,delim,baseName,'.acq'])==2
  178. ds.acq=readCPersist([datasetname,delim,baseName,'.acq']);
  179. end
  180. % .hist file
  181. if exist([datasetname,delim,baseName,'.hist'])==2
  182. fid=fopen([datasetname,delim,baseName,'.hist'],'r','ieee-be');
  183. ds.hist=char(fread(fid,'uint8'))';
  184. fclose(fid);
  185. end
  186. % .hc file
  187. if exist([datasetname,delim,baseName,'.hc'])==2
  188. ds.hc=readHc([datasetname,delim,baseName,'.hc']);
  189. end
  190. % .eeg file
  191. if exist([datasetname,delim,baseName,'.eeg'])==2
  192. ds.eeg=readEEG([datasetname,delim,baseName,'.eeg']);
  193. if isempty(ds.eeg);ds=rmfield(ds,'eeg');end
  194. end
  195. % marker file
  196. if exist([datasetname,delim,'MarkerFile.mrk'])==2
  197. ds.mrk=readMarkerFile([datasetname,delim,'MarkerFile.mrk']);
  198. end
  199. % ClassFile
  200. if exist([datasetname,delim,'ClassFile.cls'])==2
  201. ds.TrialClass=readClassFile([datasetname,delim,'ClassFile.cls']);
  202. end
  203. % bad.segments
  204. if exist([datasetname,delim,'bad.segments'])==2
  205. ds.badSegments=readBadSegments([datasetname,delim,'bad.segments']);
  206. end
  207. % BadChannels
  208. if exist([datasetname,delim,'BadChannels'])==2
  209. fid=fopen([datasetname,delim,'BadChannels'],'r','ieee-be');
  210. ds.BadChannels=char([]);
  211. while 1
  212. strng=fscanf(fid,'%s\n',1);
  213. if isempty(strng);break;end
  214. ds.BadChannels=strvcat(ds.BadChannels,strng);
  215. end
  216. fclose(fid);
  217. clear fid;
  218. end
  219. % VirtualChannels Assume this is the name of the file. Modify to accept *.vc?
  220. if exist([datasetname,delim,'VirtualChannels'])==2
  221. ds.Virtual=readVirtualChannels([datasetname,delim,'VirtualChannels']);
  222. end
  223. % processing.cfg
  224. if exist([datasetname,delim,'processing.cfg'])==2
  225. fid=fopen([datasetname,delim,'processing.cfg']);
  226. ds.processing=char(fread(fid,'uint8'))';
  227. fclose(fid);
  228. clear fid;
  229. end
  230. return
  231. %%%%%%%%%%%%%%%%% End of readCTFds %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  232. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  233. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  234. %%%%%%%%% Function readRes4 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  235. function res4=readRes4(res4File,res4Headers,...
  236. MAX_COILS,MAX_BALANCING,SENSOR_LABEL,len_sensor_name);
  237. % Open the res4 file
  238. fid=fopen(res4File,'r','ieee-be');
  239. % Check header.
  240. res4.header=char(fread(fid,8,'char')');
  241. if isempty(strmatch(res4.header,res4Headers,'exact'))
  242. fprintf(['\nreadCTFds (readRes4):res4 file header = %s. ',...
  243. 'Valid header options:'],res4.header);
  244. for k=1:size(res4Headers,1);fprintf(' %s',res4Headers(k,:));end;fprintf('\n\n');
  245. res4=struct([]);
  246. fclose(fid);
  247. return
  248. end
  249. % Remove trailing blanks, but include a final null (char(0)).
  250. res4.appName=[deblank(char(fread(fid,256,'char')')) char(0)];
  251. res4.dataOrigin=[deblank(char(fread(fid,256,'char')')) char(0)];
  252. res4.dataDescription=[deblank(char(fread(fid,256,'char')')) char(0)];
  253. res4.no_trials_avgd=fread(fid,1,'int16');
  254. res4.data_time=[deblank(char(fread(fid,255,'char')')) char(0)];
  255. res4.data_date=[deblank(char(fread(fid,255,'char')')) char(0)];
  256. % new_general_setup_rec_ext part of meg41GeneralResRec
  257. res4.no_samples=fread(fid,1,'int32');
  258. temp=fread(fid,2,'int16');
  259. res4.no_channels=temp(1);
  260. res4.sample_rate=fread(fid,1,'float64');
  261. res4.epoch_time=fread(fid,1,'float64');
  262. temp=fread(fid,2,'int16');
  263. res4.no_trials=temp(1);
  264. res4.preTrigPts=fread(fid,1,'int32');
  265. res4.no_trials_done=fread(fid,1,'int16');
  266. res4.no_trials_display=fread(fid,1,'int16');
  267. res4.save_trials=fread(fid,1,'int32');
  268. % meg41TriggerData part of new_general_setup_rec_ext 10 bytes total
  269. res4.primaryTrigger=fread(fid,1,'int32');
  270. res4.triggerPolarityMask=fread(fid,1,'int32');
  271. temp=fread(fid,1,'int16'); %Skip 2 bytes of padding
  272. % end of meg41TriggerData part of new_general_setup_rec_ext
  273. temp=fread(fid,3,'int16'); %Skip 2 bytes of padding
  274. res4.trigger_mode=temp(2);
  275. res4.accept_reject_Flag=fread(fid,1,'int32');
  276. temp=fread(fid,2,'int16'); %Skip 2 bytes of padding
  277. res4.run_time_display=temp(1);
  278. res4.zero_Head_Flag=fread(fid,1,'int32');
  279. res4.artifact_mode=fread(fid,1,'int32');
  280. % end of new_general_setup_rec_ext part of meg41GeneralResRec
  281. % meg4FileSetup part of meg41GeneralResRec
  282. % Remove trailing blanks, but include a final null (char(0))
  283. res4.nf_run_name=[deblank(char(fread(fid,32,'char')')) char(0)];
  284. res4.nf_run_title=[deblank(char(fread(fid,256,'char')')) char(0)];
  285. res4.nf_instruments=[deblank(char(fread(fid,32,'char')')) char(0)];
  286. res4.nf_collect_descriptor=[deblank(char(fread(fid,32,'char')')) char(0)];
  287. res4.nf_subject_id=[deblank(char(fread(fid,32,'char')')) char(0)];
  288. res4.nf_operator=[deblank(char(fread(fid,32,'char')')) char(0)];
  289. res4.nf_sensorFileName=[deblank(char(fread(fid,56,'char')')) char(0)];
  290. temp=fread(fid,3,'int32');
  291. res4.rdlen=temp(2);
  292. res4.run_description=[deblank(char(fread(fid,res4.rdlen,'char')')) char(0)];
  293. % end of meg4FileSetup part of meg41GeneralResRec
  294. % Filter descriptions. Set field res4.filters=[] if no filters are
  295. % defined.
  296. res4.num_filters=fread(fid,1,'int16');
  297. if res4.num_filters==0
  298. res4.filters=[];
  299. else
  300. for kfilt=1:res4.num_filters
  301. res4.filters(kfilt).freq=fread(fid,1,'float64');
  302. res4.filters(kfilt).fClass=fread(fid,1,'int32');
  303. res4.filters(kfilt).fType=fread(fid,1,'int32');
  304. res4.filters(kfilt).numParam=fread(fid,1,'int16');
  305. for kparm=1:res4.filters(kfilt).numParam
  306. res4.filters(kfilt).Param(kparm)=fread(fid,1,'float64');
  307. end
  308. end
  309. clear kfilt kparm;
  310. end
  311. % Read channel names. Must have size(res4.chanNames)=[channels 32]
  312. % Clean up the channel names. The MEG system software leaves junk
  313. % bytes in the channel name following the first zero.
  314. res4.chanNames=char(zeros(res4.no_channels,32));
  315. for kchan=1:res4.no_channels
  316. xname=strtok(char(fread(fid,32,'uint8')'),char(0));
  317. res4.chanNames(kchan,1:length(xname))=xname;
  318. end
  319. clear kchan xname;
  320. % Read sensor resource table. Floating point values are 'double'
  321. % but the code could be changed to convert to 'single' to save memory.
  322. for kchan=1:res4.no_channels
  323. res4.senres(kchan).sensorTypeIndex=fread(fid,1,'int16');
  324. res4.senres(kchan).originalRunNum=fread(fid,1,'int16');
  325. res4.senres(kchan).coilShape=fread(fid,1,'int32');
  326. res4.senres(kchan).properGain=fread(fid,1,'double');
  327. res4.senres(kchan).qGain=fread(fid,1,'double');
  328. res4.senres(kchan).ioGain=fread(fid,1,'double');
  329. res4.senres(kchan).ioOffset=fread(fid,1,'double');
  330. res4.senres(kchan).numCoils=fread(fid,1,'int16');
  331. numCoils=res4.senres(kchan).numCoils;
  332. temp=fread(fid,3,'int16');
  333. res4.senres(kchan).grad_order_no=temp(1);
  334. % Special code to take care of situations where someone wants to label bad channels
  335. % by setting their gain to zero.
  336. invgain=(res4.senres(kchan).ioGain*...
  337. res4.senres(kchan).properGain*res4.senres(kchan).qGain);
  338. if abs(invgain)>1e-50
  339. res4.senres(kchan).gain=1/invgain;
  340. else
  341. res4.senres(kchan).gain=sign(invgain)*1e50;
  342. end
  343. if res4.senres(kchan).sensorTypeIndex>=0 & res4.senres(kchan).sensorTypeIndex<=7
  344. % Nominal gain (fT/integer step)
  345. res4.senres(kchan).gain=1e15*res4.senres(kchan).gain;
  346. end
  347. % Data that was included in res4.senres(kchan).coilTbl in earlier versions of readCTFds
  348. res4.senres(kchan).pos0=zeros(3,res4.senres(kchan).numCoils);
  349. res4.senres(kchan).ori0=zeros(3,res4.senres(kchan).numCoils);
  350. res4.senres(kchan).area=zeros(1,res4.senres(kchan).numCoils);
  351. res4.senres(kchan).numturns=zeros(1,res4.senres(kchan).numCoils);
  352. for qx=1:numCoils
  353. buff=fread(fid,8,'double');
  354. res4.senres(kchan).pos0(:,qx)=buff(1:3);
  355. res4.senres(kchan).ori0(:,qx)=buff(5:7);
  356. temp=fread(fid,4,'int16');
  357. res4.senres(kchan).numturns(qx)=temp(1);
  358. res4.senres(kchan).area(qx)=fread(fid,1,'double');
  359. end
  360. if numCoils<MAX_COILS % Skip the rest of the coilTbl
  361. buff=fread(fid,10*(MAX_COILS-numCoils),'double');
  362. end
  363. % Data that was included in res4.senres(kchan).HdcoilTbl in earlier versions of readCTFds
  364. res4.senres(kchan).pos=zeros(3,res4.senres(kchan).numCoils);
  365. res4.senres(kchan).ori=zeros(3,res4.senres(kchan).numCoils);
  366. for qx=1:numCoils
  367. buff=fread(fid,8,'double');
  368. res4.senres(kchan).pos(:,qx)=buff(1:3);
  369. res4.senres(kchan).ori(:,qx)=buff(5:7);
  370. temp=fread(fid,2,'double'); % Don't bother with area and numturns. Already read.
  371. end
  372. if numCoils<MAX_COILS % Skip the rest of the HdcoilTbl
  373. buff=fread(fid,10*(MAX_COILS-numCoils),'double');
  374. end
  375. end
  376. clear kchan buff numCoils temp qx;
  377. % End reading sensor resource table
  378. res4.numcoef=fread(fid,1,'int16'); % Number of coefficient records
  379. % Create structure array res4.scrr which holds the balancing tables.
  380. for nx=1:res4.numcoef
  381. res4.scrr(nx).sensorName=uint8(fread(fid,len_sensor_name,'uint8'))';
  382. buff=fread(fid,8,'uint8');
  383. res4.scrr(nx).coefType=uint8(buff(1:4))'; % discard bytes 5-8
  384. res4.scrr(nx).numcoefs=double(fread(fid,1,'int16'));
  385. numcoef=res4.scrr(nx).numcoefs;
  386. buff=fread(fid,SENSOR_LABEL*MAX_BALANCING,'uint8');
  387. buff=reshape(buff,SENSOR_LABEL,MAX_BALANCING);
  388. res4.scrr(nx).sensor=[uint8(buff(:,1:numcoef)) ...
  389. uint8(zeros(SENSOR_LABEL,MAX_BALANCING-numcoef))];
  390. buff=fread(fid,MAX_BALANCING,'double');
  391. res4.scrr(nx).coefs=[buff(1:numcoef);zeros(MAX_BALANCING-numcoef,1)];
  392. end
  393. fclose(fid); % Close the res4 file
  394. return
  395. %%%%%%%%%% End of readRes4 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  396. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  397. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  398. %%%%%%%%% Function readHc %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  399. function hc=readHc(hcFile);
  400. % Reads the .hc file and returns a structure with head-coil posiiotns.
  401. % The hc file format is given in document CTF MEG File Formats (PN900-0088).
  402. % .hc file format: Coils and positions are specified by 4 lines of text:
  403. % Line 1: A B coil position relative to C (cm):
  404. % " 2: [tag]x = F
  405. % " 3: [tag]y = F
  406. % " 4: [tag]z = F
  407. % A = 'standard' or 'stadard' or 'measured'. 'stadard' is a typographical error that
  408. % appeared in v4 of the CTF Acq. The first set of coil positions is 'standard' or
  409. % 'stanard'. The next two sets are 'measured'.
  410. % B = coil label. For MEG systems, the first three coils must have the names 'nasion',
  411. % 'left ear', 'right ear'. They may be followed by other coil names.
  412. % For fMEG systems, there is no fixed standard for coil names.b readHc will
  413. % accept any coil names. Typically the coils are 'back','left hip','right hip',
  414. % 'sternum', 'belly'. In some UAMS data the names are 'nasion',...
  415. % The string 'coil position relative to' appears in all valid .hc files. It marks
  416. % the end of the coil label and the start ot the coordinate-system.
  417. % C = coordinate system = 'dewar' and 'head'(MEG) or 'abdomen' (fMEG). No variations
  418. % are permitted. The first two sets of coil positions are in dewar coordinates.
  419. % The last set is in 'head' or 'abdomen' coordinates.
  420. % Units : For .hc to be a valid coil position file, the coordinates must be in cm.
  421. % The program searches for the string '(cm):'.
  422. % F = coordinate (numeric)
  423. % There are exactly 3 sets of coil positions in the file appearing in the order
  424. % (1) A='standard', C='dewar'
  425. % (2) A='measured', C='dewar'
  426. % (3) A='measured', C='head' or 'abdomen'
  427. % The coils must appear in the same order in each set.
  428. % Input : Name of hc file (including complete path and .hc extension)
  429. % Output : Structure hc containing standard, dewar and CTF coordinates of the
  430. % nasion,left,right coils.
  431. % hc.name= names of the coils size(hc.name)=[nCoil x]
  432. % MEG: hc.standard, hc.dewar, hc.head : size=[3 nCoil] : coil positions in cm
  433. % fMEG: hc.standard, hc.dewar, hc.abdomen : size=[3 nCoil] : coil positions in cm
  434. if nargin==0 & nargout==0
  435. fprintf(['readHc: Version 1.3 4 Oct. 2007\n',...
  436. '\thc=readHc(hcFile) reads head-coil file hcFile and returns the head coil',...
  437. '\t positions in structure hc\n\n',...
  438. '\tThe file format is defined in document CTF MEG File Formats, PN900-0088.\n\n']);
  439. return
  440. end
  441. basicString='coil position relative to';
  442. % In MEG systems, the first three coil names are fixed.
  443. MEGCoilLabel=strvcat('nasion','left ear','right ear');
  444. C3options=strvcat('head','abdomen');
  445. unitString='(cm):';
  446. maxRCoil=200; % Report an error if any coil position is > maxRCoil cm.
  447. printWarning=1;
  448. hc=struct([]); % Return in event of an error.
  449. if exist(hcFile)~=2
  450. return
  451. end
  452. prec=1e-6; % Round positions.
  453. % Decide if this is a MEG or an fMEG system. Find the first line that contains the
  454. % string 'coil position relative to'.
  455. % MEG system: This line contains 'nasion'.
  456. % fMEG system: This line does not contains 'nasion'.
  457. fhc=fopen(hcFile,'rt','ieee-be');
  458. % Decide system type and generate arrays for strings A and C.
  459. % Search for first line. Allow for the posibility that extra text lines migt be added at
  460. % the start of the file.
  461. strngA=char([]);
  462. while 1
  463. textline=fgetl(fhc);
  464. if isequal(textline,-1)
  465. break
  466. elseif strfind(textline,'coil position relative to');
  467. stringA=strtok(textline);
  468. % Fix typographic error in some releases of Acq, not in all coils.
  469. if strcmp(stringA,'stadard')
  470. stringA = 'standard';
  471. end
  472. if ~strcmp(stringA,'standard')
  473. break
  474. end
  475. stringA=strvcat(stringA,'measured','measured');
  476. break;
  477. end
  478. end
  479. if isequal(textline,-1) | size(stringA,1)<3
  480. fprintf('readHc: File %s does not have the head position file format.\n',hcFile);
  481. fclose(fhc);
  482. return
  483. end
  484. % Set stringC(3,:)='head' or 'abdomen' later.
  485. stringC=strvcat('dewar','dewar');
  486. % textline is the first useful line of the head coil file.
  487. coilLabel=char([]); % Names given to the coils
  488. rhc=zeros(3,0); % Coil positions (cm)
  489. coil=0;
  490. nCoil=0;
  491. lastPositionSet=1;
  492. while ~isequal(textline,-1)
  493. % Parse the line
  494. [A,R]=strtok(textline);
  495. % Fix typographic error in some releases of Acq, not in all coils.
  496. if strcmp(A,'stadard')
  497. A = 'standard';
  498. end
  499. kpos=strfind(R,basicString);
  500. coilName=deblank(R(2:kpos-1));
  501. [C,R]=strtok(R(kpos+length(basicString):length(R)));
  502. if strmatch(C,C3options)
  503. stringC=strvcat(stringC,C);
  504. end
  505. [unit,R]=strtok(R);
  506. positionSet=intersect(strmatch(A,stringA),strmatch(C,stringC));
  507. if isempty(positionSet) | ~strcmp(unit,unitString) | ~isempty(R)
  508. break;
  509. end
  510. if positionSet==lastPositionSet
  511. coil=coil+1;
  512. else
  513. coil=1;
  514. end
  515. if positionSet==1
  516. % Assemble list of coil names
  517. coilLabel=strvcat(coilLabel,coilName);
  518. nCoil=coil;
  519. elseif ~strcmp(coilName,deblank(coilLabel(coil,:)));
  520. break;
  521. end
  522. % The line describing the coil and coordinate frame is OK.
  523. % Get the coordinates by reading 3 lines.
  524. buff=fscanf(fhc,'%s%s%f',9);
  525. if ~isequal(size(buff),[9 1])
  526. break
  527. end
  528. rhc=[rhc prec*round(buff(3:3:9)/prec)];
  529. fgetl(fhc); % Skip to the start of the next line.
  530. textline=fgetl(fhc);
  531. lastPositionSet=positionSet;
  532. end
  533. fclose(fhc);
  534. clear lastPositionSet coil textline fhc;
  535. clear stringA A C R kpos coilName unit buff;
  536. if size(rhc,2)~=positionSet*nCoil
  537. fprintf('readHc: File %s does not have %d complete sets of %d coils.\n',...
  538. hcFile,positionSet,nCoil);
  539. return
  540. end
  541. if max(max(abs(rhc)))>maxRCoil & printWarning
  542. fprintf('readHc: Head Coil file %s\n max(coil position)>%d.\n',...
  543. hcFile,round(maxRCoil));
  544. end
  545. % Assemble structure hc.
  546. hc=struct('names',coilLabel);
  547. coilCoords=deblank(strvcat('standard','dewar',stringC(3,:)));
  548. for q=1:positionSet
  549. hc=setfield(hc,deblank(coilCoords(q,:)),rhc(:,nCoil*(q-1)+[1:nCoil]));
  550. end
  551. return
  552. %%%%%%%%% End of readHc %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  553. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  554. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  555. %%%%%%%%% Function readEEG %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  556. function EEG=readEEG(EEGfile);
  557. % Reads the EEG file of a dataset and stores the infoemation in strucure array EEG.
  558. % EEG(k).chanName = channel name in the dataset (EEGmmm where mmm=channel number)
  559. % EEG(k).name = channel name given by the user (e.g. Fp4)
  560. % EEG(k).pos = electrode position in cm in CTF head coordinates
  561. if exist(EEGfile)~=2
  562. EEG=struct([]);
  563. return
  564. end
  565. fid=fopen(EEGfile,'r');
  566. EEG=struct('chanNum',[],'name',char([]),'pos',[]);
  567. nEEG=0;
  568. while 1
  569. chanNum=fscanf(fid,'%d',1);
  570. name=fscanf(fid,'%s',1);
  571. pos=fscanf(fid,'%f',3);
  572. if isempty(pos);break;end
  573. nEEG=nEEG+1;
  574. EEG(nEEG)=struct('chanNum',chanNum,'name',name,'pos',pos);
  575. end
  576. fclose(fid);
  577. return
  578. %%%%%%%%% End of readEEG %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  579. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  580. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  581. %%%%%%%%% Function readClassFile %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  582. function TrialClass=readClassFile(ClassFileName);
  583. % Reads a CTF ClassFile and stores information in a structure.
  584. % The class file allows a user to store a list of classsified trials in a data set.
  585. % The ClassFile format is defined in document CTF MEG File Formats, PN900-0088.
  586. % This format is rigid and readClassFile assumes that the ClassFile has the format
  587. % current in October 2006.
  588. % Inputs :
  589. % ClassFileName : marker file including the full path and extension .mrk.
  590. % trialList : List of trials to read. Trial numbering : 1,2,...
  591. % If omitted or empty, read all markers.
  592. % Output : Structure array marker. Output trial numbering starts at 1.
  593. % See CTF MEG File Formats, (PN900-0088) for the meaning of the structure
  594. % fields. A trial mat=y start before the t=0 point, so it is possible to have
  595. % markers with time<0 (see ds.res4.preTrigPts).
  596. TrialClass=struct([]);
  597. if exist(ClassFileName)~=2
  598. return % File doesn't exist.
  599. end
  600. fid=fopen(ClassFileName,'r','ieee-be');
  601. for k=1:5;fgetl(fid);end % Skip 5 lines (including path info)
  602. nClass=sscanf(fgetl(fid),'%d',1); %Read value and skip to the start of the next non-blank line.
  603. if nClass<=0
  604. fprintf('readClassFile: File %s has %d classes.\n',nClass);
  605. return
  606. end
  607. TrialClass=struct('ClassGroupId',[],'Name',char([]),...
  608. 'Comment',char([]),'Color',char([]),'Editable',char([]),'ClassId',[],'trial',[]);
  609. for k=1:nClass
  610. % Find the start of the next class identification
  611. % There is no need to check for end of file because the loop ends before an attempt
  612. % is made to read class nClass+1.
  613. while ~strcmp('CLASSGROUPID:',fgetl(fid));end
  614. ClassGroupId=sscanf(fgetl(fid),'%d',1);
  615. fgetl(fid);
  616. Name=deblank(fgetl(fid));
  617. fgetl(fid);
  618. Comment=deblank(fgetl(fid));
  619. fgetl(fid);
  620. Color=deblank(fgetl(fid));
  621. fgetl(fid);
  622. Editable=deblank(fgetl(fid));
  623. fgetl(fid);
  624. ClassId=sscanf(fgetl(fid),'%d',1);
  625. fgetl(fid);
  626. No_of_Trials=sscanf(fgetl(fid),'%d',1);
  627. fgetl(fid);fgetl(fid);
  628. if No_of_Trials>0
  629. trial=reshape(fscanf(fid,'%d',No_of_Trials),1,No_of_Trials);
  630. else
  631. trial=[];
  632. end
  633. % Adjust trial numbering so it starts at 1.
  634. TrialClass(k)=struct('ClassGroupId',ClassGroupId,'Name',Name,...
  635. 'Comment',Comment,'Color',Color,'Editable',Editable,'ClassId',ClassId,...
  636. 'trial',trial+1);
  637. end
  638. fclose(fid);
  639. return
  640. %%%%%%%%% End of readClassFile %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  641. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  642. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  643. %%%%%%%%% Function readBadSegments %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  644. function badSegments=readBadSegments(badSegmentsFile);
  645. % Reads the bad.segements file of a CTF data set and stores the information in structure
  646. % bad_segments.
  647. % badSegments.trial = List of trial numbers
  648. % badSegments.StartTime = List of bad segment start times (relative to trial).
  649. % badSegments.EndTime = List of bad segment end times.
  650. % Reads the file one line at a time. Each line has three numbers.
  651. % 1st: Trial number=integer. Trial numbering for bad.segments starts at 1.
  652. % 2nd,3rd : Start, End times (s).
  653. if exist(badSegmentsFile)~=2
  654. badSegments=struct([]);
  655. return
  656. end
  657. fid=fopen(badSegmentsFile,'r','ieee-be');
  658. badSegments=struct('chanName',char([]),'name',char([]),'pos',[]);
  659. nLine=0;
  660. data=zeros(3,0);
  661. while 1
  662. txt=fgetl(fid);
  663. if isequal(txt,-1);break;end
  664. nLine=nLine+1;
  665. [buff,count]=sscanf(txt,'%f');
  666. % Are the data good?
  667. badline=(count~=3);
  668. if ~badline;badline=(abs((buff(1)-round(buff(1)))>0.0001) | buff(3)<buff(2));end
  669. if badline
  670. fprintf('readCTFds (readBadSegments): Format error in file bad.segments.\n');
  671. fprintf('\tLine %d has %d numbers: ',nLine,count);fprintf('\t %g',buff);fprintf('\n');
  672. fprintf('\tMust have 3 numbers on each line: #1=integer, #2,#3=float, #3>=#2.\n');
  673. badSegments=struct([]);
  674. return
  675. end
  676. data=[data buff];
  677. end
  678. fclose(fid);
  679. badSegments=struct('trial',data(1,:),'StartTime',data(2,:),'EndTime',data(3,:));
  680. return
  681. %%%%%%%%% End of readBadSegments %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  682. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  683. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  684. %%%%%%%%% Function readVirtualChannels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  685. function Virtual=readVirtualChannels(VirtualChannelsName);
  686. % Reads the Virtual channels file associated with a data set
  687. Virtual=struct([]);
  688. % Check VirtualChannelsName
  689. if exist('VirtualChannelsName')~=1;VirtualChannelsName=char([]);end
  690. if isempty(VirtualChannelsName)
  691. fprintf('readVirtualChannels: Must specify a VirtualChannels file.\n');
  692. return
  693. elseif ~ischar(VirtualChannelsName)
  694. fprintf('readVirtualChannels: VirtualChannelsName must be a character string.\n');
  695. return
  696. elseif exist(VirtualChannelsName)~=2
  697. fprintf('readVirtualChannels: Cannot find file %s\n',VirtualChannelsName);
  698. return
  699. end
  700. fid=fopen(VirtualChannelsName,'r');
  701. count=0;
  702. Virtual=struct('Name',char([]),'Unit',char([]),'chan',char([]),'wt',[]);
  703. strng=textread(VirtualChannelsName,'%s','delimiter',',\t');
  704. k=0;
  705. while k<length(strng)
  706. k=k+1;
  707. if strmatch('VirtualChannel',strng{k});
  708. Refcount=0;
  709. chan=char([]);
  710. wt=[];
  711. elseif strmatch('Name:',strng{k});
  712. k=k+1;
  713. Name=strng{k};
  714. if isempty(Name);break;end
  715. elseif strmatch('Unit:',strng{k});
  716. k=k+1;
  717. Unit=strng{k};
  718. elseif strmatch('Ref:',strng{k});
  719. chan=strvcat(chan,strng{k+1});
  720. wt=[wt;str2num(strng{k+2})];
  721. k=k+2;
  722. elseif strmatch('}',strng{k});
  723. count=count+1;
  724. Virtual(count)=struct('Name',Name,'Unit',Unit,'chan',chan,'wt',wt);
  725. clear Name Unit chan wt Refcount;
  726. end
  727. end
  728. fclose(fid);
  729. if isempty(Virtual(1).Name)
  730. Virtual=struct([]);
  731. end
  732. return
  733. %%%%%%%%% End of readVirtualChannels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  734. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  735. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  736. %%%%%%%%% Function readMarkerFile %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  737. function marker=readMarkerFile(MarkerFileName,trialList);
  738. % Version 1.3 4 Oct. 2007
  739. % Reads specified trials of a CTF MarkerFile.
  740. % The MarkerFile format is defined in document CTF MEG File Formats, PN900-0088.
  741. % This format is rigid and readMarkerFile assumes that the MarkerFile has the format
  742. % current in October 2006. The document lists 4 Class Groups, but only CLASSGROUPID=0
  743. % (triggers) and CLASSGROUPID=3 (Manual) are actually used. read_MArkerFile reads only
  744. % these two groups, but it prints a message if the other groups are encountered.
  745. % Trigger markers (CLASSGROUPID=0) have an additional 4 pieces of information supplied:
  746. % BITNUMBER(int), POLARITY(+ or 1),SOURCE(text) and THRESHOLD (float). FOr
  747. % CLASSGROUPID=3, these fields are left empty.
  748. % Inputs :
  749. % MarkerFileName : marker file including the full path and extension .mrk.
  750. % trialList : List of trials to read. Trial numbering : 1,2,...
  751. % If omitted or empty, read all markers.
  752. % Output : marker: Structure array. Output trial numbering starts at 1.
  753. % See CTF MEG File Formats, (PN900-0088) for the meaning of the structure
  754. % fields. A trial may start before the t=0 point, so it is possible to have
  755. % markers with time<0 (see ds.res4.preTrigPts).
  756. % Fields: ClassGroupId, Name,Comment,Color,Editable,ClassId,trial,time;
  757. % Other fields that appear with ClassGroupId=0 are not included.
  758. if nargin==0 & nargout==0
  759. fprintf(['readMarkerFile: Version 1.3 4 Oct. 2007\n',...
  760. '\tReads a CTF dataset MarkerFile and returns a structure array.\n',...
  761. '\tmarker=readMarkerFile(FileName) reads ',...
  762. 'a marker file and returns the trials and times.\n',...
  763. '\tmarker=readMarkerFile(FileName,trialList) reads a marker file and returns only\n',...
  764. '\t\t\tthe markers for trials listed in vector trialList.\n\n',...
  765. '\treadMarkerFile increments trial numbers by 1 so first trial in a dataset has trial=1.\n\n',...
  766. '\tThe MarkerFile format is defined in document CTF MEG File Formats, PN900-0088.\n\n']);
  767. return
  768. end
  769. marker=struct([]);
  770. % Check MarkerFileName
  771. if exist('MarkerFileName')~=1;MarkerFileName=char([]);end
  772. if exist(MarkerFileName)~=2 % No MarkerFile present
  773. fprintf('readMarkerFile: Could not find MarkerFile.\n');
  774. MarkerFileName
  775. return
  776. end
  777. % Check input trialList
  778. if exist('trialList')~=1
  779. trialList=[];
  780. else
  781. trialList=reshape(trialList,1,length(trialList));
  782. if any((trialList-round(trialList))>0.1) | any(round(trialList)<=0)
  783. fprintf('readMarkerFile: trialList must have only positive integers.\n');
  784. return
  785. end
  786. end
  787. fid=fopen(MarkerFileName,'r','ieee-be');
  788. for k=1:5;fgetl(fid);end % Skip 5 lines (including path info)
  789. nMarker=sscanf(fgetl(fid),'%d',1); %Read value and skip to the start of the next non-blank line.
  790. if nMarker<=0
  791. fprintf('readMarkerFile: File %s has %d markers.\n',nMarker);
  792. fclose(fid);
  793. return
  794. end
  795. marker=struct('ClassGroupId',[],'Name',char([]),...
  796. 'Comment',char([]),'Color',char([]),'Editable',char([]),'ClassId',[],...
  797. 'BitNumber',[],'Polarity',char([]),'Source',char([]),'Threshold',[],...
  798. 'trial',[],'time',[]);
  799. for k=1:nMarker
  800. % Find the start of the next marker identification
  801. % There is no need to check for end of file because the loop ends before an attempt
  802. % is made to read marker class nClass+1.
  803. while ~strcmp('CLASSGROUPID:',fgetl(fid));end
  804. ClassGroupId=sscanf(fgetl(fid),'%d',1);
  805. if ~any(ClassGroupId==[0 3])
  806. fprintf('read_MarkerFile: Skipping a marker with CLASSGROUPID=%d\n',ClassGroupId);
  807. continue;
  808. end
  809. fgetl(fid);
  810. Name=deblank(fgetl(fid));
  811. fgetl(fid);
  812. Comment=deblank(fgetl(fid));
  813. fgetl(fid);
  814. Color=deblank(fgetl(fid));
  815. fgetl(fid);
  816. Editable=deblank(fgetl(fid));
  817. fgetl(fid);
  818. ClassId=sscanf(fgetl(fid),'%d',1);
  819. if ClassGroupId==0
  820. fgetl(fid);
  821. BitNumber=sscanf(fgetl(fid),'%d',1);
  822. fgetl(fid);
  823. Polarity=deblank(fgetl(fid));
  824. fgetl(fid);
  825. Source=deblank(fgetl(fid));
  826. fgetl(fid);
  827. Threshold=sscanf(fgetl(fid),'%f',1);
  828. else
  829. BitNumber=[];
  830. Polarity=char([]);
  831. Source=char([]);
  832. Threshold=[];
  833. end
  834. fgetl(fid);
  835. No_of_Samples=sscanf(fgetl(fid),'%d',1);
  836. fgetl(fid);fgetl(fid);
  837. trial=zeros(1,No_of_Samples);
  838. time=zeros(1,No_of_Samples);
  839. if No_of_Samples>0
  840. buff=fscanf(fid,'%d %f\n',2*No_of_Samples);
  841. trial=reshape(buff(1:2:2*No_of_Samples-1),1,No_of_Samples)+1; % Trial numbering starts at 1.
  842. time=reshape(buff(2:2:2*No_of_Samples),1,No_of_Samples);
  843. clear buff;
  844. end
  845. % Keep only the specified trials.
  846. if ~isempty(trialList)
  847. index=[];
  848. for q=trialList;
  849. index=[index find(trial==q)];
  850. end
  851. trial=trial(index);
  852. time=time(index);
  853. No_of_Samples=length(index);
  854. clear q index;
  855. end
  856. marker(k)=struct('ClassGroupId',ClassGroupId,'Name',Name,...
  857. 'Comment',Comment,'Color',Color,'Editable',Editable,'ClassId',ClassId,...
  858. 'BitNumber',BitNumber,'Polarity',Polarity,'Source',Source,'Threshold',Threshold,...
  859. 'trial',trial,'time',time);
  860. end
  861. fclose(fid);
  862. %clear k ClassGroupId Name Comment Color Editable ClassID No_of_Samples trial time;
  863. return
  864. %%%%%%%%%%%% End of readMarkerFile %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  865. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%