/*
* 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.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.Dataset;
import org.eclipse.january.dataset.DatasetUtils;
import org.eclipse.january.dataset.IDataset;
import uk.ac.diamond.scisoft.analysis.io.NumPyFile.DataTypeInfo;
/**
* Writes files in npy format as defined here; http://svn.scipy.org/svn/numpy/trunk/doc/neps/npy-format.txt
*/
public class NumPyFileSaver implements IFileSaver {
private String filename = "";
protected boolean unsigned = false;
/**
* Takes the dataset from a scan file holder and save it in npy format
*
* @param filename
*/
public NumPyFileSaver(String filename) {
this.filename = filename;
}
/**
* Takes the dataset from a scan file holder and save it in npy format
*
* @param filename
* @param unsigned
*/
public NumPyFileSaver(String filename, boolean unsigned) {
this.filename = filename;
this.unsigned = unsigned;
}
@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);
}
IDataset dataset = dh.getDataset(i);
if (dataset == null) {
throw new ScanFileHolderException("Dataset null at index " + i + " unsupported");
}
Dataset sdata = DatasetUtils.convertToDataset(dataset);
int dtype = sdata.getDType();
DataTypeInfo dataTypeInfo;
dataTypeInfo = unsigned ? NumPyFile.unsignedNumPyTypeMap.get(dtype) : NumPyFile.numPyTypeMap.get(dtype);
if (dataTypeInfo == null) { // ignore unsigned flag if not found
dataTypeInfo = NumPyFile.numPyTypeMap.get(dtype);
}
if (dataTypeInfo == null) {
throw new ScanFileHolderException("Unsupported data types for NumPy File Saver");
}
int is = sdata.getElementsPerItem();
if (is > 255) {
throw new ScanFileHolderException("Number of elements in each item exceeds allowed maximum of 255");
}
if (unsigned) {
dtype = dataTypeInfo.dType;
sdata = DatasetUtils.cast(sdata, dtype);
}
byte isize = (byte) is;
int[] shape = sdata.getShape();
if (shape.length > 255) {
throw new ScanFileHolderException("Rank exceeds 255!");
}
StringBuilder shapeTuple = new StringBuilder();
for (int j = 0; j < shape.length; j++) {
shapeTuple.append(shape[j]);
shapeTuple.append(", ");
}
shapeTuple.deleteCharAt(shapeTuple.length() - 1); // remove final space
if (shape.length > 1) {
shapeTuple.deleteCharAt(shapeTuple.length() - 1); // remove final comma
}
// format looks like this, and always in this order:
// {'descr': '<i4', 'fortran_order': False, 'shape': (100,), }
// or:
// {'descr': '<i4', 'fortran_order': False, 'shape': (100, 100), }
StringBuilder formatBuilder = new StringBuilder();
formatBuilder.append("{'descr': '");
formatBuilder.append(dataTypeInfo.numPyType); // e.g. <i4
formatBuilder.append("', 'fortran_order': False, 'shape': (");
formatBuilder.append(shapeTuple); // e.g. 100, or 100, 100
formatBuilder.append("), }");
/* header size */
int hdrSize = formatBuilder.length() + NumPyFile.magic.length + 2 + 1; // +1 for the newline
// header is padded with spaces and exactly one newline to reach multiple of 16 bytes
int padding_required = 16 - (hdrSize % 16);
hdrSize += padding_required;
while (padding_required-- > 0) {
formatBuilder.append(' ');
}
formatBuilder.append('\n');
byte[] formatBytes;
try {
formatBytes = formatBuilder.toString().getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new ScanFileHolderException("Impossible error, US-ASCII is always available?", e);
}
try {
FileOutputStream fout = new FileOutputStream(f);
FileChannel fc = fout.getChannel();
ByteBuffer hdrBuffer = ByteBuffer.allocateDirect(hdrSize);
hdrBuffer.order(ByteOrder.LITTLE_ENDIAN);
for (int j = 0; j < NumPyFile.magic.length; j++) {
hdrBuffer.put(NumPyFile.magic[j]);
}
hdrBuffer.putShort((short) formatBytes.length);
for (int j = 0; j < formatBytes.length; j++) {
hdrBuffer.put(formatBytes[j]);
}
hdrBuffer.rewind();
while (hdrBuffer.hasRemaining())
fc.write(hdrBuffer);
ByteBuffer dbBuffer = RawBinarySaver.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);
}
}
}
}