OSCR

AI-driven tripartite classification for optimizing wearable bioelectronics in depression management.

Code ↔ Paper

The paper beside its authors' code: matches between them have not been computed for this paper yet.

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 961 lines · 41 KB · CC-BY-4.0

  1. classdef channel < handle
  2. %핸들 클래스는 object를 참조하는 객체를 정의합니다. 객체를 복사하면 동일한 객체에 대한 *참조*가 하나 더 생성됩니다.
  3. properties
  4. organoidNum
  5. channelNum
  6. month
  7. sf
  8. msPerTs
  9. nTimestamps
  10. startTime
  11. endTimeApprox
  12. durationApprox
  13. originalTime
  14. t
  15. raw
  16. filtered
  17. thres
  18. timestampsPrePeak
  19. timestampsPostPeak
  20. spikeTimestamps
  21. spikeTimestampsMatrix
  22. spikeWaveforms
  23. nSpikes
  24. PCScores
  25. explainedVar
  26. clusters
  27. nClusters
  28. nSpikesPerCluster
  29. totalMeanSpikeStruct
  30. meanSpikesStruct
  31. ISIbeforePCA
  32. ISI
  33. ISIStruct
  34. thetaWaves
  35. thetaPhases
  36. filteredwave
  37. filteredphase
  38. filteredangle
  39. spikefiltAngles
  40. saveCircularfilt
  41. spikeThetaAngles
  42. phaseStruct
  43. clusterColors
  44. bursts
  45. %EMG data analysis
  46. originalSignal
  47. % baseline filterd
  48. signalRawLowBaseline
  49. signalRawNoBaseline
  50. signalFilteredLowBaseline
  51. signalFilteredNoBaseline
  52. % Rectified
  53. rawRectified
  54. filteredRectified
  55. signalRawLowBaselineRectified
  56. signalRawNoBaselineRectified
  57. signalFilteredLowBaselineRectified
  58. signalFilteredNoBaselineRectified
  59. % Enveloped
  60. rawEnveloped
  61. filteredEnveloped
  62. signalRawLowBaselineEnveloped
  63. signalRawNoBaselineEnveloped
  64. signalFilteredLowBaselineEnveloped
  65. signalFilteredNoBaselineEnveloped
  66. % max values of enveloped
  67. rawEnvelopedMax
  68. filteredEnvelopedMax
  69. signalRawLowBaselineEnvelopedMax
  70. signalRawNoBaselineEnvelopedMax
  71. signalFilteredLowBaselineEnvelopedMax
  72. signalFilteredNoBaselineEnvelopedMax
  73. % phase analysis per frequency band
  74. spikeBandAngles
  75. bandWave
  76. bandPhase
  77. bandAmplitude
  78. %% for firing rate
  79. rawShuffled
  80. end
  81. methods
  82. function ch = channel(x, t, sampleRate, organoidNum, channelNum, month)%(*)
  83. % organoidNum, channelNum, month: 관리를 위한 index
  84. % 필요한 값 저장
  85. ch.organoidNum = organoidNum;
  86. ch.channelNum = channelNum;
  87. ch.month = month;
  88. ch.sf = sampleRate;
  89. ch.msPerTs = (1000 / sampleRate);
  90. % t related
  91. ch.originalTime = t;
  92. ch.t = t;
  93. ch.nTimestamps = length(t);
  94. ch.startTime = t(1);
  95. ch.endTimeApprox = t(end);
  96. ch.durationApprox = t(end) - t(1);
  97. % signals
  98. ch.originalSignal = x;
  99. ch.raw = x;
  100. ch.filtered = x; %일단 filtered에도 raw x를 저장해 놓음
  101. ch.clusterColors = ["red", "green", "blue", "magenta", "cyan", "yellow"]';
  102. % provide essential information to the user
  103. fprintf("channel number = %d\n", ch.channelNum)
  104. fprintf("sampling rate = %fHz\n", ch.sf)
  105. fprintf("starts at %f, ends at %f, duration = %f\n", ch.startTime, ch.endTimeApprox, ch.durationApprox)
  106. fprintf("number of timestamps = %d\n\n", ch.nTimestamps)
  107. end
  108. %% Preprocessing for EMG
  109. %% 1. cutting
  110. function cutTime(ch, timeInterval)
  111. [timestampStart, timestampEnd] = ch.getIntervalTimestamps(timeInterval);
  112. ch.t = ch.t(timestampStart : timestampEnd);
  113. ch.nTimestamps = length(ch.t);
  114. ch.startTime = ch.t(1);
  115. ch.endTimeApprox = ch.t(end);
  116. ch.durationApprox = ch.t(end) - ch.t(1);
  117. %raw와 filtered 모두 cut하고 각자 저장함
  118. ch.raw = ch.originalSignal(timestampStart : timestampEnd);% raw
  119. ch.filtered = ch.originalSignal(timestampStart : timestampEnd);% filtered
  120. %display info to the user
  121. fprintf("Reset all preprocessing of signals")
  122. fprintf("new startTime = %f\n", ch.startTime)
  123. fprintf("new endTime = %f\n", ch.endTimeApprox)
  124. fprintf("new duration = %f\n", ch.durationApprox)
  125. fprintf("new nTimestamp = %d\n", ch.nTimestamps)
  126. end % end of function cutTime
  127. %% 2. baseline filtering
  128. function filterBaseline(ch, baselineTimeIntervals, passBand)
  129. timeStampIntervals = ch.getMultipleIntervalTimestamps(baselineTimeIntervals);
  130. replacement = bandpass(ch.raw, passBand, ch.sf);
  131. ch.signalRawLowBaseline = ch.raw;
  132. ch.signalRawNoBaseline = ch.raw;
  133. for i = 1 : length(timeStampIntervals)
  134. intervalNow = timeStampIntervals{i};
  135. startStamp = intervalNow(1);
  136. endStamp = intervalNow(2);
  137. ch.signalRawLowBaseline(startStamp : endStamp) = replacement(startStamp : endStamp);
  138. ch.signalRawNoBaseline(startStamp : endStamp) = 0;
  139. end % end of for loop over timeStampIntervals
  140. ch.signalFilteredLowBaseline = ch.signalRawLowBaseline; %copy
  141. ch.signalFilteredNoBaseline = ch.signalRawNoBaseline; %copy
  142. end % end of method filterBaseline
  143. %% 3. signal filtering
  144. % bandpass filter
  145. function bandPass(ch, passBand)%(*)
  146. ch.filtered = bandpass(ch.filtered, passBand, ch.sf);
  147. end
  148. % butterworth high-pass filter
  149. function highPassButterworth(ch, order, cutoff)
  150. %cutoff of high-pass filter = lower bound
  151. Fn = (ch.sf/2); % Nyquist frequency
  152. ftype = "high";
  153. [b, a] = butter(order, cutoff/Fn, ftype);
  154. ch.filtered = filter(b,a,ch.filtered);
  155. ch.signalFilteredLowBaseline = filter(b,a,ch.signalFilteredLowBaseline);
  156. ch.signalFilteredNoBaseline = filter(b,a,ch.signalFilteredNoBaseline);
  157. end
  158. function notchButterworth(ch, order, notch)
  159. Fn = (ch.sf/2); % Nyquist frequency
  160. ftype = 'stop';
  161. [b, a] = butter(order, notch/Fn, ftype);
  162. ch.filtered = filter(b,a,ch.filtered);
  163. ch.signalFilteredLowBaseline = filter(b,a,ch.signalFilteredLowBaseline);
  164. ch.signalFilteredNoBaseline = filter(b,a,ch.signalFilteredNoBaseline);
  165. end
  166. %% 4. rectify
  167. function rectify(ch)
  168. ch.rawRectified = abs(ch.raw);
  169. ch.filteredRectified = abs(ch.filtered);
  170. ch.signalRawLowBaselineRectified = abs(ch.signalRawLowBaseline);
  171. ch.signalRawNoBaselineRectified = abs(ch.signalRawNoBaseline);
  172. ch.signalFilteredLowBaselineRectified = abs(ch.signalFilteredLowBaseline);
  173. ch.signalFilteredNoBaselineRectified = abs(ch.signalFilteredNoBaseline);
  174. end
  175. function envelope(ch, paramter, method)
  176. %enveloping
  177. [ch.rawEnveloped, lo] = envelope(ch.rawRectified, paramter, method);
  178. [ch.filteredEnveloped, lo] = envelope(ch.filteredRectified, paramter, method);
  179. [ch.signalRawLowBaselineEnveloped, lo] = envelope(ch.signalRawLowBaselineRectified, paramter, method);
  180. [ch.signalRawNoBaselineEnveloped, lo] = envelope(ch.signalRawNoBaselineRectified, paramter, method);
  181. [ch.signalFilteredLowBaselineEnveloped, lo] = envelope(ch.signalFilteredLowBaselineRectified, paramter, method);
  182. [ch.signalFilteredNoBaselineEnveloped, lo] = envelope(ch.signalFilteredNoBaselineRectified, paramter, method);
  183. %maxs
  184. ch.rawEnvelopedMax = max(ch.rawEnveloped);
  185. ch.filteredEnvelopedMax = max(ch.filteredEnveloped);
  186. ch.signalRawLowBaselineEnvelopedMax = max(ch.signalRawLowBaselineEnveloped);
  187. ch.signalRawNoBaselineEnvelopedMax = max(ch.signalRawNoBaselineEnveloped);
  188. ch.signalFilteredLowBaselineEnvelopedMax = max(ch.signalFilteredLowBaselineEnveloped);
  189. ch.signalFilteredNoBaselineEnvelopedMax = max(ch.signalFilteredNoBaselineEnveloped);
  190. end
  191. %% baseline
  192. function [timestampStart, timestampEnd] = getIntervalTimestamps(ch, timeInterval)
  193. startTime = timeInterval(1);
  194. endTime = timeInterval(2);
  195. % deal with edge cases
  196. if startTime < ch.startTime
  197. fprintf("Start time is earlier than the channel's first timestamp")
  198. return
  199. end
  200. if endTime > ch.endTimeApprox
  201. fprintf("End time is later than the channel's last timestamp")
  202. return
  203. end
  204. startDiff = startTime - ch.startTime;
  205. timestampStart = floor( (startDiff / (ch.msPerTs/1000) )) + 1;
  206. endDiff = ch.endTimeApprox - endTime;
  207. timestampEnd = length(ch.t) - floor( (endDiff / (ch.msPerTs/1000)) );
  208. end
  209. function timeStampIntervals = getMultipleIntervalTimestamps(ch, baselineTimeIntervals)
  210. timeStampIntervals = {};
  211. for i = 1 : length(baselineTimeIntervals)
  212. timeInterval = baselineTimeIntervals{i};
  213. [timestampStart, timestampEnd] = ch.getIntervalTimestamps(timeInterval);
  214. timeStampIntervals{i} = [timestampStart, timestampEnd];
  215. end
  216. end %end of method getIntervalTimestamps
  217. function timeStampIntervalsConcat = getIntervalTimestampsConcat(ch, timeIntervals)
  218. intervalsTimestamp = ch.getMultipleIntervalTimestamps(timeIntervals);
  219. firtInterval = intervalsTimestamp{1};
  220. timeStampIntervalsConcat = firtInterval(1):firtInterval(2);
  221. for i = 2:length(intervalsTimestamp)
  222. intervalNow = intervalsTimestamp{i};
  223. timeStampsNow = intervalNow(1) : intervalNow(2);
  224. timeStampIntervalsConcat = [timeStampIntervalsConcat, timeStampsNow];
  225. end
  226. end
  227. %% summary statistics
  228. function meanRMSValue = meanRMS(ch, timeIntervals)
  229. originalSignal = ch.originalSignal;
  230. timeStampIntervals = ch.getMultipleIntervalTimestamps(timeIntervals);
  231. for i = 1 : length(timeStampIntervals)
  232. intervalNow = timeStampIntervals{i};
  233. startStamp = intervalNow(1);
  234. endStamp = intervalNow(2);
  235. originalSignal(startStamp : endStamp) = 0;
  236. end
  237. meanRMSValue = mean(abs(originalSignal));
  238. end %end of method meanRMSValue
  239. function [pSignal, pNoise] = SNRValue(ch, signalIntervals, noiseIntervals)
  240. originalSignal = ch.originalSignal;
  241. timeStampSignal = ch.getIntervalTimestampsConcat(signalIntervals);
  242. timeStampNoise = ch.getIntervalTimestampsConcat(noiseIntervals);
  243. pSignal = rms(originalSignal(timeStampSignal));
  244. pNoise = rms(originalSignal(timeStampNoise));
  245. end %end of method SNRValue
  246. %%
  247. function detectSpikes(ch, thres, preTime, postTime)
  248. % from CyborgBrainOrg.m
  249. %주의:threshold에 minus가 붙어 있음, 즉 이 알고리즘은 local minimum을 찾는 알고리즘
  250. threshold = -thres.*median(abs(ch.filtered)/0.6745); % 표준편차를 근사하는 공식. outlier에 덜 민감
  251. ch.thres = threshold;
  252. ch.timestampsPrePeak = ceil(preTime * (ch.sf/1000)); % 발견된 spike peak 앞쪽으로 몇 timestamp만큼의 waveform을 저장해야 하는지 계산
  253. ch.timestampsPostPeak = ceil(postTime * (ch.sf/1000)); %발견된 spike peak 뒷쪽으로 몇 timestamp만큼의 waveform을 저장해야 하는지 계산
  254. ch.spikeTimestamps = []; % spike가 발견된 timestamp를 저장할 1d array
  255. ch.spikeWaveforms = []; % 앞에서 계산한 길이로 spike 앞뒤를 잘라 얻은 waveform을 각 row에 저장할 nd array
  256. % spike detection 수행
  257. ii = ch.timestampsPrePeak + 1;
  258. count = 0;
  259. while ii < ch.nTimestamps
  260. tmp = ch.filtered(ii);
  261. if tmp < threshold
  262. if ii + ch.timestampsPostPeak < ch.nTimestamps % spike가 뒷쪽에 있을 경우 waveformwidth가 확보될 때만 기록.
  263. count = count + 1;
  264. ch.spikeTimestamps(count) = ii;
  265. ch.spikeWaveforms(count,:) = ch.filtered((-ch.timestampsPrePeak : ch.timestampsPostPeak) + ii);
  266. end
  267. ii = ii + ch.timestampsPostPeak;
  268. else
  269. ii = ii + 1;
  270. end
  271. end
  272. ch.nSpikes = length(ch.spikeTimestamps); %발견한 spike 개수 저장
  273. fprintf('number of spikes found : %d\n', ch.nSpikes);
  274. ch.calculateTotalMeanSpikes(); %mean spike 계산 후 저장
  275. end %end of detectSpikes
  276. function detectSpikesPositive(ch, thres, preTime, postTime)
  277. % from CyborgBrainOrg.m
  278. %주의:threshold에 plus가 붙어 있음, 즉 이 알고리즘은 local maximum을 찾는 알고리즘
  279. threshold = thres.*median(abs(ch.filtered)/0.6745); %% 바꾼 부분 % 표준편차를 근사하는 공식. outlier에 덜 민감
  280. ch.timestampsPrePeak = ceil(preTime * (ch.sf/1000)); % 발견된 spike peak 앞쪽으로 몇 timestamp만큼의 waveform을 저장해야 하는지 계산
  281. ch.timestampsPostPeak = ceil(postTime * (ch.sf/1000)); %발견된 spike peak 뒷쪽으로 몇 timestamp만큼의 waveform을 저장해야 하는지 계산
  282. ch.spikeTimestamps = []; % spike가 발견된 timestamp를 저장할 1d array
  283. ch.spikeWaveforms = []; % 앞에서 계산한 길이로 spike 앞뒤를 잘라 얻은 waveform을 각 row에 저장할 nd array
  284. % spike detection 수행
  285. ii = ch.timestampsPrePeak + 1;
  286. count = 0;
  287. while ii < ch.nTimestamps
  288. tmp = ch.filtered(ii);
  289. if tmp > threshold %%바꾼 부분
  290. if ii + ch.timestampsPostPeak < ch.nTimestamps % spike가 뒷쪽에 있을 경우 waveformwidth가 확보될 때만 기록.
  291. count = count + 1;
  292. ch.spikeTimestamps(count) = ii;
  293. ch.spikeWaveforms(count,:) = ch.filtered((-ch.timestampsPrePeak : ch.timestampsPostPeak) + ii);
  294. end
  295. ii = ii + ch.timestampsPostPeak;
  296. else
  297. ii = ii + 1;
  298. end
  299. end
  300. ch.nSpikes = length(ch.spikeTimestamps); %발견한 spike 개수 저장
  301. fprintf('number of spikes found : %d\n', ch.nSpikes);
  302. ch.calculateTotalMeanSpikes(); %mean spike 계산 후 저장
  303. end %end of detectSpikesPositive
  304. function getPCScores(ch)
  305. % from CyborgBrainOrg.m
  306. waveformZ = zscore(ch.spikeWaveforms); %standard scaling
  307. [~,score,~,~,explained] = pca(waveformZ); % waveform들에 PCA 적용
  308. ch.PCScores = score(:,1:2); % pick first two PC scores
  309. ch.explainedVar = explained; % PC의 분산 설명량 저장
  310. end
  311. function getKmeansClusters(ch, clusternum, seednum)
  312. % from CyborgBrainOrg.m
  313. rng(seednum); % 시드 넘버 설정
  314. [clusters, centroid] = kmeans(ch.PCScores, clusternum);%주어진 cluster개수로 kmeans 실행
  315. ch.clusters = clusters; %클러스터 membership 저장
  316. ch.nClusters = clusternum; %클러스터 개수 저장
  317. % 클러스터당 spike 개수
  318. ch.nSpikesPerCluster = zeros([ch.nClusters,1]);
  319. for ii = 1 : length(ch.clusters)
  320. for c = 1 : ch.nClusters
  321. if ch.clusters(ii) == c
  322. ch.nSpikesPerCluster(c) = ch.nSpikesPerCluster(c) + 1;
  323. ch.spikeTimestampsMatrix(c, ch.nSpikesPerCluster(c)) = ch.spikeTimestamps(ii);
  324. end
  325. end
  326. end
  327. % 클러스터별 spike 개수를 사용자에게 출력해 주기
  328. disp("number of spikes per cluster:")
  329. for clusterNum = 1 : length(ch.nSpikesPerCluster)
  330. fprintf("cluaster %d: %d \n", clusterNum, ch.nSpikesPerCluster(clusterNum))
  331. end
  332. ch.calculateClusterMeanSpikes() %클러스터별로 meanSpike, S.D. 계산
  333. %클러스터별로 InterSpike Intervals 계산. 각 클러스터별로 spike가 두 개 이상이어야 함
  334. if sum(ch.nSpikesPerCluster<=1) == 0
  335. ch.calculateISI()
  336. else
  337. disp("mean spike와 sd는 계산되었으나, spike가 한 개 이하인 클러스터가 존재하므로 클러스터별 ISI를 계산할 수 없습니다. ISI를 계산하려면 seed number를 다르게 하거나, 클러스터 개수를 다르게 해야 합니다.")
  338. end
  339. end % end of getKmeansClusters
  340. function getThetas(ch)%(*)
  341. ch.thetaWaves = bandpass(ch.raw, [4, 8], ch.sf); %apply 4-8 Hz frequency band
  342. xHilbert = hilbert(ch.thetaWaves); % Hilbert transform하여 복소수 형태로 표현
  343. ch.thetaPhases = angle(xHilbert); % 실수부와 허수부 사이 각을 계산
  344. ch.spikeThetaAngles = ch.thetaPhases(ch.spikeTimestamps); %spike 발생 시점의 theta angle을 저장
  345. ch.saveCircularTheta() %cluster별로 나누어 theta phase 값을 저장
  346. end %end of getThetas
  347. function getfilteredwave(ch,passBands)%(*)
  348. ch.filteredwave = bandpass(ch.raw, passBands, ch.sf); %apply a-b Hz frequency band
  349. xHilberts = hilbert(ch.filteredwave); % Hilbert transform하여 복소수 형태로 표현
  350. ch.filteredphase = angle(xHilberts); % 실수부와 허수부 사이 각을 계산
  351. ch.filteredangle = 57.29578 * ch.filteredphase;
  352. ch.spikefiltAngles = ch.filteredphase(ch.spikeTimestamps); %spike 발생 시점의 theta angle을 저장
  353. ch.saveCircularfilt(); %cluster별로 나누어 theta phase 값을 저장
  354. end %end of getThetas
  355. function [bandWaveOut, bandPhaseOut] = getBand(ch, frequencyRange)
  356. % added 2023.06.06
  357. % slightly modified the function getThetas
  358. % for the request "LFP-SU joint analysis"
  359. % input:
  360. % 1. freqBand: array of two numbers e.g. [4, 8]
  361. % - frequency band used for bandpass filter
  362. %
  363. % output:
  364. % 1. bandWave: 1d array.
  365. % - filtered voltage
  366. % 2. bandPhase: 1d array.
  367. % - filtered phase
  368. ch.bandWave = bandpass(ch.raw, frequencyRange, ch.sf); %uses the raw signal, so the function do not applies iteratively.
  369. analytic_signal = hilbert(ch.bandWave); % Hilbert transform하여 복소수 형태로 표현
  370. ch.bandAmplitude = abs(analytic_signal);
  371. ch.bandPhase = angle(analytic_signal); % 실수부와 허수부 사이 각을 계산
  372. ch.spikeBandAngles = ch.bandPhase(ch.spikeTimestamps); %spike 발생 시점의 angle을 저장
  373. ch.saveCircularBand() %cluster별로 나누어 phase 값을 저장
  374. %output
  375. bandWaveOut = ch.bandWave;
  376. bandPhaseOut = ch.bandPhase;
  377. end %end of function getBand
  378. function saveCircularBand(ch)%(*)
  379. for c = 1 : ch.nClusters
  380. ch.phaseStruct(c).values = ch.spikeBandAngles(ch.clusters == c); %timestamp 단위를 milisecond 단위로 변환
  381. ch.phaseStruct(c).clusterNum = c;%클러스터 번호 저장
  382. ch.phaseStruct(c).nSpikes = ch.nSpikesPerCluster(c);% 클러스터 내 spike 개수 저장
  383. end
  384. end % saveCircularBand
  385. function [phaseValues, nSpikes] = getBandPhaseByClusterNum(ch, clusterNum)%(*)
  386. %주어진 clusterNum에 해당하는 클러스터의 theta phase 값을 가져오는 함수. 히스토그램 그릴 때
  387. %쓰는 데이터를 얻기 위해 사용
  388. phaseValues = ch.phaseStruct(clusterNum).values;
  389. nSpikes = ch.phaseStruct(clusterNum).nSpikes;
  390. end % end of getBandPhaseByClusterNum
  391. function uniformTest(ch)
  392. pvals = zeros([ch.nClusters,1]);
  393. for c = 1 : ch.nClusters
  394. angles = ch.spikeBandAngles(ch.clusters == c);
  395. [pval, z] = circ_rtest(angles);
  396. pvals(c) = pval;
  397. end
  398. pvals
  399. end
  400. function uniformTest2(ch)
  401. pvalue = zeros(ch)
  402. for c=1:ch
  403. angles = ch.spikefiltAngles(ch)
  404. [pval, z] = circ_rtest(angles);
  405. pvalue(c) = pval;
  406. end
  407. pavlue;
  408. end
  409. % drawing functions
  410. function calculateTotalMeanSpikes(ch)
  411. %통합 mean spike
  412. tRangeCentered = (-ch.timestampsPrePeak : ch.timestampsPostPeak) * ch.msPerTs; %각 waveform의 t range. spike위치가 0이 되게 centering되어 있고, milisecond 단위로 변환
  413. %meanSpike를 정의
  414. if ch.nSpikes > 1 % 전체 spike 개수가 2개 이상이면
  415. meanSpike = mean(ch.spikeWaveforms); %모든 spike의 waveform을 평균한 것이 meanSpike
  416. else %전체 spike 개수가 1개 뿐이면
  417. meanSpike = ch.spikeWaveforms;% 굳이 mean을 계산할 필요 없이 그 spike가 곧 meanSpike
  418. end
  419. stdSpikes = std(ch.spikeWaveforms);
  420. ch.totalMeanSpikeStruct.nSpikes = ch.nSpikes; % cluster 내 spike 개수 저장
  421. ch.totalMeanSpikeStruct.meanSpike = meanSpike; % meanSpike waveform 저장
  422. ch.totalMeanSpikeStruct.std = stdSpikes;% standard deviation 저장
  423. ch.totalMeanSpikeStruct.tRangeCentered = tRangeCentered; %waveform의 t range 저장
  424. end %end of calculateTotalMeanSpikes
  425. function calculateClusterMeanSpikes(ch)
  426. % from CyborgBrainOrg.m
  427. % for each cluster, calculate average waveform, and S.D.;
  428. tRangeCentered = (-ch.timestampsPrePeak : ch.timestampsPostPeak) * ch.msPerTs; %각 waveform의 t range. spike위치가 0이 되게 centering되어 있고, milisecond 단위로 변환
  429. for c = 1 : ch.nClusters %각 클러스터마다 반복
  430. nSpikesNow = sum(ch.clusters == c);% 클러스터 내 spike 개수 저장
  431. spikesNow = ch.spikeWaveforms((ch.clusters == c),:); %현 cluster 내 spike waveform만 모은 행렬
  432. stdSpikes = std(ch.spikeWaveforms((ch.clusters == c),:));% 표준편차 계산
  433. %meanSpike를 정의
  434. if nSpikesNow > 1 % 클러스터 내에 spike 개수가 2개 이상이면
  435. meanSpike = mean(spikesNow); %모든 spike의 waveform을 평균한 것이 meanSpike
  436. else %클러스터 내에 spike 개수가 1개 뿐이면
  437. meanSpike = spikesNow;% 굳이 mean을 계산할 필요 없이 그 spike가 곧 meanSpike
  438. end
  439. % 구조체에 클러스터별 데이터 저장장
  440. ch.meanSpikesStruct(c).nSpikes = nSpikesNow; % cluster 내 spike 개수 저장
  441. ch.meanSpikesStruct(c).meanSpike = meanSpike; % meanSpike waveform 저장
  442. ch.meanSpikesStruct(c).std = stdSpikes;% standard deviation 저장
  443. ch.meanSpikesStruct(c).tRangeCentered = tRangeCentered; %waveform의 t range 저장
  444. end
  445. end % end of calculateClusterMeanSpikes
  446. function [meanSpikeWaveform, std, tRangeCentered, nSpikes] = getTotalMeanSpike(ch)
  447. meanSpikeWaveform = ch.totalMeanSpikeStruct.meanSpike;
  448. std = ch.totalMeanSpikeStruct.std;
  449. tRangeCentered = ch.totalMeanSpikeStruct.tRangeCentered;
  450. nSpikes = ch.totalMeanSpikeStruct.nSpikes;
  451. end %end of getTotalMeanSpike
  452. function [meanSpikeWaveform, std, tRangeCentered, nSpikes] = getClusterMeanSpike(ch, clusterNum)
  453. meanSpikeWaveform = ch.meanSpikesStruct(clusterNum).meanSpike;
  454. std = ch.meanSpikesStruct(clusterNum).std;
  455. tRangeCentered = ch.meanSpikesStruct(clusterNum).tRangeCentered;
  456. nSpikes = ch.meanSpikesStruct(clusterNum).nSpikes;
  457. end %end of getClusterMeanSpike
  458. function drawRaster(ch, color)
  459. % from CyborgBrainOrg.m
  460. for ii = 1:length(ch.spikeTimestamps)
  461. spikeTimestampTuple = ch.startTime + [ch.spikeTimestamps(ii), ch.spikeTimestamps(ii)]/ch.sf;
  462. p = plot(spikeTimestampTuple, [-1,1], 'k');
  463. p.Color = color;
  464. hold on
  465. end
  466. hold off
  467. ylim([-2, 2]);
  468. title('Raster plot');
  469. xlabel('time(s)');
  470. ylabel('Raster');
  471. end %drawRaster
  472. function drawColoredRaster(ch, clusterColors)
  473. % from CyborgBrainOrg.m
  474. for ii = 1 : length(ch.clusters)
  475. if ch.clusters(ii) == 1
  476. clusterNum = ch.clusters(ii);
  477. spikeTimestampTuple = ch.startTime + [ch.spikeTimestamps(ii), ch.spikeTimestamps(ii)]/ch.sf;
  478. plot(spikeTimestampTuple,[-1,1], clusterColors(clusterNum));
  479. hold on
  480. elseif ch.clusters(ii) == 2
  481. clusterNum = ch.clusters(ii);
  482. spikeTimestampTuple = ch.startTime + [ch.spikeTimestamps(ii), ch.spikeTimestamps(ii)]/ch.sf;
  483. plot(spikeTimestampTuple,[-1,1], clusterColors(clusterNum));
  484. hold on
  485. elseif ch.clusters(ii) == 3
  486. clusterNum = ch.clusters(ii);
  487. spikeTimestampTuple = ch.startTime + [ch.spikeTimestamps(ii), ch.spikeTimestamps(ii)]/ch.sf;
  488. plot(spikeTimestampTuple,[-1,1], clusterColors(clusterNum));
  489. hold on
  490. elseif ch.clusters(ii) == 4
  491. clusterNum = ch.clusters(ii);
  492. spikeTimestampTuple = ch.startTime + [ch.spikeTimestamps(ii), ch.spikeTimestamps(ii)]/ch.sf;
  493. plot(spikeTimestampTuple,[-1,1], clusterColors(clusterNum));
  494. hold on
  495. elseif ch.clusters(ii) == 5
  496. clusterNum = ch.clusters(ii);
  497. spikeTimestampTuple = ch.startTime + [ch.spikeTimestamps(ii), ch.spikeTimestamps(ii)]/ch.sf;
  498. plot(spikeTimestampTuple,[-1,1], clusterColors(clusterNum));
  499. hold on
  500. end
  501. end
  502. ylim([-2,2]);
  503. hold off
  504. end % drawColoredRaster
  505. function ISIbeforePCA = getISIvaluesBeforePCA(ch)
  506. ch.ISIbeforePCA = diff(ch.spikeTimestamps);
  507. ISIbeforePCA = ch.ISIbeforePCA * ch.msPerTs;
  508. end % end of getISIvaluesBeforePCA
  509. function calculateISI(ch)
  510. % from CyborgBrainOrg.m
  511. %calculate ISI
  512. for c = 1 : ch.nClusters
  513. for j = 2 : ch.nSpikesPerCluster(c)
  514. if ch.spikeTimestampsMatrix(c, j) > 0
  515. ch.ISI(c,j) = ch.spikeTimestampsMatrix(c, j) - ch.spikeTimestampsMatrix(c, j - 1);
  516. end
  517. end
  518. end
  519. ch.ISI(:, 1) = [];
  520. %클러스터별 ISI 값을 구조체(struct)에 저장
  521. for c = 1 : ch.nClusters %cluster 1부터 마지막 cluster까지 반복. c가 클러스터 번호
  522. ch.ISIStruct(c).values = ch.ISI(c, 1 : ch.nSpikesPerCluster(c) - 1) * ch.msPerTs; %timestamp 단위를 milisecond 단위로 변환
  523. ch.ISIStruct(c).clusterNum = c;%클러스터 번호 저장
  524. ch.ISIStruct(c).nSpikes = ch.nSpikesPerCluster(c);% 클러스터 내 spike 개수 저장
  525. end
  526. end % end of calculateISI
  527. function [ISIvalues, nSpikes] = getISIvalues(ch, clusterNum)
  528. ISIvalues = ch.ISIStruct(clusterNum).values;
  529. nSpikes = ch.ISIStruct(clusterNum).nSpikes;
  530. end %end of getISIvalues
  531. function bursts = detectBurstsMI(ch, begISI, endISI, minSpikes, minDurn, minIBI)
  532. % input:
  533. % - one spike train
  534. % - begISI : maximum interval to start burst; max ISI at start of burst; Beginning inter spike interval
  535. % - endISI : maximum interval to end burst; max ISI in burst; Ending inter spike interval
  536. % - minIBI: minimum interval between bursts (threshold for combining bursts)
  537. % - minDurn: minimum duration of a burst; minimum duration to consider as burst
  538. % - minSpikes: minimum number of spikes in burst; minimum number of spikes to consider as burst
  539. % output: bursts found using max interval method.
  540. msPerTs = ch.msPerTs;
  541. nspikes = ch.nSpikes;
  542. spikes = ch.spikeTimestamps;
  543. spikes = spikes * msPerTs;
  544. % Create a temp array for the storage of the bursts.
  545. % Assume that it will not be longer than Nspikes/2
  546. % since we need at least two spikes to be in a burst.
  547. maxBursts = floor(nspikes/2);
  548. bursts = NaN(maxBursts, 3);
  549. bursts = array2table(bursts, 'VariableNames',{'beg','end','IBI'});
  550. noBursts = []; %value to return if no bursts found.
  551. burst = 0; % current burst number
  552. %Start of the main algorithm
  553. % Phase 1 -- burst detection.
  554. %
  555. % parameters used: begISI, endISI
  556. %
  557. % when two consecutive spikes have an ISI *less* than begISI apart.
  558. % i.e. if nextISI < begISI,
  559. % a burst is defined as starting.
  560. %
  561. % The end of the burst is given
  562. % when two spikes have an ISI *greater* than endISI,
  563. % i.e. if nextISI > endISI.
  564. % in short, we find ISIs closer than begISI, and end with endISI.
  565. % lastEnd is the time of the last spike in the previous burst.
  566. % This is used to calculate the IBI.
  567. % For the first burst, this is no previous IBI
  568. lastEnd = NaN; %for first burst, there is no IBI.
  569. n = 2;
  570. isInBurst = false;
  571. while n <= nspikes
  572. nextISI = spikes(n) - spikes(n-1);
  573. if isInBurst
  574. % end of burst
  575. if nextISI > endISI
  576. endStamp = n - 1;
  577. isInBurst = false;
  578. ibi = spikes(beg) - lastEnd;
  579. lastEnd = spikes(endStamp);
  580. res = [beg, endStamp, ibi];
  581. burst = burst + 1;
  582. % fail case
  583. if burst > maxBursts
  584. print("too many bursts!!! algorithm failed.")
  585. return
  586. end %end of {if burst > maxBursts}
  587. bursts(burst, : ) = array2table(res);
  588. end % end of {nextISI > endISI}
  589. else % else of {if isInBurst}, i.e. not yet in burst
  590. % Found the start of a new burst.
  591. if nextISI < begISI
  592. beg = n - 1;
  593. isInBurst = true;
  594. end % end of {nextISI < begISI}
  595. end % end of {if isInBurst}
  596. n = n + 1;
  597. end %end of while n <= nspikes
  598. %phase 1.1. At the end of the burst, check if we were in a burst when the train finished.
  599. if isInBurst
  600. endStamp = nspikes;
  601. ibi = spikes(beg) - lastEnd;
  602. res = [beg, endStamp, ibi];
  603. burst = burst + 1;
  604. % fail case
  605. if burst > maxBursts
  606. print("too many bursts!!! algorithm failed.")
  607. return
  608. end % end of if burst > maxBursts
  609. bursts(burst , :) = array2table(res);
  610. end % end of if isInBurst
  611. %phase 1.2. Check if any bursts were found.
  612. if burst > 0
  613. % truncate to right length, as bursts will typically be very long.
  614. % (since we initated bursts with nrow = maxBursts)
  615. bursts = bursts(1:burst, :);
  616. else
  617. %% no bursts were found, so return an empty structure.
  618. print("no bursts were found. algorithm failed.")
  619. return
  620. end %end of {burst > 0}
  621. %print results
  622. nBurstsPhase1 = size(bursts);
  623. nBurstsPhase1 = nBurstsPhase1(1);
  624. fprintf("phase 1 result: found %d bursts, using parameters begISI and endISI\n\n", nBurstsPhase1)
  625. %bursts
  626. % Phase 2 -- merging of bursts.
  627. %
  628. % parameters used : minIBI
  629. %
  630. % Here we see if any pair of bursts have an IBI *less* than minIBI;
  631. % if so, we then merge the bursts.
  632. % We specifically need to check when say three bursts are merged into one.
  633. ibis = bursts(: ,'IBI');
  634. ibis = table2array(ibis);
  635. isMergeNeeded = ibis < minIBI;
  636. isAnyMergeNeeded = logical(sum(isMergeNeeded));
  637. if isAnyMergeNeeded
  638. % Merge bursts efficiently.
  639. % Work backwards through the list,
  640. % and then delete the merged lines afterwards.
  641. % This works when we have say 3+ consecutive bursts that merge into one.
  642. mergeIndex = find(isMergeNeeded);
  643. mergeIndexRev = flip(mergeIndex)
  644. for j = mergeIndexRev
  645. burst = mergeIndexRev(j);
  646. bursts(burst-1, "end") = bursts(burst, "end") %move the information one step forward.
  647. bursts(burst , "end") = NaN %not needed, but helpful.
  648. end %end or for loop
  649. bursts = bursts(not(isMergeNeeded) , : ) % delete the unwanted info.
  650. end % end of {sum(mergeBursts) > 1}
  651. nBurstsPhase2 = size(bursts);
  652. nBurstsPhase2 = nBurstsPhase2(1);
  653. fprintf("phase 2 result: after merging by minIBI, %d bursts left\n\n", nBurstsPhase2)
  654. % bursts
  655. % Phase 3 -- remove small bursts
  656. %
  657. % parameters used : minDurn, minSpikes
  658. %
  659. % delete small bursts i.e.
  660. % less than min duration (minDurn), or
  661. % having too few spikes (less than minSpikes).
  662. % In this phase we have the possibility of deleting all spikes.
  663. % LEN = number of spikes in a burst.
  664. % DURN = duration of burst.
  665. bursts = table2array(bursts);
  666. len = bursts(: , 2) - bursts(: , 1) + 1; %end, beg
  667. durn = spikes(bursts(: , 2)) - spikes(bursts(: , 1)); %end, beg
  668. bursts = [bursts, len, durn'];
  669. bursts = array2table(bursts, 'VariableNames',{'beg','end','IBI', 'len', 'durn'});
  670. IsReject = ((durn' < minDurn) | ( len < minSpikes));
  671. isAnyRejects = logical(sum(IsReject));
  672. fprintf("phase 3 result: %d bursts were removed whose duration is less than %d milisecond or have spikes less than %d ", sum(IsReject), minDurn, minSpikes)
  673. rejectsIndex = find(IsReject);
  674. % delete small bursts
  675. if isAnyRejects
  676. bursts = bursts(not(IsReject) , : );
  677. end % end of if isAnyRejects
  678. nBursts = size(bursts);
  679. nBursts = nBursts(1);
  680. if nBursts == 0 % if all the bursts were removed during phase 3.
  681. bursts = noBursts;
  682. else % else of {nBursts == 0}
  683. % Compute mean ISIS
  684. bursts = table2array(bursts);
  685. len = bursts(: , 2) - bursts(: , 1) + 1; %end, beg
  686. durn = spikes( bursts(: , 2) ) - spikes( bursts(: , 1) ); %end, beg
  687. meanISI = durn' ./ (len-1);
  688. % Recompute IBI (only needed if phase 3 deleted some cells).
  689. if nBursts > 1
  690. ibiBeg = spikes( bursts(: , 1) ); %beg
  691. ibiBeg = ibiBeg(2:nBursts);
  692. ibiEnd = spikes( bursts(: , 2) ); %end
  693. ibiEnd = ibiEnd(1:(nBursts-1));
  694. ibi2 = ibiBeg - ibiEnd;
  695. ibi2 = [NaN; ibi2'];
  696. else
  697. ibi2 = NaN;
  698. end
  699. bursts(: ,3) = ibi2; %IBI
  700. bursts = [bursts, meanISI];
  701. bursts = array2table(bursts, 'VariableNames',{'beg','end','IBI', 'nSpikes', 'durn', 'meanISI'});
  702. end %end of {if nBursts == 0}
  703. ch.bursts = bursts;
  704. end %end of the function
  705. function saveCircularTheta(ch)%(*)
  706. for c = 1 : ch.nClusters
  707. ch.phaseStruct(c).values = ch.spikeThetaAngles(ch.clusters == c); %timestamp 단위를 milisecond 단위로 변환
  708. ch.phaseStruct(c).clusterNum = c;%클러스터 번호 저장
  709. ch.phaseStruct(c).nSpikes = ch.nSpikesPerCluster(c);% 클러스터 내 spike 개수 저장
  710. end
  711. end % saveCircularTheta
  712. function [phaseValues, nSpikes] = getThetaPhaseByClusterNum(ch, clusterNum)%(*)
  713. %주어진 clusterNum에 해당하는 클러스터의 theta phase 값을 가져오는 함수. 히스토그램 그릴 때
  714. %쓰는 데이터를 얻기 위해 사용
  715. phaseValues = ch.phaseStruct(clusterNum).values;
  716. nSpikes = ch.phaseStruct(clusterNum).nSpikes;
  717. end % end of getThetaPhaseByClusterNum
  718. %% for test purpose
  719. function shuffle(ch, nfold)
  720. % add random noise to the raw signal to imitate
  721. nSample = length(ch.raw);
  722. nSamplePerFold = fix(nSample / nfold);
  723. nSampleRemain= rem(nSample, nfold);
  724. fold_index = {};
  725. for i = 1 : nfold
  726. fold_index{i} = ((i-1) * nSamplePerFold + 1) : (i * nSamplePerFold);
  727. end
  728. p = randperm(nfold);
  729. for i = 1 : nfold
  730. random_integer = p(i);
  731. raw_index = fold_index{i};
  732. random_index = fold_index{random_integer};
  733. ch.raw(raw_index) = ch.raw(random_index);
  734. end
  735. ch.filtered = ch.raw;
  736. end
  737. %%
  738. function [MI,MeanAmp] = ModIndex_v1(lfp,srate,Pf1,Pf2,Af1,Af2,position)
  739. % the eegfilt routine employed below is obtained from the EEGLAB toolbox
  740. % (Delorme and Makeig J Neurosci Methods 2004)
  741. PhaseFreq=eegfilt(lfp,srate,Pf1,Pf2); % this is just filtering
  742. Phase=angle(hilbert(PhaseFreq)); % this is getting the phase time series
  743. AmpFreq=eegfilt(lfp,srate,Af1,Af2); % just filtering
  744. Amp=abs(hilbert(AmpFreq)); % getting the amplitude envelope
  745. % Now we search for a Phase-Amp relation between these frequencies by
  746. % caclulating the mean amplitude of the AmpFreq in each phase bin of the
  747. % PhaseFreq
  748. % Computing the mean amplitude in each phase:
  749. nbin=length(position);
  750. winsize = 2*pi/nbin;
  751. MeanAmp=zeros(1,nbin);
  752. for j=1:nbin
  753. I = find(Phase < position(j)+winsize & Phase >= position(j));
  754. MeanAmp(j)=mean(Amp(I));
  755. end
  756. % the center of each bin (for plotting purposes) is position+winsize/2
  757. % quantifying the amount of amp modulation by means of a
  758. % normalized entropy index (Tort et al PNAS 2008):
  759. MI=(log(nbin)-(-sum((MeanAmp/sum(MeanAmp)).*log((MeanAmp/sum(MeanAmp))))))/log(nbin);
  760. end
  761. end %methods
  762. end %class

channel.m, under CC-BY-4.0 · at the source

Overview

  1. Department of Materials Science and Engineering, Yonsei University College of Engineering, Seoul 03722, Republic of Korea
  2. Center for Nanomedicine, Institute for Basic Science (IBS), Seoul 03722, Republic of Korea
  3. Department of Nano Biomedical Engineering (NanoBME), Advanced Science Institute, Yonsei University, Seoul 03722, Republic of Korea
  4. Department of Neurosurgery, Yonsei University College of Medicine, Seoul 03722, Republic of Korea
  5. Yonsei-KIST Convergence Research Institute, Seoul 03722, Republic of Korea
Journal: Science advances, volume 12, issue 26, article eaec9837
Dates: received 10 October 2025; accepted 15 May 2026; published online 24 June 2026; in print June 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1126/sciadv.aec9837 · PMID 42341109 · PMCID PMC13292945 · OpenAlex W7165747589
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: other (modality), human (organism), depression (population), clinical / translational (subfield)
Methods: Spectral & time-frequency, Preprocessing, Machine learning, Evoked potentials
MeSH: Artificial Intelligence*, Depression*, Wearable Electronic Devices*, Biomarkers, Digital Health, Disease Management, Humans, Intelligent Systems (* major topic)
Journal subjects: Physical and Materials Sciences, Engineering, Materials Science
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: National Research Foundation (RS-2023-NR077138, RS-2024-00464032, RS-2025-16063568, RS-2025-00514998, RS-2024-00460364, RS-2024-00406240, RS-2025-18362970, RS-2025-08672969, RS-2026-25512726, N10260097); Korea Institute of Science and Technology (2E33191, 2E33190); Institute for Basic Science (IBS) (IBS-R026-D1); Regional Innovation System & Education (RISE) Center (2026-RISE-01-022-02)
Citations: not cited yet (Europe PMC); 62 references in the paper

Abstract

Current disease-sensing devices primarily focus on distinguishing between healthy and diseased states, effective for diagnosis but limited in guiding optimal intervention timing for prevention. We developed a tripartite framework identifying pre-disease state in depression, a reversible phase preceding irreversible onset. Using complex systems theory, we analyzed early-warning signals emerging as biological systems approach critical transitions. Continuous monitoring of nine multimodal biomarkers—spanning electrophysiological, behavioral, and biological—enabled classification into normal, pre-disease, and disease states by quantitatively defining critical points. An artificial intelligence agent classified disease states with 95.2% accuracy using multimodal data, enabled by ultrasoft neural probes for stable, low-damage recordings. Therapeutic validation with a skin-attachable wireless vagus nerve stimulator integrating soft three-dimensional electrodes demonstrated superior efficacy during pre-disease states. Subjects treated during pre-disease showed faster recovery and greater therapeutic responses, while those treated after disease onset failed to achieve full recovery. This framework provides evidence-based rationale for early intervention.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above.

figshare 31813771

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 3 files
Software Heritage: not checked
Found in: “Data, code, and materials availability:”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
3 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 3 scripts, each with its path and the digest of its content;
  • no match between paragraphs and code yet;
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data, code, and materials availability

This study did not generate new materials. All data and code needed to evaluate and reproduce the results in the paper are present in the paper and/or the Supplementary Materials. The software code and source data used in this study are deposited in Figshare (https://doi.org/10.6084/m9.figshare.31813771) (62). All images not otherwise credited were created by the authors.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 9 authors, 8 MeSH terms, 4 funders, 52 references.

Cite

This paper

Lee, J., Hong, Y.-M., Kim, E., Seo, H., Chung, W. G., Park, W., Song, H., Kim, S., & Park, J.-U. (2026). AI-driven tripartite classification for optimizing wearable bioelectronics in depression management. Science advances, 12(26), eaec9837. https://doi.org/10.1126/sciadv.aec9837

BibTeX

@article{lee2026ai,
author = {Lee, Jakyoung and Hong, Yeon-Mi and Kim, Enji and Seo, Hunkyu and Chung, Won Gi and Park, Wonjung and Song, Hayoung and Kim, Sumin and Park, Jang-Ung},
title = {{AI-driven tripartite classification for optimizing wearable bioelectronics in depression management}},
journal = {Science advances},
year = {2026},
month = jun,
volume = {12},
number = {26},
pages = {eaec9837},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aec9837},
url = {https://doi.org/10.1126/sciadv.aec9837},
pmid = {42341109},
pmcid = {PMC13292945}
}

RIS

TY - JOUR
AU - Lee, Jakyoung
AU - Hong, Yeon-Mi
AU - Kim, Enji
AU - Seo, Hunkyu
AU - Chung, Won Gi
AU - Park, Wonjung
AU - Song, Hayoung
AU - Kim, Sumin
AU - Park, Jang-Ung
TI - AI-driven tripartite classification for optimizing wearable bioelectronics in depression management
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/06/24
VL - 12
IS - 26
SP - eaec9837
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aec9837
UR - https://doi.org/10.1126/sciadv.aec9837
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aec9837",
"type": "article-journal",
"title": "AI-driven tripartite classification for optimizing wearable bioelectronics in depression management",
"container-title": "Science advances",
"author": [
{
"family": "Lee",
"given": "Jakyoung"
},
{
"family": "Hong",
"given": "Yeon-Mi"
},
{
"family": "Kim",
"given": "Enji"
},
{
"family": "Seo",
"given": "Hunkyu"
},
{
"family": "Chung",
"given": "Won Gi"
},
{
"family": "Park",
"given": "Wonjung"
},
{
"family": "Song",
"given": "Hayoung"
},
{
"family": "Kim",
"given": "Sumin"
},
{
"family": "Park",
"given": "Jang-Ung"
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "26",
"page": "eaec9837",
"DOI": "10.1126/sciadv.aec9837",
"PMID": "42341109",
"PMCID": "PMC13292945",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aec9837",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
24
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-75244-w [code]
Neural dynamics of temporal interference stimulation monitoring by soft liquid metal interfaces across neural systems.
Journal: Nature communications
In common: 8 references, 2 authors
[2] doi:10.1371/journal.pbio.3003938 [code]
Theta oscillations tag episodic memories for sleep-dependent consolidation.
Journal: PLoS biology
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[3] doi:10.1371/journal.pbio.3003818 [code]
Human neuronal firing varies with the frequency of local field potential oscillations.
Journal: PLoS biology
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[4] doi:10.1523/jneurosci.0154-26.2026 [code]
Faster but less precise: expectation enhances response speed while reducing sensory fidelity.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[5] doi:10.1162/imag.a.1229 [code]
40 Hz audiovisual stimulation improves sustained attention and related brain oscillations.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[6] doi:10.1038/s41598-026-49900-6 [code]
Global neural oscillations underlie performance variability and attentional state fluctuations in humans.
Journal: Scientific reports
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[7] doi:10.7554/elife.110795 [code]
REM sleep prefrontal high-frequency oscillation chains mediate distinct cortical - hippocampal reactivation patterns compared to NREM sleep.
Journal: eLife
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[8] doi:10.1523/jneurosci.2001-25.2026 [code]
Dynamics of Dentate Gyrus Place Cells and Dentate Spikes during Spatial and Nonspatial Changes in Environments.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[9] doi:10.1038/s41593-026-02357-2 [code]
Experience reorganizes content-specific memory traces in macaques.
Journal: Nature neuroscience
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool
[10] doi:10.1038/s41467-026-75347-4 [code]
Sleep reveals dynamics integrating and segregating movement and stimulus representations in V1.
Journal: Nature communications
In common: CircStat, EEGLAB, Signal Processing Toolbox, 1 other tool

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.