/*
* 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.FileOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import org.eclipse.dawnsci.analysis.api.io.IDataHolder;
import org.eclipse.dawnsci.analysis.api.io.IFileSaver;
import org.eclipse.dawnsci.analysis.api.io.ScanFileHolderException;
import org.eclipse.january.dataset.BooleanDataset;
import org.eclipse.january.dataset.ByteDataset;
import org.eclipse.january.dataset.ComplexDoubleDataset;
import org.eclipse.january.dataset.ComplexFloatDataset;
import org.eclipse.january.dataset.Dataset;
import org.eclipse.january.dataset.DatasetUtils;
import org.eclipse.january.dataset.DoubleDataset;
import org.eclipse.january.dataset.FloatDataset;
import org.eclipse.january.dataset.IndexIterator;
import org.eclipse.january.dataset.IntegerDataset;
import org.eclipse.january.dataset.LongDataset;
import org.eclipse.january.dataset.ShortDataset;
/**
* Save datasets in a Diamond specific raw format
*
* All this is done in little-endian:
*
* File format tag: 0x0D1A05C1 when read as a 32-bit word in big-endian (4bytes - stands for Diamond Scisoft)
* Dataset type: (1byte) (0 - bool, 1 - int8, 2 - int16, 3 - int32, 4 - int64, 5 - float32,
* 6 - float64, 7 - complex64, 8 - complex128)
* array datasets supported but with single element dataset types
* Item size: (1 unsigned byte - number of elements per data item)
* Rank: (1 unsigned byte)
* Shape: (rank*4-byte unsigned word)
* Name: (length in bytes as 2-byte unsigned word, utf8 string)
* Pad: to round up header to multiple of 4 bytes
* Data: (little-endian raw dump, row major order)
*
*/
public class RawBinarySaver implements IFileSaver {
private String filename = "";
private static int formatTag = 0xC1051A0D; // this is 0x0D1A05C1 in big endian
/**
* @return format tag for identifying a Diamond specific raw format
*/
public static int getFormatTag() {
return formatTag;
}
/**
* Takes the dataset from a scan file holder and save it as our own Diamond Scisoft format
*
* @param filename
*/
public RawBinarySaver(String filename) {
this.filename = filename;
}
@Override
public void saveFile(IDataHolder dh) throws ScanFileHolderException {
File f = null;
final int imax = dh.size();
for (int i = 0; i < imax; i++) {
try {
String name = null;
String end = null;
if (imax == 1) {
name = filename;
} else {
try {
name = filename.substring(0, (filename.lastIndexOf(".")));
end = filename.substring(filename.lastIndexOf("."));
} catch (Exception e) {
name = filename;
}
NumberFormat format = new DecimalFormat("00000");
name = name + format.format(i + 1) + end;
}
f = new File(name);
} catch (Exception e) {
throw new ScanFileHolderException("Error saving file '" + filename + "'", e);
}
Dataset sdata = DatasetUtils.convertToDataset(dh.getDataset(i));
int dtype = sdata.getDType();
switch (dtype) {
case Dataset.ARRAYINT8:
dtype = Dataset.INT8;
break;
case Dataset.ARRAYINT16:
dtype = Dataset.INT16;
break;
case Dataset.ARRAYINT32:
dtype = Dataset.INT32;
break;
case Dataset.ARRAYINT64:
dtype = Dataset.INT64;
break;
case Dataset.ARRAYFLOAT32:
dtype = Dataset.FLOAT32;
break;
case Dataset.ARRAYFLOAT64:
dtype = Dataset.FLOAT64;
break;
}
int is = sdata.getElementsPerItem();
if (is > 255) {
throw new ScanFileHolderException("Number of elements in each item exceeds allowed maximum of 255");
}
byte isize = (byte) is;
String dataName = sdata.getName();
byte[] name = null;
try {
name = dataName.getBytes("UTF-8");
int nlen = dataName.length() - 2;
while (name.length > 65535) { // truncate if necessary
name = dataName.substring(0, nlen).getBytes("UTF8");
nlen--;
}
} catch (UnsupportedEncodingException e1) {
throw new ScanFileHolderException("Problem dealing with dataset name", e1);
}
int[] shape = sdata.getShape();
if (shape.length > 255) {
throw new ScanFileHolderException("Rank exceeds 255!");
}
byte rank = (byte) shape.length;
int hdrSize = 4 + 1 + 1 + 1 + rank*4 + 2 + name.length;
hdrSize = ((hdrSize+3)/4) * 4; // round up to multiple of 4 bytes
try {
FileOutputStream fout = new FileOutputStream(f);
FileChannel fc = fout.getChannel();
ByteBuffer hdrBuffer = ByteBuffer.allocateDirect(hdrSize);
hdrBuffer.order(ByteOrder.LITTLE_ENDIAN);
hdrBuffer.putInt(formatTag);
hdrBuffer.put((byte) dtype);
hdrBuffer.put(isize);
hdrBuffer.put(rank);
for (int j = 0; j < rank; j++)
hdrBuffer.putInt(shape[j]);
hdrBuffer.putShort((short) name.length);
if (name.length > 0)
hdrBuffer.put(name);
while (hdrBuffer.position() < hdrSize)
hdrBuffer.put((byte) 0);
hdrBuffer.rewind();
while (hdrBuffer.hasRemaining())
fc.write(hdrBuffer);
ByteBuffer dbBuffer = saveRawDataset(sdata, dtype, isize);
dbBuffer.rewind();
while (dbBuffer.hasRemaining())
fc.write(dbBuffer);
fc.close();
fout.close();
} catch (Exception e) {
throw new ScanFileHolderException("Error saving file '" + filename + "'", e);
}
}
}
/**
* Saves the dataset to the ByteBuffer provided in a raw format, ie no headers
* @param sdata Dataset to save
* @param dtype Dataset type for save purpose
* @param isize Each entry item size
* @return the allocated ByteBuffer where the data is saved
*/
public static ByteBuffer saveRawDataset(Dataset sdata, int dtype, byte isize) {
ByteBuffer dbBuffer = ByteBuffer.allocateDirect(sdata.getNbytes());
dbBuffer.order(ByteOrder.LITTLE_ENDIAN);
switch (dtype) {
case Dataset.BOOL:
BooleanDataset b = (BooleanDataset) sdata;
boolean[] dataBln = b.getData();
IndexIterator iter = b.getIterator();
while (iter.hasNext()) {
dbBuffer.put((byte) (dataBln[iter.index] ? 1 : 0));
}
break;
case Dataset.INT8:
ByteDataset i8 = (ByteDataset) sdata;
byte[] dataB = i8.getData();
iter = i8.getIterator();
if (isize == 1) {
while (iter.hasNext()) {
dbBuffer.put(dataB[iter.index]);
}
} else {
while (iter.hasNext()) {
for (int j = 0; j < isize; j++)
dbBuffer.put(dataB[iter.index+j]);
}
}
break;
case Dataset.INT16:
ShortDataset i16 = (ShortDataset) sdata;
ShortBuffer sDataBuffer = dbBuffer.asShortBuffer();
short[] dataS = i16.getData();
iter = i16.getIterator();
if (isize == 1) {
while (iter.hasNext()) {
sDataBuffer.put(dataS[iter.index]);
}
} else {
while (iter.hasNext()) {
for (int j = 0; j < isize; j++)
sDataBuffer.put(dataS[iter.index+j]);
}
}
break;
case Dataset.INT32:
IntegerDataset i32 = (IntegerDataset) sdata;
IntBuffer iDataBuffer = dbBuffer.asIntBuffer();
int[] dataI = i32.getData();
iter = i32.getIterator();
if (isize == 1) {
while (iter.hasNext()) {
iDataBuffer.put(dataI[iter.index]);
}
} else {
while (iter.hasNext()) {
for (int j = 0; j < isize; j++)
iDataBuffer.put(dataI[iter.index+j]);
}
}
break;
case Dataset.INT64:
LongDataset i64 = (LongDataset) sdata;
LongBuffer lDataBuffer = dbBuffer.asLongBuffer();
long[] dataL = i64.getData();
iter = i64.getIterator();
if (isize == 1) {
while (iter.hasNext()) {
lDataBuffer.put(dataL[iter.index]);
}
} else {
while (iter.hasNext()) {
for (int j = 0; j < isize; j++)
lDataBuffer.put(dataL[iter.index+j]);
}
}
break;
case Dataset.FLOAT32:
FloatDataset f32 = (FloatDataset) sdata;
FloatBuffer fDataBuffer = dbBuffer.asFloatBuffer();
float[] dataFlt = f32.getData();
iter = f32.getIterator();
if (isize == 1) {
while (iter.hasNext()) {
fDataBuffer.put(dataFlt[iter.index]);
}
} else {
while (iter.hasNext()) {
for (int j = 0; j < isize; j++)
fDataBuffer.put(dataFlt[iter.index+j]);
}
}
break;
case Dataset.FLOAT64:
DoubleDataset f64 = (DoubleDataset) sdata;
DoubleBuffer dataBuffer = dbBuffer.asDoubleBuffer();
double[] dataDbl = f64.getData();
iter = f64.getIterator();
if (isize == 1) {
while (iter.hasNext()) {
dataBuffer.put(dataDbl[iter.index]);
}
} else {
while (iter.hasNext()) {
for (int j = 0; j < isize; j++)
dataBuffer.put(dataDbl[iter.index+j]);
}
}
break;
case Dataset.COMPLEX64:
ComplexFloatDataset c64 = (ComplexFloatDataset) sdata;
fDataBuffer = dbBuffer.asFloatBuffer();
dataFlt = c64.getData();
iter = c64.getIterator();
while (iter.hasNext()) {
fDataBuffer.put(dataFlt[iter.index]);
fDataBuffer.put(dataFlt[iter.index+1]);
}
break;
case Dataset.COMPLEX128:
ComplexDoubleDataset c128 = (ComplexDoubleDataset) sdata;
dataBuffer = dbBuffer.asDoubleBuffer();
dataDbl = c128.getData();
iter = c128.getIterator();
while (iter.hasNext()) {
dataBuffer.put(dataDbl[iter.index]);
dataBuffer.put(dataDbl[iter.index+1]);
}
break;
}
return dbBuffer;
}
}