/* * Copyright 2016 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.xpdf.operations; import java.util.Map; import org.eclipse.dawnsci.analysis.api.metadata.IDiffractionMetadata; import org.eclipse.dawnsci.analysis.api.processing.OperationData; import org.eclipse.dawnsci.analysis.api.processing.OperationException; import org.eclipse.dawnsci.analysis.api.processing.OperationRank; import org.eclipse.dawnsci.analysis.api.tree.Attribute; import org.eclipse.dawnsci.analysis.api.tree.GroupNode; import org.eclipse.dawnsci.analysis.api.tree.IFindInTree; import org.eclipse.dawnsci.analysis.api.tree.NodeLink; import org.eclipse.dawnsci.analysis.api.tree.Tree; import org.eclipse.dawnsci.analysis.api.tree.TreeUtils; import org.eclipse.dawnsci.analysis.dataset.operations.AbstractOperation; import org.eclipse.dawnsci.analysis.dataset.slicer.SliceFromSeriesMetadata; import org.eclipse.dawnsci.hdf5.nexus.NexusFileHDF5; import org.eclipse.dawnsci.nexus.NXbeam; import org.eclipse.dawnsci.nexus.NXcontainer; import org.eclipse.dawnsci.nexus.NXdata; import org.eclipse.dawnsci.nexus.NXdetector; import org.eclipse.dawnsci.nexus.NXobject; import org.eclipse.dawnsci.nexus.NXsample; import org.eclipse.dawnsci.nexus.NXshape; import org.eclipse.dawnsci.nexus.NXslit; import org.eclipse.dawnsci.nexus.NexusException; import org.eclipse.dawnsci.nexus.NexusFile; import org.eclipse.dawnsci.nexus.NexusUtils; import org.eclipse.january.DatasetException; import org.eclipse.january.IMonitor; import org.eclipse.january.MetadataException; import org.eclipse.january.dataset.Comparisons; import org.eclipse.january.dataset.Dataset; import org.eclipse.january.dataset.DatasetUtils; import org.eclipse.january.dataset.IDataset; import org.eclipse.january.dataset.ILazyDataset; import org.eclipse.january.metadata.MaskMetadata; import org.eclipse.january.metadata.MetadataFactory; import uk.ac.diamond.scisoft.analysis.io.NexusDiffractionCalibrationReader; import uk.ac.diamond.scisoft.xpdf.XPDFBeamData; import uk.ac.diamond.scisoft.xpdf.XPDFBeamTrace; import uk.ac.diamond.scisoft.xpdf.XPDFComponentCylinder; import uk.ac.diamond.scisoft.xpdf.XPDFComponentGeometry; import uk.ac.diamond.scisoft.xpdf.XPDFComponentPlate; import uk.ac.diamond.scisoft.xpdf.XPDFDetector; import uk.ac.diamond.scisoft.xpdf.XPDFMetadataImpl; import uk.ac.diamond.scisoft.xpdf.XPDFSubstance; import uk.ac.diamond.scisoft.xpdf.XPDFTargetComponent; public class XPDFReadMetadataOperation extends AbstractOperation<XPDFReadMetadataModel, OperationData> { @Override protected OperationData process(IDataset input, IMonitor monitor) throws OperationException { // Get the SliceFromSeriesMetadata SliceFromSeriesMetadata ssm = input.getFirstMetadata(SliceFromSeriesMetadata.class); // Get the mask if (model.isReadMask()) readAndAddMask(input, ssm.getFilePath(), ssm.getParent()); // Get the detector calibration if (model.isReadDetectorCal()) readAndAddDetectorCalibration(input, ssm.getFilePath(), ssm.getParent()); if (model.isReadAnyXPDFMetadata()) { // Create the XPDF metadata object XPDFMetadataImpl xpdfMeta = new XPDFMetadataImpl(); // Get the NeXus file tree // IDataHolder dh; Tree tree; try { // dh = LoaderFactory.getData(ssm.getFilePath()); // tree = dh.getTree(); String nexusFilePath = ssm.getFilePath(); NexusFile nexusFile = NexusFileHDF5.openNexusFileReadOnly(nexusFilePath); tree = NexusUtils.loadNexusTree(nexusFile); } catch (Exception e1) { throw new OperationException(this, e1); } if (tree == null) throw new OperationException(this, "Error constructing file tree for " + ssm.getFilePath()); // sample details if (model.isReadSampleInfo()) { readAndAddSampleInfo(xpdfMeta, tree, ssm.getParent()); readDataParameters(xpdfMeta, tree, ssm.getParent()); } // container details and container data if (model.isReadContainerInfo() || model.isReadContainerData()) { readAndAddContainerInfo(xpdfMeta, tree, ssm.getParent()); } // empty beam data if (model.isReadBeamData()) { readAndAddBeamData(xpdfMeta, tree, ssm.getParent()); } // beam information (size, wavelength) if (model.isReadBeamInfo()) { readAndAddBeamInfo(xpdfMeta, tree, ssm.getParent()); } // detector physical details if (model.isReadDetectorInfo()) { readAndAddDetector(xpdfMeta, tree, ssm.getParent()); } input.setMetadata(xpdfMeta);; } return new OperationData(input); } private void readAndAddSampleInfo(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { // Get the map of names to samples NXsample nxample = getNXsampleFromTree(xpdfMeta, tree, parent); XPDFTargetComponent sampleCompo = new XPDFTargetComponent(nxample, null); sampleCompo.setSample(true); xpdfMeta.setSampleData(sampleCompo); } private void readDataParameters(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { XPDFBeamTrace sampleIntegration = traceFromTree(tree, false); xpdfMeta.setSampleTrace(sampleIntegration); } private XPDFBeamTrace traceFromTree(Tree tree, boolean addData) { // Get the first NXdata from the tree NXdata data = getFirstSomething(tree, "NXdata"); double countTime = data.getDouble("count_time"); XPDFBeamTrace componentIntegration = new XPDFBeamTrace(); componentIntegration.setAxisAngle(true); componentIntegration.setCountingTime(countTime); componentIntegration.setMonitorRelativeFlux(1.0); // FIXME: No value yet in the NeXus file, default to 1 // Get the data from the tree, and add it to the BeamTrace if (addData) { ILazyDataset iLazyDataset = data.getDataNode("data").getDataset(); Dataset dataset; try { dataset = DatasetUtils.sliceAndConvertLazyDataset(iLazyDataset); } catch (DatasetException e) { throw new OperationException(this, e); } componentIntegration.setTrace(dataset); } return componentIntegration; } /** * Gets the container metadata. * <p> * Using the inside_of_file_name attribute in the container data, the * container XPDFTargetComponent and XPDFBeamTrace are added to the XPDFMetadata. * @param xpdfMeta * metadata object to add the container to * @param tree * the tree containing the location of the container file * @param parent * the dataset of the sample */ private void readAndAddContainerInfo(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { GroupNode containerFileNameNode = getFirstSomething(tree, "NXcontainer"); String containerFileName = containerFileNameNode.getDataNode("inside_of_file_name").getString();//getAttribute("inside_of_file_name").getFirstElement(); // Now open the relevant file, and get the tree NexusFile containerFile; Tree containerTree; try { containerFile = NexusFileHDF5.openNexusFileReadOnly(containerFileName); containerTree = NexusUtils.loadNexusTree(containerFile); } catch (Exception e1) { throw new OperationException(this, e1); } if (tree == null) throw new OperationException(this, "Error constructing container file tree from " + containerFileName); // Component details (material, density &c.) of the container NXsample nxampleContainer = getNXsampleFromTree(null, containerTree, null); XPDFTargetComponent containerCompo = new XPDFTargetComponent(nxampleContainer, null); containerCompo.setSample(false); // Shape of the container NXcontainer container = nxampleContainer.getChild("container", NXcontainer.class); NXshape shape = container.getShape(); XPDFComponentGeometry geom = null; // TODO: should this be moved to a factory class? String shapeName = shape.getShapeScalar(); if ("nxcylinder".equalsIgnoreCase(shapeName)) { geom = new XPDFComponentCylinder(); geom.setStreamality(true, true); Dataset cylinderParameters = DatasetUtils.convertToDataset(shape.getSize()); double[] radii = new double[2]; radii[0] = cylinderParameters.getDouble(0, 0); radii[1] = cylinderParameters.getDouble(1, 0); geom.setDistances(radii[0], radii[1]); } else if ("nxbox".equalsIgnoreCase(shapeName)) { geom = new XPDFComponentPlate(); } containerCompo.getForm().setGeom(geom); xpdfMeta.addContainer(containerCompo); // Container data XPDFBeamTrace containerTrace = traceFromTree(containerTree, true); xpdfMeta.setContainerTrace(containerCompo, containerTrace); try { containerFile.close(); } catch (NexusException nE) { // Can't close the file? Oh well, never mind } } /** * Adds the empty beam data. * <p> * By recursing through all the containers, following the * 'inside_of_file_name' values, this method gets the data associated with * the outermost NeXus file. This is added to the XPDF metadata as the * empty beam data. * @param xpdfMeta * metadata object to add the data to * @param tree * the Nexus tree of the sample data * @param parent * sample data */ private void readAndAddBeamData(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { NexusFile beamFile, componentFile = null; Tree componentTree = tree, beamTree; while(true){ GroupNode containerFileNameNode = getFirstSomething(componentTree, "NXcontainer"); if (containerFileNameNode == null) { // No NXcontainer found. This must be the outermost container, the I15-1 beam beamTree = componentTree; beamFile = componentFile; break; } String componentFileName = containerFileNameNode.getDataNode("inside_of_file_name").getString(); // Now open the relevant file, and get the tree try { componentFile = NexusFileHDF5.openNexusFileReadOnly(componentFileName); componentTree = NexusUtils.loadNexusTree(componentFile); } catch (Exception e1) { throw new OperationException(this, e1); } try{ if (componentFile != null) componentFile.close(); } catch (Exception e1) { throw new OperationException(this, e1); } if (componentTree == null) throw new OperationException(this, "Error constructing container file tree from " + componentFileName); } // Empty beam data XPDFBeamTrace containerTrace = traceFromTree(beamTree, true); xpdfMeta.setEmptyTrace(containerTrace); try { if (beamFile != null) beamFile.close(); } catch (NexusException nE) { // Can't close the file? Oh well, never mind } } private void readAndAddBeamInfo(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { // Beam wavelength from the beam NXbeam beamNode = getFirstSomething(tree, "NXbeam"); XPDFBeamData beam = new XPDFBeamData(); beam.setBeamWavelength(beamNode.getIncident_wavelengthScalar()); // beam size from the slits // Get the beam_defining aperture in the experimental hutch, as part of the instrument NXslit definer = (NXslit) tree.getGroupNode().getGroupNode("entry1").getGroupNode("instrument").getGroupNode("experimental_hutch").getGroupNode("beam_defining_aperture"); beam.setBeamWidth((double) definer.getX_gapScalar()); beam.setBeamHeight((double) definer.getY_gapScalar()); xpdfMeta.setBeamData(beam); } // Get the mask private void readAndAddDetectorCalibration(IDataset input, String filePath, ILazyDataset parent) { // TODO: This method does not cope with multiple detectors IDiffractionMetadata diffraction; try { diffraction = NexusDiffractionCalibrationReader.getDiffractionMetadataFromNexus(filePath, parent); } catch (DatasetException e) { throw new OperationException(this, e); } if (diffraction != null) input.setMetadata(diffraction); } // Get the detector calibration private void readAndAddMask(IDataset input, String filePath, ILazyDataset parent) { // TODO: This method does not cope with multiple detectors IDataset firstMask; try { firstMask = NexusDiffractionCalibrationReader.getDetectorPixelMaskFromNexus(filePath, parent); } catch (Exception e) { throw new OperationException(this, e); } IDataset firstBooleanMask; if (firstMask != null) { firstBooleanMask = Comparisons.equalTo(firstMask, 0); MaskMetadata mm; try { mm = MetadataFactory.createMetadata(MaskMetadata.class, firstBooleanMask); } catch (MetadataException e) { throw new OperationException(this, e); } input.setMetadata(mm); } } // Add the detector information to the XPDF metadata private void readAndAddDetector(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { // get the node describing the detector. Hard coded, following the I15-1 Nexus spec GroupNode rootNode = tree.getGroupNode(), entryNode = rootNode.getGroupNode("entry1"), instrumentNode = entryNode.getGroupNode("instrument"), hutchNode = instrumentNode.getGroupNode("experimental_hutch"); GroupNode tectNode = hutchNode.getGroupNode("detector_1"); NXdetector nxtect = (NXdetector) tectNode; // simple values XPDFDetector xpdftect = new XPDFDetector(); // substance double density = nxtect.getDouble("sensor_density"); xpdftect.setSubstance(new XPDFSubstance(nxtect.getSensor_materialScalar(), nxtect.getSensor_materialScalar(), density, 1.0)); // thickness xpdftect.setThickness(nxtect.getSensor_thicknessScalar()); // Solid angle. TODO: calculate the true solid angle double solidAngle; solidAngle = xpdftect.getSolidAngle(); // failure is assigning zero, not throwing an Exception if (solidAngle == 0.0) solidAngle = 0.1; xpdftect.setSolidAngle(solidAngle); xpdfMeta.setDetector(xpdftect); } private NXsample getNXsampleFromTree(XPDFMetadataImpl xpdfMeta, Tree tree, ILazyDataset parent) { Map<String, NodeLink> nodeMap = TreeUtils.treeBreadthFirstSearch(tree.getGroupNode(), getSample(), true, null); if (nodeMap.size() < 1) throw new OperationException(this, "Sample information requested, but no NXsample data was found."); if (nodeMap.size() > 1) throw new OperationException(this, "Multiple NXsample data found. Giving up."); // Get the first (only) NXsample GroupNode sampleNode = (GroupNode) nodeMap.values().toArray( new NodeLink[nodeMap.size()])[0].getDestination(); NXsample nxample = (NXsample) sampleNode; return nxample; } // find me a sample private static IFindInTree getSample() { return getSomething("NXsample"); } // find me anything private static IFindInTree getSomething(String NXclass) { return new IFindInTree() { @Override public boolean found(NodeLink node) { if (node.getDestination() instanceof GroupNode) { Attribute nxClass = ((GroupNode) node.getDestination()).getAttribute("NX_class"); if (nxClass != null && nxClass.getFirstElement() != null && nxClass.getFirstElement().equals(NXclass)) return true; } return false; } }; } private <T extends NXobject> T getFirstSomething(Tree tree, String NXclass) { Map<String, NodeLink> nodeMap = TreeUtils.treeBreadthFirstSearch(tree.getGroupNode(), getSomething(NXclass), true, null); if (nodeMap.size() == 0) return null; GroupNode node = (GroupNode) nodeMap.values().toArray( new NodeLink[nodeMap.size()])[0].getDestination(); // The cast has already been checked, since NXobject is a derived class of GroupNode @SuppressWarnings("unchecked") T tt = (T) node; return tt; } @Override public String getId() { return "uk.ac.diamond.scisoft.xpdf.operations.XPDFReadMetadataOperation"; } @Override public OperationRank getInputRank() { return OperationRank.TWO; } @Override public OperationRank getOutputRank() { return OperationRank.TWO; } }