/* SleepComparison.java created 2008-02-27 * */ package org.signalml.plugin.newstager.data; import static org.signalml.app.util.i18n.SvarogI18n._; import java.beans.IntrospectionException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedSet; import org.apache.log4j.Logger; import org.signalml.app.document.TagDocument; import org.signalml.app.model.components.LabelledPropertyDescriptor; import org.signalml.app.model.components.PropertyProvider; import org.signalml.domain.tag.SleepTagName; import org.signalml.domain.tag.StyledTagSet; import org.signalml.exception.SanityCheckException; import org.signalml.plugin.export.signal.ExportedTag; import org.signalml.plugin.export.signal.ExportedTagDocument; import org.signalml.plugin.export.signal.ExportedTagStyle; import org.signalml.plugin.export.signal.Tag; import org.signalml.plugin.export.signal.TagStyle; /** * SleepComparison * * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe * Sp. z o.o. */ public class NewStagerSleepComparison implements PropertyProvider { protected static final Logger logger = Logger .getLogger(NewStagerSleepComparison.class); private NewStagerSleepStatistic statistic; private ExportedTagDocument tag; private TagDocument expertTag; private TagDocument artifactTag; // styleOverlay[i][j] = stager said i, expert said j // i goes from 0 to count-1 // j goes from 0 to count and j == count => unrecognized expert tag private int[][] styleOverlay; private int[] rowSums; // T(i,:) private int[] columnSums; // T(:,i) private ArrayList<ExportedTagStyle> styles; private HashMap<String, Integer> styleNameMap; private int styleCount; private double segmentLength; private int segmentCount; private int total; public NewStagerSleepComparison(NewStagerSleepStatistic statistic, ExportedTagDocument tagDocument, TagDocument expertTag, TagDocument artifactTag) { this.statistic = statistic; this.tag = tagDocument; this.expertTag = expertTag; this.artifactTag = artifactTag; Set<ExportedTagStyle> tagStylesSet = tagDocument.getTagStyles(); StyledTagSet expertTagSet = expertTag.getTagSet(); StyledTagSet artifactTagSet = null; if (artifactTag != null) { artifactTagSet = artifactTag.getTagSet(); } styles = new ArrayList<ExportedTagStyle>(); if (SleepTagName.isValidRKSleepTag(tagDocument)) { styles.addAll(this.getSleepStyles(tagStylesSet, SleepTagName.RK_WAKE, SleepTagName.RK_1, SleepTagName.RK_2, SleepTagName.RK_3, SleepTagName.RK_4, SleepTagName.RK_REM, SleepTagName.RK_MT)); } else if (SleepTagName.isValidAASMSleepTag(tagDocument)) { styles.addAll(this.getSleepStyles(tagStylesSet, SleepTagName.AASM_WAKE, SleepTagName.AASM_N1, SleepTagName.AASM_N2, SleepTagName.AASM_N3, SleepTagName.AASM_REM)); } else { throw new SanityCheckException("Unsupported stage tag type"); } ExportedTag tag = null; int index = 0; ExportedTagStyle style = null; double position = 0; int i; SortedSet<ExportedTag> tags = tagDocument.getSetOfTags(); Iterator<ExportedTag> it = tags.iterator(); SortedSet<Tag> expertTags; Iterator<Tag> expertIt; Tag expert = null; TagStyle expertStyle = null; SortedSet<Tag> artifactTags; Iterator<Tag> artifactIt; Tag artifact = null; styleCount = styles.size(); styleNameMap = new HashMap<String, Integer>(styleCount); for (i = 0; i < styleCount; i++) { style = styles.get(i); styleNameMap.put(style.getName(), i); } styleOverlay = new int[styleCount][styleCount + 1]; double expectedTime; Integer idx; segmentLength = statistic.getSegmentLength(); segmentCount = statistic.getSegmentCount(); for (i = 0; i < segmentCount; i++) { expectedTime = i * segmentLength; while (tag == null) { // acquire next valid page tag if (!it.hasNext()) { logger.warn("No more tags at [" + expectedTime + "]"); break; } tag = it.next(); style = tag.getStyle(); position = tag.getPosition(); if (!style.getType().isPage()) { tag = null; } else if (position < expectedTime) { logger.warn("Extra tag [" + style.getName() + "] found at [" + position + "]"); tag = null; } else if (tag.getLength() != segmentLength) { logger.warn("Bad tag length [" + tag.getLength() + "]"); tag = null; } else { index = styles.indexOf(style); if (index < 0) { logger.warn("Unknown page style [" + style.getName() + "]"); tag = null; } } } if (tag != null) { if (position > expectedTime) { logger.warn("Missing tag at [" + expectedTime + "]"); continue; } // so at this time we know that we have a tag, starting as // expected // at expectedTime, lasting segmentTime, with style @ index in // styles // check artifacts if applicable if (artifactTagSet != null) { artifactTags = artifactTagSet.getTagsBetween(expectedTime, expectedTime + segmentLength); artifactIt = artifactTags.iterator(); while (artifactIt.hasNext()) { artifact = artifactIt.next(); if (artifact.overlaps(tag)) { logger.info("Artifacts at position [" + expectedTime + "]"); tag = null; break; } } if (tag == null) { continue; } } expertTags = expertTagSet.getTagsBetween(expectedTime, expectedTime + segmentLength); expert = null; expertIt = expertTags.iterator(); while (expert == null && expertIt.hasNext()) { expert = expertIt.next(); expertStyle = expert.getStyle(); if (!expertStyle.getType().isPage()) { expert = null; } else if (expert.getPosition() != expectedTime) { expert = null; } else if (expert.getLength() != segmentLength) { logger.warn("Bad expert tag length [" + expert.getLength() + "]"); expert = null; } } if (expert == null) { logger.warn("No corresponding expert tag found for location [" + expectedTime + "]"); styleOverlay[index][styleCount]++; tag = null; continue; } idx = styleNameMap.get(expertStyle.getName()); int expertIndex = (idx != null ? idx.intValue() : -1); if (expertIndex < 0) { logger.warn("Unrecognized expert style [" + expertStyle.getName() + "] at position [" + expectedTime + "]"); styleOverlay[index][styleCount]++; tag = null; continue; } styleOverlay[index][expertIndex]++; tag = null; } } // overlay matrix has been computed int e; total = 0; rowSums = new int[styleCount]; columnSums = new int[styleCount + 1]; for (i = 0; i < styleCount; i++) { for (e = 0; e <= styleCount; e++) { rowSums[i] += styleOverlay[i][e]; columnSums[e] += styleOverlay[i][e]; } total += rowSums[i]; } } public NewStagerSleepStatistic getStatistic() { return statistic; } /* * public ExportedTagDocument getTag() { return tag; } * * public TagDocument getExpertTag() { return expertTag; } * * public TagDocument getArtifactTag() { return artifactTag; } */ public int getStyleCount() { return styleCount; } public ExportedTagStyle getStyleAt(int index) { return styles.get(index); } public int getSegmentCount() { return segmentCount; } public float getSegmentLength() { return (float) segmentLength; } public int getStyleOverlay(int stager, int expert) { return styleOverlay[stager][expert]; } public int getUncategorizeStyleOverlay(int stager) { return styleOverlay[stager][styleCount]; } public int getStagerTotal(int stager) { return rowSums[stager]; } public int getExpertTotal(int expert) { return columnSums[expert]; } public int getUncategorizedTotal() { return columnSums[styleCount]; } public int getTotal() { return total; } public double getConcordance(int index) { int low = (rowSums[index] + columnSums[index] - styleOverlay[index][index]); if (low == 0) { return 0; } else { return (((double) styleOverlay[index][index]) / ((double) low)); } } public double getSensitivity(int index) { if (rowSums[index] == 0) { return 0; } else { return (((double) styleOverlay[index][index]) / ((double) rowSums[index])); } } public double getSelectivity(int index) { if (columnSums[index] == 0) { return 0; } else { return (((double) styleOverlay[index][index]) / ((double) columnSums[index])); } } public double getCohensKappa() { if (total == 0) { return -1; } long diagonal = 0; long marginal = 0; for (int i = 0; i < styleCount; i++) { diagonal += styleOverlay[i][i]; marginal += rowSums[i] * columnSums[i]; } double q = ((double) marginal) / ((double) total); double low = (((double) total) - q); if (low == 0) { return -1; } else { return ((((double) diagonal) - q) / low); } } public double getTotalConcordance() { if (total == 0) { return -1; } long diagonal = 0; for (int i = 0; i < styleCount; i++) { diagonal += styleOverlay[i][i]; } return (((double) diagonal) / ((double) total)); } public String getTotalConcordancePretty() { double totalConcordance = getTotalConcordance(); if (totalConcordance < 0) { return "-"; } else { return (new Double( ((double) Math.round(totalConcordance * 100)) / 100)) .toString(); } } public String getCohensKappaPretty() { double cohensKappa = getCohensKappa(); if (cohensKappa < 0) { return "-"; } else { return (new Double(((double) Math.round(cohensKappa * 100)) / 100)) .toString(); } } @Override public List<LabelledPropertyDescriptor> getPropertyList() throws IntrospectionException { LinkedList<LabelledPropertyDescriptor> list = new LinkedList<LabelledPropertyDescriptor>(); list.add(new LabelledPropertyDescriptor( _("total concordance"), "totalConcordancePretty", NewStagerSleepComparison.class, "getTotalConcordancePretty", null)); list.add(new LabelledPropertyDescriptor( _("cohens kappa"), "cohensKappaPretty", NewStagerSleepComparison.class, "getCohensKappaPretty", null)); return list; } private Collection<ExportedTagStyle> getSleepStyles( Set<ExportedTagStyle> tagStylesSet, String... styleNames) { Set<ExportedTagStyle> result = new HashSet<ExportedTagStyle>(); Set<String> styleNamesSet = new HashSet<String>( Arrays.asList(styleNames)); for (ExportedTagStyle style : tagStylesSet) { String styleName = style.getName(); if (styleNamesSet.contains(styleName)) { result.add(style); } } return result; } }