/*
* 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.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.dawnsci.analysis.api.io.ScanFileHolderException;
import org.eclipse.january.dataset.Dataset;
import org.eclipse.january.dataset.DatasetFactory;
import org.eclipse.january.dataset.DatasetUtils;
import org.eclipse.january.dataset.IDataset;
import org.eclipse.january.dataset.ILazyDataset;
import org.eclipse.january.dataset.ShortDataset;
import org.eclipse.january.metadata.Metadata;
/**
* Loader to allow the XMap files to be loaded in
*/
public class XMapLoader extends AbstractFileLoader {
public XMapLoader() {
}
/**
* Constructor which takes a filename
*
* @param InputFileName
*/
public XMapLoader(String InputFileName) {
fileName = InputFileName;
}
@Override
protected void clearMetadata() {
}
/**
* Implemented method which actually loads in the data. Using the DXD-xMAP : Mapping buffer Specification
*
* @return the loaded data
* @throws ScanFileHolderException
*/
@Override
public DataHolder loadFile() throws ScanFileHolderException {
ZipFile zipFile = null;
DataHolder ldh = new DataHolder();
ILazyDataset[] data = new ILazyDataset[CHANNELS];
int[] size = new int[CHANNELS];
int[] count = new int[CHANNELS];
try {
zipFile = new ZipFile(fileName);
for (Enumeration<?> e = zipFile.entries(); e.hasMoreElements();) {
InputStream in;
try {
in = zipFile.getInputStream((ZipEntry) e.nextElement());
} catch (IOException e1) {
throw new ScanFileHolderException("Zip entry not valid", e1);
}
boolean finished = false;
BufferData bufferedData = new BufferData();
try {
bufferedData.read(in);
} catch (IOException e2) {
throw new ScanFileHolderException("The main header was not read correctly", e2);
}
while (!finished) {
if (bufferedData.mappingMode == 1) {
// i think there are a number of scans at this point
for (int i = 0; i < bufferedData.numberOfPixelsInBuffer; i++) {
// we are running in mapping mode and should therefore read in the data in this way
MappingMode1Data mappingMode1Data = new MappingMode1Data();
try {
mappingMode1Data.read(in, !loadLazily);
} catch (IOException e2) {
throw new ScanFileHolderException("The zip file was not opened correctly", e2);
}
// System.out.println(mappingMode1Data);
for (int c = 0; c < CHANNELS; c++) {
if (mappingMode1Data.channelSize[c] > 0) {
size[c] = mappingMode1Data.channelSize[c];
if (!loadLazily) {
ShortDataset temp = DatasetFactory.createFromObject(ShortDataset.class, mappingMode1Data.channelSpectrum[c], 1,
mappingMode1Data.channelSize[c]);
if (data[c] == null) {
// create the dataset
data[c] = temp;
} else {
data[c] = DatasetUtils.append((IDataset) data[c], temp, 0);
}
}
count[c]++;
}
}
}
}
try {
bufferedData.read(in);
} catch (IOException e2) {
finished = true;
}
// System.out.println(bufferedData);
}
}
if (loadLazily) {
for (int c = 0; c < CHANNELS; c++) {
data[c] = createLazyDataset("Channel" + c, Dataset.INT16, new int[] { count[c], size[c] },
new XMapLoader(fileName));
}
}
} catch (Exception e) {
throw new ScanFileHolderException("The zip file was not opened correctly", e);
} finally {
try {
if (zipFile != null)
zipFile.close();
} catch (IOException e) {
throw new ScanFileHolderException("The zip file could not be closed correctly", e);
}
}
for (int c = 0; c < CHANNELS; c++) {
ldh.addDataset("Channel" + c, data[c]);
}
if (loadMetadata) {
metadata = new Metadata();
metadata.setFilePath(fileName);
for (int c = 0; c < CHANNELS; c++) {
metadata.addDataInfo("Channel" + c, data[c].getShape());
}
}
return ldh;
}
final static int CHANNELS = 4;
private class BufferData {
int tagWord0;
int tagWord1;
int bufferHeadderSize;
int mappingMode;
int runNumber;
int sequentialBufferNumber;
int bufferID;
int numberOfPixelsInBuffer;
int startingPixelNumber;
int moduleSerialNumber;
int[] detectorChannel = new int[CHANNELS];
int[] detectorElementChannel = new int[CHANNELS];
int[] channelSize = new int[CHANNELS];
int bufferErrors;
void read(InputStream in) throws IOException {
ByteBuffer buf = new ByteBuffer(256, in);
tagWord0 = buf.readShort();
tagWord1 = buf.readShort();
bufferHeadderSize = buf.readShort();
mappingMode = buf.readShort();
runNumber = buf.readShort();
sequentialBufferNumber = buf.readInt();
bufferID = buf.readShort();
numberOfPixelsInBuffer = buf.readShort();
startingPixelNumber = buf.readInt();
moduleSerialNumber = buf.readShort();
for (int c = 0; c < CHANNELS; c++) {
detectorChannel[c] = buf.readShort();
detectorElementChannel[c] = buf.readShort();
}
for (int c = 0; c < CHANNELS; c++) {
channelSize[c] = buf.readShort();
}
bufferErrors = buf.readShort();
buf.skipWords( 231);
}
/**
* To string method
*/
@Override
public String toString() {
String output = "";
output += ("tagWord0 " + tagWord0 + "\n");
output += ("tagWord1 " + tagWord1 + "\n");
output += ("bufferHeadderSize " + bufferHeadderSize + "\n");
output += ("mappingMode " + mappingMode + "\n");
output += ("runNumber " + runNumber + "\n");
output += ("sequentialBufferNumber " + sequentialBufferNumber + "\n");
output += ("bufferID " + bufferID + "\n");
output += ("numberOfPixelsInBuffer " + numberOfPixelsInBuffer + "\n");
output += ("startingPixelNumber " + startingPixelNumber + "\n");
output += ("moduleSerialNumber " + moduleSerialNumber + "\n");
for (int c = 0; c < CHANNELS; c++) {
output += "detectorChannel " + c + ": " + detectorChannel[c] + "\n";
output += "detectorElementChannel " + c + ": " + detectorElementChannel[c] + "\n";
}
for (int c = 0; c < CHANNELS; c++) {
output += "Channel " + c + " Size = " + channelSize[c] + "\n";
}
output += ("bufferErrors " + bufferErrors + "\n");
return output;
}
}
private class ChannelStatistics {
float realtime;
float livetime;
int triggers;
int outputEvents;
void read(ByteBuffer buf) { // 16 bytes
realtime = buf.readFloat();
livetime = buf.readFloat();
triggers = buf.readInt();
outputEvents = buf.readInt();
}
/**
* Overloaded tostring method
*
* @return output
*/
@Override
public String toString() {
return "Realtime " + realtime + ": Livetime " + livetime + ": Triggers " + triggers + ": Output Events "
+ outputEvents;
}
}
private class MappingMode1Data {
// int spaceToStart;
int tagWord0;
int tagWord1;
int pixelheaderSize;
int mappingMode;
int pixelNumber;
int totalPixelBlockSize;
int[] channelSize = new int[CHANNELS];
ChannelStatistics[] channelStatistics = new ChannelStatistics[CHANNELS];
short[][] channelSpectrum = new short[CHANNELS][];
short[] readSpectrum(InputStream in, int sizeOfSpectrum) throws IOException {
short[] spectrum = new short[sizeOfSpectrum];
ByteBuffer buf = new ByteBuffer(sizeOfSpectrum, in);
for (int i = 0; i < sizeOfSpectrum; i++) {
spectrum[i] = (short) buf.readShort();
}
return spectrum;
}
void read(InputStream in, boolean includeData) throws IOException {
ByteBuffer buf = new ByteBuffer(256, in);
tagWord0 = buf.readShort();
tagWord1 = buf.readShort();
pixelheaderSize = buf.readShort();
mappingMode = buf.readShort();
pixelNumber = buf.readInt();
totalPixelBlockSize = buf.readInt();
for (int c = 0; c < CHANNELS; c++) {
channelSize[c] = buf.readShort();
}
if (includeData) {
buf.skipWords(20);
for (int c = 0; c < CHANNELS; c++) {
channelStatistics[c] = new ChannelStatistics();
channelStatistics[c].read(buf);
}
buf.skipWords(192);
for (int c = 0; c < CHANNELS; c++) {
channelSpectrum[c] = readSpectrum(in, channelSize[c]);
}
} else {
// 4x2 + 2x4 + 4x2 + 20x2 + 4x16 + 192x2 = 512 bytes already read in
// (channel0Size + ...) x 2
in.skip((channelSize[0] + channelSize[1] + channelSize[2] + channelSize[3]) * 2);
}
}
/**
* Overloaded tostring method
*
* @return output
*/
@Override
public String toString() {
String output = "";
output += "Tag Word 0 = " + tagWord0 + "\n";
output += "Tag Word 1 = " + tagWord1 + "\n";
output += "Pixel Header Size = " + pixelheaderSize + "\n";
output += "Mapping Mode = " + mappingMode + "\n";
output += "Pixel Number = " + pixelNumber + "\n";
output += "Total Pixel Block Size = " + totalPixelBlockSize + "\n";
for (int c = 0; c < CHANNELS; c++) {
output += "Channel " + c + " Size = " + channelSize[c] + "\n";
}
for (int c = 0; c < CHANNELS; c++) {
output += "Channel " + c + " Statistics = " + channelStatistics[c] + "\n";
}
return output;
}
}
private class ByteBuffer {
byte[] buffer;
int pos = 0;
/**
* Constructor which specifies the details needed by the ByteBuffer
* @param size The amount of data to be read.
* @param in The input stream to read from.
* @throws IOException If there is any issue with the stream
*/
public ByteBuffer(int size, InputStream in) throws IOException {
buffer = new byte[size*2];
if ( in.read(buffer) == -1) {
throw new IOException("End of File");
}
pos = 0;
}
/**
* reads a short from the stream
* @return the read short.
*/
public int readShort() {
int a = ( 0xFF & buffer[pos]);
pos++;
int b = ( 0xFF & buffer[pos]);
pos++;
return (b << 8) + a;
}
/**
* reads an int from the stream
* @return the read integer
*/
public int readInt() {
int a = ( 0xFF & buffer[pos]);
pos++;
int b = ( 0xFF & buffer[pos]);
pos++;
int c = ( 0xFF & buffer[pos]);
pos++;
int d = ( 0xFF & buffer[pos]);
pos++;
return (d << 24) + (c << 16) + (b << 8) + a;
}
/**
* reads a float from the stream
* @return the read Float
*/
public float readFloat() {
return Float.intBitsToFloat(readInt());
}
/**
* Skips a number of words in the stream, as there are several small gaps in the data file.
* @param numberOfWordsToSkip The number of words to skip
*/
public void skipWords(int numberOfWordsToSkip) {
pos += numberOfWordsToSkip*2;
}
}
}