package ua.stu.scplib.structure; import java.util.ArrayList; import java.util.Iterator; import java.util.StringTokenizer; import java.util.TreeMap; import java.io.IOException; import ua.stu.scplib.attribute.BinaryInputStream; /** * <p>A class to encapsulate an entire SCP-ECG object.</p> * * <p>Typically this class would be used to read an SCP-ECG object from a file, * and to do something with the decompressed waveform data values. For example:</p> * * <pre> try { BinaryInputStream i = new BinaryInputStream(new BufferedInputStream(new FileInputStream(arg[0])),false); // always little endian SCPECG scpecg = new SCPECG(i,verbose); short[][] data = scpecg.getDecompressedRhythmData(); BinaryOutputStream o = new BinaryOutputStream(new BufferedOutputStream(new FileOutputStream(arg[1])),false); // little endian // write interleaved raw little endian data int numberOfChannels = data.length; int nSamplesPerChannel = data[0].length; // assume all the same for (int sample=0; sample<nSamplesPerChannel; ++sample) { for (int lead=0;lead<numberOfChannels; ++lead) { o.writeSigned16(data[lead][sample]); } } o.close(); } catch (Exception e) { e.printStackTrace(System.err); } * </pre> * <p>One might want to dump the entire contents of the instance as a string, as follows:</p> * * <pre> System.err.print(scpecg); * </pre> * <p>or perhaps to display the contents as a JTree:</p> * * <pre> ApplicationFrame af = new ApplicationFrame(); JScrollPane scrollPane = new JScrollPane(); SCPTreeBrowser browser = new SCPTreeBrowser(scpecg,scrollPane); af.getContentPane().add(scrollPane); af.pack(); af.show(); * </pre> * */ public class SCPECG { private static String nameOfCodeFild=""; public static String getNameOfCodeFild() { return nameOfCodeFild; } public void setNameOfCodeFild(String nameOfCodeFild) { this.nameOfCodeFild = nameOfCodeFild; } private class ReferenceBeatSubtractionZone { private int numberOfReferenceBeatSubtractionZones; long fcm; // sample number of the fiducial relative to the beginning of reference beat 0 private long[] start; private long[] fc; private long[] end; ReferenceBeatSubtractionZone(long sampleNumberOfQRSOfFiducial, long[] sampleNumberOfResidualToStartSubtractingQRS, long[] sampleNumberOfResidualOfFiducial, long[] sampleNumberOfResidualToEndSubtractingQRS) { fcm=sampleNumberOfQRSOfFiducial; start=sampleNumberOfResidualToStartSubtractingQRS; fc=sampleNumberOfResidualOfFiducial; end=sampleNumberOfResidualToEndSubtractingQRS; //assert(start != null); //assert(fc != null); //assert(end != null); numberOfReferenceBeatSubtractionZones=start.length; //assert(numberOfReferenceBeatSubtractionZones == fc.length); //assert(numberOfReferenceBeatSubtractionZones == end.length); } public int getSampleOffsetWithinReferenceBeatSubtractionZone(long sample) { // -1 is flag that it is not in the zone for (int qrs=0; qrs<numberOfReferenceBeatSubtractionZones; ++qrs) { if (sample >= start[qrs] && sample <= end[qrs]) { //assert((fc[qrs] - start[qrs]) <= max Java int); int offsetToAlignFiducial = (int)(fcm - (fc[qrs] - start[qrs])); //assert((sample - start[qrs]) <= max Java int); int offsetFromStartOfReferenceBeat = (int)(sample - start[qrs] + offsetToAlignFiducial); // numbered from zero //assert(offsetFromStartOfReferenceBeat != -1); //System.err.println("In the ReferenceBeatSubtractionZone "+sample); return offsetFromStartOfReferenceBeat; } } return -1; } } private class ProtectedArea { private int numberOfProtectedAreas; private long[] start; private long[] end; ProtectedArea(long[] sampleNumberOfResidualToStartProtectedArea,long[] sampleNumberOfResidualToEndProtectedArea) { start=sampleNumberOfResidualToStartProtectedArea; end=sampleNumberOfResidualToEndProtectedArea; //assert(start != null); //assert(end != null); numberOfProtectedAreas=start.length; //assert(numberOfProtectedAreas == end.length); } public boolean isSampleWithinProtectedArea(long sample) { boolean within=false; for (int area=0; area<numberOfProtectedAreas; ++area) { if (sample >= start[area] && sample <= end[area]) { within=true; break; } } return within; } } private TreeMap sections; private RecordHeader recordHeader; private short[][] decompressedRhythmData; /** * <p>Get the decompressed rhythm data.</p> * * <p>This includes undecimating and adding in any reference beat.</p> * * <p>Note that interpolation and filtering are NOT applied in the current implementation.</p> * * @return arrays of samples for each lead */ public short[][] getDecompressedRhythmData() { return decompressedRhythmData; } private void decompressRhythmData() { Section3 section3 = getSection3(); int numberOfLeads = section3 == null ? 0 : section3.getNumberOfLeads(); long[] numbersOfSamples = section3 == null ? null : section3.getNumbersOfSamples(); boolean referenceBeatUsedForCompression = section3 == null ? false : section3.getReferenceBeatUsedForCompression(); Section2 section2 = getSection2(); int numberOfHuffmanTables = section2 == null ? 0 : section2.getNumberOfHuffmanTables(); ArrayList huffmanTablesList = section2 == null ? null : section2.getHuffmanTables(); Section4 section4 = getSection4(); int sampleNumberOfQRSOfFiducial = section4 == null ? 0 : section4.getSampleNumberOfQRSOfFiducial(); long[] sampleNumberOfResidualToStartSubtractingQRS = section4 == null ? null : section4.getSampleNumberOfResidualToStartSubtractingQRS(); long[] sampleNumberOfResidualOfFiducial = section4 == null ? null : section4.getSampleNumberOfResidualOfFiducial(); long[] sampleNumberOfResidualToEndSubtractingQRS = section4 == null ? null : section4.getSampleNumberOfResidualToEndSubtractingQRS(); long[] sampleNumberOfResidualToStartProtectedArea = section4 == null ? null : section4.getSampleNumberOfResidualToStartProtectedArea(); long[] sampleNumberOfResidualToEndProtectedArea = section4 == null ? null : section4.getSampleNumberOfResidualToEndProtectedArea(); Section5Or6 section5 = getSection5(); int sampleTimeIntervalForReference = section5 == null ? 0 : section5.getSampleTimeInterval(); Section5Or6 section6 = getSection6(); int amplitudeValueMultiplier = section6 == null ? 0 : section6.getAmplitudeValueMultiplier(); int differenceDataUsed = section6 == null ? 0 : section6.getDifferenceDataUsed(); byte[][] compressedLeadData = section6 == null ? null : section6.getCompressedLeadData(); short[][] leadData = section6 == null ? null : section6.getLeadData(); int bimodalCompressionUsed = section6 == null ? 0 : section6.getBimodalCompressionUsed(); int sampleTimeIntervalForRhythm = section6 == null ? 0 : section6.getSampleTimeInterval(); int samplingRateDecimationFactor = (sampleTimeIntervalForReference == 0) ? 1 : sampleTimeIntervalForRhythm/sampleTimeIntervalForReference; //System.err.println("samplingRateDecimationFactor "+samplingRateDecimationFactor); //System.err.println("bimodalCompressionUsed "+bimodalCompressionUsed); //ProtectedArea protectedAreas = bimodalCompressionUsed == 0 ProtectedArea protectedAreas = (sampleNumberOfResidualToStartProtectedArea == null || sampleNumberOfResidualToEndProtectedArea == null) ? null : new ProtectedArea(sampleNumberOfResidualToStartProtectedArea,sampleNumberOfResidualToEndProtectedArea); ReferenceBeatSubtractionZone referenceBeatSubtractionZones = referenceBeatUsedForCompression ? new ReferenceBeatSubtractionZone(sampleNumberOfQRSOfFiducial, sampleNumberOfResidualToStartSubtractingQRS,sampleNumberOfResidualOfFiducial,sampleNumberOfResidualToEndSubtractingQRS) : null; decompressedRhythmData = new short[numberOfLeads][]; for (int lead=0; lead<numberOfLeads; ++lead) { //System.err.println("Decompressing rhythm or residual data for lead "+lead); decompressedRhythmData[lead] = null; // assert(numbersOfSamples[lead] <= largest Java int); int useNumberOfSamples = (int)(numbersOfSamples[lead]); //System.err.println("useNumberOfSamples = "+useNumberOfSamples); try { HuffmanDecoder decoder=null; if (section2!=null){ decoder = new HuffmanDecoder( compressedLeadData[lead], differenceDataUsed,amplitudeValueMultiplier/1000, // amplitudeValueMultiplier is nanoVolts, not microVolts numberOfHuffmanTables,huffmanTablesList); } { decompressedRhythmData[lead] = new short[useNumberOfSamples]; short value = 0; int decimationOffsetCount = 0; short lastDecimatedValue = 0; short currentDecimatedValue = 0; for (int sample=1; sample<=useNumberOfSamples; ++sample) { boolean within = protectedAreas != null && protectedAreas.isSampleWithinProtectedArea(sample); //System.err.println("["+sample+"] in protected area = "+within); if (within) { if (section2!=null) value = decoder.decode(); else { value = leadData[lead][sample-1]; value*=amplitudeValueMultiplier/1000; } decimationOffsetCount = 0; } else { if (samplingRateDecimationFactor <= 1) { // should never happen, but if we didn't check, division by zero if (section2!=null) value = decoder.decode(); else if ((sample -1) < useNumberOfSamples - 2){ value = leadData[lead][sample-1]; value*=(float) amplitudeValueMultiplier/1000; } } else { //int interpolationOffset = decimationOffsetCount%samplingRateDecimationFactor; //double interpolationMultipier = ((double)interpolationOffset)/samplingRateDecimationFactor; //if (interpolationOffset == 0) { // lastDecimatedValue = currentDecimatedValue; // currentDecimatedValue = decoder.decode(); // if (decimationOffsetCount < samplingRateDecimationFactor) { // lastDecimatedValue=currentDecimatedValue; // there was no previous value to interpolate from // } //} //value = (short)(lastDecimatedValue + (currentDecimatedValue-lastDecimatedValue)*interpolationMultipier); int interpolationOffset = decimationOffsetCount%samplingRateDecimationFactor; if (interpolationOffset == 0) { if (section2!=null) currentDecimatedValue = decoder.decode(); else { currentDecimatedValue = leadData[lead][sample-1]; currentDecimatedValue*=amplitudeValueMultiplier/1000; } } value = (short)currentDecimatedValue; } ++decimationOffsetCount; } if (referenceBeatSubtractionZones == null) { decompressedRhythmData[lead][sample-1]=value; } else { int offset = referenceBeatSubtractionZones.getSampleOffsetWithinReferenceBeatSubtractionZone(sample); //System.err.println("["+sample+"] reference beat subtraction zone offset="+offset); if (offset != -1) { //System.err.print("["+lead+","+sample+"] = "+value+" BEFORE; AFTER "); value+=decompressedReferenceBeatData[lead][offset]; } decompressedRhythmData[lead][sample-1]=value; } //System.err.println("["+lead+","+sample+"] = "+value); } } //System.err.println("Decoder status after all samples done "+decoder.toString()); } catch (Exception e) { e.printStackTrace(System.err); } } System.out.println(); } private short[][] decompressedReferenceBeatData; /** * <p>Get the decompressed reference beat data.</p> * * @return arrays of samples for each lead */ public short[][] getDecompressedReferenceBeatData() { return decompressedReferenceBeatData; } private void decompressReferenceBeatData() { Section3 section3 = (Section3)(sections.get(new Integer(3))); int numberOfLeads = section3 == null ? 0 : section3.getNumberOfLeads(); Section2 section2 = (Section2)(sections.get(new Integer(2))); int numberOfHuffmanTables = section2 == null ? 0 : section2.getNumberOfHuffmanTables(); ArrayList huffmanTablesList = section2 == null ? null : section2.getHuffmanTables(); Section4 section4 = (Section4)(sections.get(new Integer(4))); int lengthOfReferenceBeat0DataInMilliSeconds = section4 == null ? 0 : section4.getLengthOfReferenceBeat0DataInMilliSeconds(); Section5Or6 section5 = (Section5Or6)(sections.get(new Integer(5))); if (section5 == null) { return; } int amplitudeValueMultiplier = section5 == null ? 0 : section5.getAmplitudeValueMultiplier(); int sampleTimeInterval = section5 == null ? 0 : section5.getSampleTimeInterval(); int differenceDataUsed = section5 == null ? 0 : section5.getDifferenceDataUsed(); byte[][] compressedLeadData = section5 == null ? null : section5.getCompressedLeadData(); int numberOfSamples = 1000*lengthOfReferenceBeat0DataInMilliSeconds/sampleTimeInterval; // See prENV 1064 5.7.2 //System.err.println("Number of reference beat samples "+numberOfSamples); decompressedReferenceBeatData = new short[numberOfLeads][]; for (int lead=0; lead<numberOfLeads; ++lead) { //System.err.println("Decompressing reference beat data for lead "+lead); HuffmanDecoder decoder = new HuffmanDecoder( compressedLeadData[lead], differenceDataUsed,amplitudeValueMultiplier/1000, // amplitudeValueMultiplier is nanoVolts, not microVolts numberOfHuffmanTables,huffmanTablesList); try { // assert numbersOfSamples[lead] <= largest Java int decompressedReferenceBeatData[lead] = decoder.decode((int)(numberOfSamples)); } catch (Exception e) { e.printStackTrace(System.err); } } } /** * <p>Read an SCP-ECG object from an input stream.</p> * * <p>As errors are encountered, they are described on <code>stderr</code>.</p> * * <p>The sections are also validated as they are read, and Section 0 * is validated against the other sections.</p> * * @param i the input stream (should have been opened as little endian) * @param verbose if true, then the progress of the read is described */ public SCPECG(BinaryInputStream i,boolean verbose) throws IOException { sections = new TreeMap(); recordHeader = new RecordHeader(); long bytesRead = recordHeader.read(i); long recordBytesRemaining = recordHeader.getRecordLength()-bytesRead; // i.e. CRC and length itself are included in length long byteOffset = bytesRead; if (verbose) System.err.print(recordHeader); while (recordBytesRemaining>0) { // do sections SectionHeader sectionHeader = new SectionHeader(); bytesRead = sectionHeader.read(i,byteOffset); //System.err.println("Section "+sectionHeader.getSectionIDNumber()); //System.err.println("[Bytes read = "+bytesRead+" dec (0x"+Long.toHexString(bytesRead)+")]"); recordBytesRemaining-=bytesRead; //System.err.println("[Record bytes remaining = "+recordBytesRemaining+" dec (0x"+Long.toHexString(recordBytesRemaining)+")]"); byteOffset+=bytesRead; //System.err.println("[Byte offset = "+byteOffset+" dec (0x"+Long.toHexString(byteOffset)+")]"); if (verbose) System.err.print(sectionHeader); Section section = Section.makeSection(sectionHeader,sections); bytesRead = section.read(i); //System.err.println("[Bytes read = "+bytesRead+" dec (0x"+Long.toHexString(bytesRead)+")]"); recordBytesRemaining-=bytesRead; //System.err.println("[Record bytes remaining = "+recordBytesRemaining+" dec (0x"+Long.toHexString(recordBytesRemaining)+")]"); byteOffset+=bytesRead; //System.err.println("[Byte offset = "+byteOffset+" dec (0x"+Long.toHexString(byteOffset)+")]"); if (verbose) System.err.println(section); System.err.print(section.validate()); sections.put(new Integer(sectionHeader.getSectionIDNumber()),section); } Section0 section0 = (Section0)(sections.get(new Integer(0))); /*System.out.println(sections.get(new Integer(1)).toString()); System.out.println(sections.get(new Integer(2)).toString()); System.out.println(sections.get(new Integer(3)).toString()); System.out.println(sections.get(new Integer(4)).toString()); System.out.println(sections.get(new Integer(5)).toString()); System.out.println(sections.get(new Integer(6)).toString());*/ System.err.print(section0.validateAgainstOtherSections(sections)); decompressReferenceBeatData(); decompressRhythmData(); getIsoCode(); } /** * <p>Dump the object as a <code>String</code>.</p> * * @return the object as a <code>String</code> */ public String toString() { StringBuffer strbuf = new StringBuffer(); Section1 section1 = getSection1(); if (section1 == null) { strbuf.append("No demographic and administrative data\n"); } else { strbuf.append(section1); } Section2 section2 = getSection2(); if (section2 == null) { strbuf.append("No Huffman entropy coding\n"); } else { if (section2.useDefaultTable()) { strbuf.append("Huffman entropy coding with default table\n"); } else { strbuf.append("Huffman entropy coding with "+section2.getNumberOfHuffmanTables()+" specified tables\n"); } } Section3 section3 = getSection3(); if (section3 == null) { strbuf.append("No lead description :(\n"); } else { strbuf.append("Number of Leads = "+section3.getNumberOfLeads()+"\n"); strbuf.append((section3.getReferenceBeatUsedForCompression() ? "Reference Beat Used For Compression" : "Reference Beat Not Used For Compression")+"\n"); strbuf.append((section3.getLeadsAllSimultaneouslyRecorded() ? "Leads All Simultaneously Recorded" : "Leads Not All Simultaneously Recorded")+"\n"); strbuf.append("Number of Simultaneously Recorded Leads = "+section3.getNumberOfSimultaneouslyRecordedLeads()+"\n"); } Section4 section4 = getSection4(); if (section4 == null) { strbuf.append("No reference beat description\n"); } else { strbuf.append("Length ofReference Beat 0 Data In MilliSeconds = "+section4.getLengthOfReferenceBeat0DataInMilliSeconds()+"\n"); strbuf.append("Sample Number ofQRS of Fiducial = "+section4.getSampleNumberOfQRSOfFiducial()+"\n"); strbuf.append("Total Number of QRS Complexes = "+section4.getTotalNumberOfQRSComplexes()+"\n"); } Section5Or6 section5 = getSection5(); if (section5 == null) { strbuf.append("No reference beat data\n"); } else { strbuf.append("Reference beat data\n"); strbuf.append("\tAmplitude Value Multiplier = "+section5.getAmplitudeValueMultiplier()+"\n"); strbuf.append("\tSample Time Interval = "+section5.getSampleTimeInterval()+"\n"); strbuf.append("\tDifference Data Used = "+section5.getDifferenceDataUsed()+"\n"); } Section5Or6 section6 = getSection6(); if (section6 == null) { strbuf.append("No rhythm or residual data\n"); } else { strbuf.append("Rhythm or residual data\n"); strbuf.append("\tAmplitude Value Multiplier = "+section6.getAmplitudeValueMultiplier()+"\n"); strbuf.append("\tSample Time Interval = "+section6.getSampleTimeInterval()+"\n"); strbuf.append("\tDifference Data Used = "+section6.getDifferenceDataUsed()+"\n"); strbuf.append("\tBimodal Compression Used = "+section6.getBimodalCompressionUsed()+"\n"); } return strbuf.toString(); } public Section1 getSection1() { return (Section1)(sections.get(new Integer(1))); } public Section2 getSection2() { return (Section2)(sections.get(new Integer(2))); } public Section3 getSection3() { return (Section3)(sections.get(new Integer(3))); } public Section4 getSection4() { return (Section4)(sections.get(new Integer(4))); } public Section5Or6 getSection5() { return (Section5Or6)(sections.get(new Integer(5))); } public Section5Or6 getSection6() { return (Section5Or6)(sections.get(new Integer(6))); } /* public Section7 getSection7() { return (Section7)(sections.get(new Integer(7))); } public Section8Or11 getSection8() { return (Section8Or11)(sections.get(new Integer(8))); } public Section10 getSection10() { return (Section10)(sections.get(new Integer(10))); } public Section8Or11 getSection11() { return (Section8Or11)(sections.get(new Integer(11))); } */ /** * <p>Get the number of leads.</p> * * @return the number of leads */ public int getNumberOfLeads() { Section3 section3 = getSection3(); return section3 == null ? 0 : section3.getNumberOfLeads(); } /** * <p>Get the number of samples for each lead.</p> * * @return an array of the number of samples for each lead */ public long[] getNumbersOfSamples() { Section3 section3 = getSection3(); return section3 == null ? null : section3.getNumbersOfSamples(); } /** * <p>Get the sample time interval that will be applicable to the decompressed rhythm data.</p> * * <p>Note that this is the value after undecimation if the rhythm data was * decimated; specifically in such cases it will be the sample time interval * of the reference beat.</p> * * @return the sample time interval in microSeconds */ public int getDecompressedRhythmDataSampleTimeInterval() { int interval=0; Section5Or6 section5 = getSection5(); if (section5 != null) { interval=section5.getSampleTimeInterval(); // the reference beat interval will have been // used once the compressed rhythm data was undecimated to match it } else { Section5Or6 section6 = getSection6(); if (section6 != null) { interval=section6.getSampleTimeInterval(); // no reference beat, therefore no decimation occurred ( ? :( ) } } return interval; } /** * <p>Get Isocode.</p> * * @return the values as a <code>String</code> */ public void getIsoCode(){ //get encoding String nameOfCode=""; int namberInInt=0; StringTokenizer str=null; Section1 section1 = getSection1(); if (section1!=null){ StringTokenizer adi=new StringTokenizer(section1.getConcatenatedStringValuesOfAllOccurencesOfNamedField("AcquiringDeviceIdentificationNumber"),","); while (adi.hasMoreTokens()) { String curr=adi.nextToken(); if (curr.startsWith(" languageSupport")){ // System.out.println(curr); str =new StringTokenizer(curr,"="); str.nextToken(); namberInInt=Integer.parseInt(str.nextToken().trim().substring(2),16); } } switch (namberInInt) { case 19: nameOfCode="ISO8859_5"; break; case 0: nameOfCode="ASCII"; break; case 3: nameOfCode="ISO8859_2"; break; case 11: nameOfCode="ISO8859_4"; break; case 27: nameOfCode="ISO8859_6"; break; case 35: nameOfCode="ISO8859_7"; break; case 43: nameOfCode="ISO8859_8"; break; case 51: nameOfCode="ISO8859_11"; break; default: nameOfCode="ASCII"; break; } // System.out.println("-------------------------"); // System.out.println(nameOfCode); } setNameOfCodeFild(nameOfCode); } /** * <p>Get the concatenated values of all the occurences of a named field from Section 1 as a <code>String</code>.</p> * * @return the values as a <code>String</code> */ public String getNamedField(String fieldName) { Section1 section1 = getSection1(); return section1 == null ? null : section1.getConcatenatedStringValuesOfAllOccurencesOfNamedField(fieldName); } /** * <p>Get the names of the leads from Section 3.</p> * * @return the names of the leads */ public String[] getLeadNames() { Section3 section3 = getSection3(); return section3 == null ? null : section3.getLeadNames(); } /** * <p>Get the numbers of the leads from Section 3.</p> * * @return the numbers of the leads (using the codes in the standard and encoded in the data) */ public int[] getLeadNumbers() { Section3 section3 = getSection3(); return section3 == null ? null : section3.getLeadNumbers(); } /** * <p>Get an <code>Iterator</code> over all the sections.</p> * * @return an iterator of objects of type {@link com.pixelmed.scpecg.Section Section} */ public Iterator getSectionIterator() { return sections == null ? null : sections.values().iterator(); } /** * <p>For testing.</p> * * @param arg one, two or three arguments, the input filename, optionally a output raw data filename, and optionally a text dump filename */ /* public static void main(String arg[]) { boolean verbose = false; try { BinaryInputStream i = new BinaryInputStream(new BufferedInputStream(new FileInputStream(arg[0])),false); // SCP-ECG always little endian SCPECG scpecg = new SCPECG(i,verbose); //System.err.print(scpecg); if (arg.length > 1) { short[][] data = scpecg.getDecompressedRhythmData(); BinaryOutputStream o = new BinaryOutputStream(new BufferedOutputStream(new FileOutputStream(arg[1])),false); // SCP-ECG always little endian // write interleaved raw little endian data int numberOfChannels = data.length; int nSamplesPerChannel = data[0].length; // assume all the same for (int sample=0; sample<nSamplesPerChannel; ++sample) { for (int lead=0;lead<numberOfChannels; ++lead) { o.writeSigned16(data[lead][sample]); } } o.close(); if (arg.length > 2) { PrintWriter p = new PrintWriter(new BufferedOutputStream(new FileOutputStream(arg[2]))); // write signed short values non-interleaved (i.e. one lead at a time) for (int lead=0;lead<numberOfChannels; ++lead) { p.println("Lead: "+lead); for (int sample=0; sample<nSamplesPerChannel; ++sample) { p.println("["+sample+"]="+data[lead][sample]); } } p.close(); } } { ApplicationFrame af = new ApplicationFrame(); JScrollPane scrollPane = new JScrollPane(); SCPTreeBrowser browser = new SCPTreeBrowser(scpecg,scrollPane); af.getContentPane().add(scrollPane); af.pack(); af.show(); } } catch (Exception e) { e.printStackTrace(System.err); } }*/ }