/* * Copyright (c) 2012 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package uk.ac.diamond.scisoft.analysis.io; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import javax.imageio.metadata.IIOMetadata; import javax.vecmath.Matrix3d; import javax.vecmath.Vector3d; import org.eclipse.dawnsci.analysis.api.diffraction.DetectorProperties; import org.eclipse.dawnsci.analysis.api.diffraction.DiffractionCrystalEnvironment; import org.eclipse.dawnsci.analysis.api.io.ScanFileHolderException; import com.sun.media.imageio.plugins.tiff.TIFFDirectory; import com.sun.media.imageio.plugins.tiff.TIFFField; /** * This class will read the MAR 300 image file type * * This class is a sub class of the JavaImageLoader class. This reads the image * data. The following method initially reads the default tiff header while the * following reads the MAR specific headers. All information is read into a * hashmap as string object pairs. The MAR header is read as a stream of bytes * the value of which is determined in a C header file. Most values are 32bit ints. */ public class MARLoader extends TIFFImageLoader implements Serializable { private static final int MAR_TAG_NUMBER = 34710; static final int MAX_IMAGES = 9; static final int MAR_HEADER_SIZE = 3072; private boolean littleEndian; private Map<String, Serializable> metadataTable = new HashMap<String, Serializable>(); /** * @param FileName */ public MARLoader(String FileName) { super(FileName); } @Override protected void clearMetadata() { super.clearMetadata(); metadataTable.clear(); } @Override protected Map<String, Serializable> createMetadataMap(IIOMetadata imageMetadata) throws ScanFileHolderException { long offset = -1; try { TIFFDirectory tiffDir = TIFFDirectory.createFromMetadata(imageMetadata); TIFFField[] tiffField = tiffDir.getTIFFFields(); for (TIFFField tfield : tiffField) { if (tfield.getTagNumber() == MAR_TAG_NUMBER) { offset = tfield.getAsLong(0); continue; } metadataTable.put(tfield.getTag().getName(), tfield.getValueAsString(0)); } } catch (Exception e) { throw new ScanFileHolderException("Problem loading tiff header metadata in the MAR Loader class", e); } if (offset < 0) { throw new ScanFileHolderException("Cannot find MAR header offset as TIFF header tag is missing"); } File f = new File(fileName); InputStream is = null; try { is = new FileInputStream(f); byte[] hbd = new byte[MAR_HEADER_SIZE]; is.skip(offset); is.read(hbd, 0, MAR_HEADER_SIZE); int poss = 28; // 4+16+4+4 littleEndian = isLittleEndian(hbd, poss); metadataTable.put("headerByteOrderLE", littleEndian); // read header information // header format parameters 256 bytes poss = 0; metadataTable.put("headerType", getInteger(hbd, poss)); // flag for header type (can be used as magic number) poss += 4; metadataTable.put("headerName", Utils.getString(hbd, poss, 16)); // header name (MMX) poss += 16; metadataTable.put("headerMajorVersion", getInteger(hbd, poss)) ;// header_major_version (n.) poss += 4; metadataTable.put("headerMinorVersion", getInteger(hbd, poss)); // header_minor_version (.n) poss += 4; poss += 4; // skip header byte order boolean dataByteOrder = false; try { dataByteOrder = isLittleEndian(hbd, poss); } catch (Exception e) { // Do nothing--data should be read in superclass // System.out.println("Unknown dataByteOrder"); } metadataTable.put("dataByteOrder", dataByteOrder); // BIG_ENDIAN (Motorola,MIPS); LITTLE_ENDIAN (DEC, Intel) poss += 4; metadataTable.put("headerSize", getInteger(hbd, poss)); // in bytes poss += 4; metadataTable.put("frameType", getInteger(hbd, poss)); // flag for frame type poss += 4; metadataTable.put("magicNumber", getInteger(hbd, poss)); // to be used as a flag - usually to indicate new file poss += 4; metadataTable.put("compressionType", getInteger(hbd, poss)); // type of image compression poss += 4; metadataTable.put("compression1", getInteger(hbd, poss)); // compression parameter 1 poss += 4; metadataTable.put("compression2", getInteger(hbd, poss)); // compression parameter 2 poss += 4; metadataTable.put("compression3", getInteger(hbd, poss)); // compression parameter 3 poss += 4; metadataTable.put("compression4", getInteger(hbd, poss)); // compression parameter 4 poss += 4; metadataTable.put("compression5", getInteger(hbd, poss)); // compression parameter 5 poss += 4; metadataTable.put("compression6", getInteger(hbd, poss)); // compression parameter 6 poss += 4; metadataTable.put("nHeaders", getInteger(hbd, poss)); // total number of headers poss += 4; metadataTable.put("nFast", getInteger(hbd, poss)); // number of pixels in one line poss += 4; metadataTable.put("nSlow", getInteger(hbd, poss)); // number of lines in image poss += 4; metadataTable.put("depth", getInteger(hbd, poss)); // number of bytes per pixel poss += 4; metadataTable.put("recordLength", getInteger(hbd, poss)); // number of pixels between successive rows poss += 4; metadataTable.put("signifBits", getInteger(hbd, poss)); // true depth of data, in bits poss += 4; metadataTable.put("dataType", getInteger(hbd, poss)); // (signed,unsigned,float...) poss += 4; metadataTable.put("saturatedValue", getInteger(hbd, poss)); // value marks pixel as saturated poss += 4; metadataTable.put("sequence", getInteger(hbd, poss)); // TRUE or FALSE poss += 4; metadataTable.put("nImages", getInteger(hbd, poss)); // total number of images - size of each is nfast*(nslow/nimages) poss += 4; metadataTable.put("origin", getInteger(hbd, poss)); // corner of origin poss += 4; metadataTable.put("orientation", getInteger(hbd, poss)); // direction of fast axis poss += 4; metadataTable.put("viewDirection", getInteger(hbd, poss)); // direction to view frame poss += 4; metadataTable.put("overflowLocation", getInteger(hbd, poss)); // FOLLOWING_HEADER, FOLLOWING_DATA poss += 4; metadataTable.put("over8Bits", getInteger(hbd, poss)); // # of pixels with counts > 255 poss += 4; metadataTable.put("over16Bits", getInteger(hbd, poss)); // # of pixels with count > 65535 poss += 4; metadataTable.put("multiplexed", getInteger(hbd, poss)); // multiplex flag poss += 4; metadataTable.put("numFastImages", getInteger(hbd, poss));// # of images in fast direction poss += 4; metadataTable.put("numSlowImages", getInteger(hbd, poss)); // # of images in slow direction poss += 4; metadataTable.put("backgroundApplied", getInteger(hbd, poss)); // flags correction has been applied - hold magic number? poss += 4; metadataTable.put("biasApplied", getInteger(hbd, poss)); // flags correction has been applied - hold magic number ? poss += 4; metadataTable.put("flatFieldApplied", getInteger(hbd, poss)); // flags correction has been applied - hold magic number ? poss += 4; metadataTable.put("distortionApplied", getInteger(hbd, poss)); // flags correction has been applied - hold magic number ? poss += 4; metadataTable.put("originalHeaderType", getInteger(hbd, poss)); // Header/frame type from file that frame is read from poss += 4; metadataTable.put("fileSaved", getInteger(hbd, poss)); // Header/frame type from file that frame is read from poss += 4; // 15 more items missed out poss += 80; // move forward to ignore reserve1 (64-40)*sizeof(int32)-16 assert poss == 256; // Data statistics (128) int[] totalCounts = new int[2]; totalCounts[0] = getInteger(hbd, poss); poss += 4; totalCounts[1] = getInteger(hbd, poss); poss += 4; metadataTable.put("totalCounts", totalCounts); int[] specialCounts1 = new int[2]; specialCounts1[0] = getInteger(hbd, poss); poss += 4; specialCounts1[1] = getInteger(hbd, poss); poss += 4; metadataTable.put("specialCounts1", specialCounts1); int[] specialCounts2 = new int[2]; specialCounts2[0] = getInteger(hbd, poss); poss += 4; specialCounts2[1] = getInteger(hbd, poss); poss += 4; metadataTable.put("specialCounts2", specialCounts1); metadataTable.put("min", getInteger(hbd, poss)); poss += 4; metadataTable.put("max", getInteger(hbd, poss)); poss += 4; metadataTable.put("mean", getInteger(hbd, poss)); poss += 4; metadataTable.put("rms", getInteger(hbd, poss)); poss += 4; metadataTable.put("p10", getInteger(hbd, poss)); poss += 4; metadataTable.put("p90", getInteger(hbd, poss)); poss += 4; metadataTable.put("statsUpToDate", getInteger(hbd, poss)); poss += 4; int[] pixelNoise = new int[MAX_IMAGES]; for (int i = 0; i < MAX_IMAGES; i++) { pixelNoise[i] = getInteger(hbd, poss); poss += 4; } metadataTable.put("pixelNoise", pixelNoise); poss += (32 - 13 - MAX_IMAGES) * 4; // move forward to ignore reserve 2 32-13-MAX_IMAGES*sizeof(int32) assert poss == 384; // more Statistics (256) // this can be sample changer info too int[] percentile = new int[128]; for (int i = 0; i < 128; i++) { percentile[i] = getShort(hbd, poss); poss += 2; } assert poss == 640; // goniostat parameters (128) metadataTable.put("xtalToDetector", getInteger(hbd, poss) / 1000.0); // 1000*distance in millimetres poss += 4; metadataTable.put("beamX", getInteger(hbd, poss) / 1000.0); // 1000*x beam position (pixels) poss += 4; metadataTable.put("beamY", getInteger(hbd, poss) / 1000.0); // 1000*y beam position (pixels) poss += 4; metadataTable.put("intergrationTime", getInteger(hbd, poss) / 1000.0); // integration time in milliseconds poss += 4; metadataTable.put("exposureTime", getInteger(hbd, poss) / 1000.0); // exposure time in milliseconds poss += 4; metadataTable.put("readoutTime", getInteger(hbd, poss) / 1000.0); // readout time in milliseconds poss += 4; metadataTable.put("nReads", getInteger(hbd, poss) / 1000.0); // number of readouts to get this image poss += 4; metadataTable.put("start2theta", getInteger(hbd, poss) / 1000.0); // 1000*two_theta angle poss += 4; metadataTable.put("startOmega", getInteger(hbd, poss) / 1000.0); // 1000*omega angle poss += 4; metadataTable.put("startChi", getInteger(hbd, poss) / 1000.0); // 1000*chi angle poss += 4; metadataTable.put("startKappa", getInteger(hbd, poss) / 1000.0); // 1000*kappa angle poss += 4; metadataTable.put("startPhi", getInteger(hbd, poss) / 1000.0); // 1000*phi angle poss += 4; metadataTable.put("startDelta", getInteger(hbd, poss) / 1000.0); // 1000*delta angle poss += 4; metadataTable.put("startGamma", getInteger(hbd, poss) / 1000.0); // 1000*gamma angle poss += 4; metadataTable.put("startXtalToDetector", getInteger(hbd, poss) / 1000.0); // 1000*distance in mm (dist in um) poss += 4; metadataTable.put("stop2theta", (getInteger(hbd, poss) / 1000.0)); // 1000*two_theta angle poss += 4; metadataTable.put("stopOmega", getInteger(hbd, poss) / 1000.0); // 1000*omega angle poss += 4; metadataTable.put("stopChi", getInteger(hbd, poss) / 1000.0); // 1000*chi angle poss += 4; metadataTable.put("stopKappa", getInteger(hbd, poss) / 1000.0); // 1000*kappa angle poss += 4; metadataTable.put("stopPhi", getInteger(hbd, poss) / 1000.0); // 1000*phi angle poss += 4; metadataTable.put("stopDelta", getInteger(hbd, poss) / 1000.0); // 1000*delta angle poss += 4; metadataTable.put("stopGamma", getInteger(hbd, poss) / 1000.0); // 1000*gamma angle poss += 4; metadataTable.put("stopXtalToDetector", getInteger(hbd, poss) / 1000.0); // 1000*distance in mm (dist in um) poss += 4; metadataTable.put("rotationAxis", getInteger(hbd, poss)); // active rotation axis poss += 4; metadataTable.put("rotationRange", getInteger(hbd, poss) / 1000.0); // 1000*rotation angle poss += 4; metadataTable.put("detectorRotateX", getInteger(hbd, poss) / 1000.0); // 1000*rotation of detector around X poss += 4; metadataTable.put("detectorRotateY", getInteger(hbd, poss) / 1000.0); // 1000*rotation of detector around Y poss += 4; metadataTable.put("detectorRotateZ", getInteger(hbd, poss) / 1000.0); // 1000*rotation of detector around Z poss += 4; // ignored total_dose poss += (32 - 28) * 4; // skip forward to ignore reserve 3 32-28*sizeof(uint32) assert poss == 768; // Detector parameters (128) metadataTable.put("detectorType", getInteger(hbd, poss)); // detector type poss += 4; metadataTable.put("pixelSizeX", getInteger(hbd, poss)); // pixel size (nanometers) poss += 4; metadataTable.put("pixelSizeY", getInteger(hbd, poss)); // pixel size (nanometers) poss += 4; metadataTable.put("meanBias", getInteger(hbd, poss) / 1000.0); // 1000*mean bias value poss += 4; metadataTable.put("photonPer100ADU", getInteger(hbd, poss)); // photons/100 ADUs poss += 4; int[] measuredBias = new int[MAX_IMAGES]; for (int i = 0; i < MAX_IMAGES; i++) { measuredBias[i] = getInteger(hbd, poss); // 1000*mean bias value for each image poss += 4; } metadataTable.put("measuredBias", measuredBias); int[] measuredTemperature = new int[MAX_IMAGES]; for (int i = 0; i < MAX_IMAGES; i++) { measuredTemperature[i] = getInteger(hbd, poss); // Temperature of each detector in milliKelvins poss += 4; } metadataTable.put("measuredTemperature", measuredTemperature); int[] measuredPressure = new int[MAX_IMAGES]; for (int i = 0; i < MAX_IMAGES; i++) { measuredPressure[i] = getInteger(hbd, poss); // Pressure of each chamber in microTorr poss += 4; } metadataTable.put("measuredPressure", measuredPressure); // currentOffset += 32 - 5 + 3 * MAX_IMAGES * 4; // 32-5+3*MAX_IMAGES*sizeof(int23) // reserve 4 retired to make room for measured pressure and measured temperature assert poss == 896; // X-ray source and optics parameters (128) // X-ray source parameters metadataTable.put("sourceType", getInteger(hbd, poss)); // (code) - target, synch. etc poss += 4; metadataTable.put("sourceDx", getInteger(hbd, poss));// Optics param. - (size microns) poss += 4; metadataTable.put("sourceDy", getInteger(hbd, poss)); // Optics param. - (size microns) poss += 4; metadataTable.put("sourceWavelength", getInteger(hbd, poss)); // wavelength (femtoMeters) poss += 4; metadataTable.put("sourcePower", getInteger(hbd, poss)); // (Watts) poss += 4; metadataTable.put("sourceVoltage", getInteger(hbd, poss)); // (Volts) poss += 4; metadataTable.put("sourceCurrent", getInteger(hbd, poss)); // (microAmps) poss += 4; metadataTable.put("sourceBias", getInteger(hbd, poss)); // (Volts) poss += 4; metadataTable.put("sourcePolarizationX", getInteger(hbd, poss)); // () poss += 4; metadataTable.put("sourcePolarizationY", getInteger(hbd, poss));// () poss += 4; poss += 16; // ignore reserve_source 4*sizeof(int32) // X-ray optics parameters metadataTable.put("opticsType", getInteger(hbd, poss)); // Optics type (code) poss += 4; metadataTable.put("opticsDx", getInteger(hbd, poss)); // Optics param. - (size microns) poss += 4; metadataTable.put("opticsDy", getInteger(hbd, poss)); // Optics param. - (size microns) poss += 4; metadataTable.put("opticsWavelength", getInteger(hbd, poss)); // Optics param. - (size microns) poss += 4; metadataTable.put("opticsDispersion", getInteger(hbd, poss)); // Optics param. - (*10E6) poss += 4; metadataTable.put("opticsCrossfireX", getInteger(hbd, poss)); // Optics param. - (microRadians) poss += 4; metadataTable.put("opticsCrossfireY", getInteger(hbd, poss)); // Optics param. - (microRadians) poss += 4; metadataTable.put("opticsAngle", getInteger(hbd, poss)); // Optics param. - (microRadians) poss += 4; metadataTable.put("opticsPolarizationX", getInteger(hbd, poss)); // () poss += 4; metadataTable.put("opticsPolarizationY", getInteger(hbd, poss)); // () poss += 4; poss += 16; // 4*sizeof(int32) reserve_optics poss += 16; // 32-28*sizeof(int32) reserve5 assert poss == 1024; // File parameters 1024 bytes metadataTable.put("filetitle", Utils.getString(hbd, poss, 128)); // Title poss += 128; metadataTable.put("filepath", Utils.getString(hbd, poss, 128)); // path name for data file poss += 128; metadataTable.put("filename", Utils.getString(hbd, poss, 64)); // name of data file poss += 64; metadataTable.put("AcquireTimestamp", Utils.getString(hbd, poss, 32)); // date and time of acquisition poss += 32; metadataTable.put("headerTimestamp", Utils.getString(hbd, poss, 32)); // date and time of header update poss += 32; metadataTable.put("saveTimestamp", Utils.getString(hbd, poss, 32)); // date and time file saved poss += 32; metadataTable.put("fileComment", Utils.getString(hbd, poss, 512)); // comments poss += 512; poss += 96; // 1024-(128+128+64+3*32+512) // reserve 6 assert poss == 2048; metadataTable.put("datasetComments", Utils.getString(hbd, poss, 512)); // comments - can be used as desired poss += 512; metadataTable.put("userData", Utils.getString(hbd, poss, 512)); // reserved for user definable data - will not be used by Mar! poss += 512; assert poss == MAR_HEADER_SIZE; } catch (Exception e) { throw new ScanFileHolderException("Problem loading MAR metadata", e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { throw new ScanFileHolderException("Problem closing MAR file", e); } } } return createGDAMetadata(); } /** * Check if four bytes from MAR file indicate little endian byte ordering * @param bytes * @param pos * @return true if little endian * @throws ScanFileHolderException */ public static boolean isLittleEndian(byte[] bytes, int pos) throws ScanFileHolderException { int ba = bytes[pos++]; int bb = bytes[pos++]; int bc = bytes[pos++]; int bd = bytes[pos]; if (Utils.leInt(ba, bb, bc, bd) == 1234) return true; if (Utils.beInt(ba, bb, bc, bd) == 4321) return false; throw new ScanFileHolderException("Byte order unknown!"); } private int getInteger(byte[] bytes, int pos) { return littleEndian ? Utils.leInt(bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]) : Utils.beInt(bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]); } private int getShort(byte[] bytes, int pos) { return littleEndian ? Utils.leInt(bytes[pos], bytes[pos + 1]) : Utils.beInt(bytes[pos], bytes[pos + 1]); } private Map<String, Serializable> createGDAMetadata() throws ScanFileHolderException { Map<String, Serializable> GDAMetadata = new HashMap<String, Serializable>(); try { double pixelSizeX = ((Integer) getMetadataValue("pixelSizeX")).intValue() / 1.0e6; int imageWidth = Integer.parseInt((String) getMetadataValue("ImageWidth")); double beamX = ((Double) getMetadataValue("beamX")).doubleValue(); double pixelSizeY = (((Integer) getMetadataValue("pixelSizeY")).intValue() / 1.0e6); int imageLength = Integer.parseInt((String) getMetadataValue("ImageLength")); double beamY = ((Double) getMetadataValue("beamY")).doubleValue(); double distance = ((Double) getMetadataValue("xtalToDetector")).doubleValue(); // NXGeometery:NXtranslation double[] detectorOrigin = { imageWidth * pixelSizeX - beamX, imageLength * pixelSizeY - beamY, distance }; GDAMetadata.put("NXdetector:NXgeometry:NXtranslation", detectorOrigin); GDAMetadata.put("NXdetector:NXgeometry:NXtranslation@units", "metre"); // NXGeometery:NXOrientation double detectorRotationX = ((Double) getMetadataValue("detectorRotateX")).doubleValue(); double detectorRotationY = ((Double) getMetadataValue("detectorRotateY")).doubleValue(); double detectorRotationZ = ((Double) getMetadataValue("detectorRotateZ")).doubleValue(); Matrix3d rotX = new Matrix3d(); rotX.rotX(detectorRotationX); Matrix3d rotY = new Matrix3d(); rotY.rotY(detectorRotationY); Matrix3d rotZ = new Matrix3d(); rotZ.rotZ(detectorRotationZ); Matrix3d euler = new Matrix3d(); euler.mul(rotX, rotY); euler.mul(rotZ); double[] tmp = new double[3]; double[] tmp1 = new double[3]; double[] directionCosine = new double[6]; euler.getColumn(0, tmp); euler.getColumn(1, tmp1); for (int i = 0; i < directionCosine.length / 2; i++) { directionCosine[i] = tmp[i]; directionCosine[3 + i] = tmp1[i]; } GDAMetadata.put("NXdetector:NXgeometry:NXorientation", directionCosine); // NXGeometery:XShape (shape from origin (+x, +y, +z,0, 0, 0) > x,y,0,0,0,0) double[] detectorShape = { imageWidth * pixelSizeX, imageLength * pixelSizeY, 0, 0, 0, 0 }; GDAMetadata.put("NXdetector:NXgeometry:NXshape", detectorShape); GDAMetadata.put("NXdetector:NXgeometry:NXshape@units", "milli*metre"); // NXGeometery:NXFloat GDAMetadata.put("NXdetector:x_pixel_size", pixelSizeX); GDAMetadata.put("NXdetector:x_pixel_size@units", "milli*metre"); GDAMetadata.put("NXdetector:y_pixel_size", pixelSizeY); GDAMetadata.put("NXdetector:y_pixel_size@units", "milli*metre"); // "NXmonochromator:wavelength" double lambda = ((Integer) getMetadataValue("sourceWavelength")).intValue() / 100000.0; GDAMetadata.put("NXmonochromator:wavelength", lambda); GDAMetadata.put("NXmonochromator:wavelength@units", "Angstrom"); // oscillation range double startOmega = Double.parseDouble( getMetadataValue("startOmega").toString()); double rangeOmega = startOmega - Double.parseDouble(getMetadataValue("stopOmega").toString()); GDAMetadata.put("NXsample:rotation_start",startOmega); GDAMetadata.put("NXsample:rotation_start@units","degree"); GDAMetadata.put("NXsample:rotation_range",rangeOmega); GDAMetadata.put("NXsample:rotation_range@units", "degree"); //Exposure time GDAMetadata.put("NXsample:exposure_time", getMetadataValue("exposureTime")); GDAMetadata.put("NXsample:exposure_time@units", "seconds"); createMetadata(detectorOrigin, imageLength, imageWidth, pixelSizeX, pixelSizeY, euler, lambda, startOmega, rangeOmega); } catch (NumberFormatException e) { throw new ScanFileHolderException("There was a problem parsing numerical value from string", e); } catch (ScanFileHolderException e) { throw new ScanFileHolderException("A problem occoured parsing the internal metatdata into the GDA metadata", e); } return GDAMetadata; } private void createMetadata(double[] detectorOrigin, int imageLength, int imageWidth, double pixelSizeX, double pixelSizeY, Matrix3d euler, double lambda, double startOmega, double rangeOmega) throws ScanFileHolderException { DetectorProperties detProps = new DetectorProperties(new Vector3d(detectorOrigin), imageLength, imageWidth, pixelSizeX, pixelSizeY, euler); DiffractionCrystalEnvironment diffEnv = new DiffractionCrystalEnvironment(lambda, startOmega, rangeOmega, (Double) getMetadataValue("exposureTime")); metadata = new DiffractionMetadata(fileName, detProps, diffEnv); metadata.setMetadata(createStringMap()); } private Serializable getMetadataValue(String key) throws ScanFileHolderException { try { Serializable value = metadataTable.get(key); return value; } catch (Exception e) { throw new ScanFileHolderException("The keyword " + key + " was not found in the MAR header", e); } } private Map<String, String> createStringMap() { Map<String, String> ret = new HashMap<String,String>(7); for (String key : metadataTable.keySet()) { ret.put(key, metadataTable.get(key).toString().trim()); } return ret; } }