/* * 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.image; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.eclipse.dawnsci.analysis.api.io.ScanFileHolderException; import org.eclipse.dawnsci.analysis.dataset.roi.RectangularROI; import org.eclipse.dawnsci.analysis.dataset.roi.RectangularROIList; import org.eclipse.dawnsci.hdf5.HDF5FileFactory; import org.eclipse.dawnsci.hdf5.HDF5Utils; import org.eclipse.january.DatasetException; import org.eclipse.january.IMonitor; import org.eclipse.january.dataset.Dataset; import org.eclipse.january.dataset.DatasetFactory; import org.eclipse.january.dataset.DatasetUtils; import org.eclipse.january.dataset.DoubleDataset; import org.eclipse.january.dataset.IDataset; import org.eclipse.january.dataset.ILazyDataset; import org.eclipse.january.dataset.ILazyWriteableDataset; import org.eclipse.january.dataset.Slice; import org.eclipse.january.dataset.SliceND; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.diamond.scisoft.analysis.dataset.function.RegisterImage2; import uk.ac.diamond.scisoft.analysis.io.LoaderFactory; import uk.ac.diamond.scisoft.analysis.utils.FileUtils; public class AlignImages { private static final Logger logger = LoggerFactory.getLogger(AlignImages.class); /** * Align images * @param images datasets * @param shifted images * @param roi * @param preShift * @param monitor * @return shifts */ public static List<double[]> align(final IDataset[] images, final List<Dataset> shifted, final RectangularROI roi, double[] preShift, IMonitor monitor) { List<IDataset> list = new ArrayList<>(); Collections.addAll(list, images); RegisterImage2 registerImage = new RegisterImage2(); registerImage.setRectangle(roi); registerImage.setMonitor(monitor); // low-pass filter by averaging 9x9 pixels DoubleDataset filter = DatasetFactory.ones(DoubleDataset.class, 9, 9); filter.imultiply(1./ ((Number) filter.sum()).doubleValue()); registerImage.setFilter(filter); registerImage.setShiftImage(shifted != null); List<Dataset> output = registerImage.value(images); final int length = output.size(); final List<double[]> shift = new ArrayList<>(); for (int i = 0; i < length; i+=2) { double[] s = (double[]) output.get(i).getBuffer(); // We add the preShift to the shift data if (preShift != null) { s[0] += preShift[0]; s[1] += preShift[1]; } shift.add(s); if (shifted != null) { Dataset data = output.get(i + 1); data.setName("aligned_" + images[i/2].getName()); shifted.add(data); } } return shift; } /** * * @param files * @param shifted images * @param roi * @param preShift * @return shifts */ public static List<double[]> align(final String[] files, final List<Dataset> shifted, final RectangularROI roi, final double[] preShift, IMonitor monitor) { IDataset[] images = new IDataset[files.length]; for (int i = 0; i < files.length; i++) { try { images[i] = LoaderFactory.getData(files[i], false, null).getDataset(0); } catch (Exception e) { logger.error("Cannot load file {}", files[i]); throw new IllegalArgumentException("Cannot load file " + files[i]); } } return align(images, shifted, roi, preShift, monitor); } /** * <p> * If mode = 0 then alignment routine is modeless and uses all the images at once. * @param data * original list of dataset to be aligned * @param shifts * output where to put resulting shifts * @param roi * rectangular ROI used for the alignment * @param mode * number of columns used: can be 0, 2 or 4 * @param monitor * @return aligned list of dataset */ public static List<IDataset> alignWithROI(List<IDataset> data, List<List<double[]>> shifts, RectangularROI roi, int mode, IMonitor monitor) { if (mode == 0) { return alignWithROI(data, shifts, roi, monitor); } int nsets = data.size() / mode; RectangularROIList rois = new RectangularROIList(); rois.add(roi); if (shifts == null) shifts = new ArrayList<>(); List<IDataset> shiftedImages = new ArrayList<>(); int nr = rois.size(); if (nr > 0) { if (nr < mode) { // clean up roi list if (mode == 2) { rois.add(rois.get(0)); } else { switch (nr) { case 1: rois.add(rois.get(0)); rois.add(rois.get(0)); rois.add(rois.get(0)); break; case 2: case 3: rois.add(2, rois.get(0)); rois.add(3, rois.get(1)); break; } } } IDataset[] tImages = new IDataset[nsets]; List<Dataset> shifted = new ArrayList<>(nsets); // align first images across columns: // Example: [0,1,2]-[3,4,5]-[6,7,8]-[9,10,11] for 12 images on 4 columns // with images 0,3,6,9 as the top images of each column. Dataset anchor = null; int index = 0; for (int p = 0; p < mode; p++) { RectangularROI croi = rois.get(p); for (int i = 0; i < nsets; i++) { tImages[i] = data.get(index++); } shifted.clear(); // align a column List<double[]> nshifts = align(tImages, shifted, croi, null, monitor); // create anchor by summing Dataset canchor = DatasetFactory.zeros(DoubleDataset.class, tImages[0].getShape()); for (Dataset d : shifted) { canchor.iadd(d); } if (anchor == null) { anchor = canchor; } else { // align with first column's anchor double[] topShift = align(new IDataset[] {anchor, canchor}, null, croi, null, monitor).get(1); logger.info("Top shift: {}", Arrays.toString(topShift)); // align with new pre-shift if (monitor != null) { monitor.subTask("Re-shift images to first image"); } for (int i = 0; i < nsets; i++) { double[] cshift = nshifts.get(i); cshift[0] += topShift[0]; cshift[1] += topShift[1]; logger.info("New shifts for {}: {}", i, Arrays.toString(cshift)); Dataset cimage = RegisterImage2.shiftImage(DatasetUtils.convertToDataset(tImages[i]), cshift); cimage.setName(shifted.get(i).getName()); shifted.set(i, cimage); if (monitor != null) { if (monitor.isCancelled()) { return shiftedImages; } monitor.worked(1); } } } shifts.add(nshifts); shiftedImages.addAll(shifted); if (monitor != null) { if (monitor.isCancelled()) return shiftedImages; monitor.worked(1); } } } return shiftedImages; } /** * @param data * original list of dataset to be aligned * @param shifts * output where to put resulting shifts * @param roi * rectangular ROI used for the alignment * @param monitor * @return aligned list of dataset */ private static List<IDataset> alignWithROI(List<IDataset> data, List<List<double[]>> shifts, RectangularROI roi, IMonitor monitor) { if (shifts == null) shifts = new ArrayList<>(); int nsets = data.size(); List<IDataset> shiftedImages = new ArrayList<>(nsets); IDataset[] tImages = data.toArray(new IDataset[0]); List<Dataset> shifted = new ArrayList<>(nsets); List<double[]> nshifts = align(tImages, shifted, roi, null, monitor); shifts.add(nshifts); shiftedImages.addAll(shifted); return shiftedImages; } /** * Aligns images from a lazy dataset and returns a lazy dataset. This alignment process saves the aligned data in an * hdf5 file saved on disk and this method can be used without running into a MemoryOverflowError. * <p> * If mode = 0 then alignment routine is modeless and uses all the images at once. * @param data * original list of dataset to be aligned * @param shifts * output where to put resulting shifts * @param roi * rectangular ROI used for the alignment * @param mode * number of columns used: can be 0, 2 or 4 * @param monitor * @return aligned list of dataset */ public static ILazyDataset alignLazyWithROI(ILazyDataset data, List<List<double[]>> shifts, RectangularROI roi, int mode, IMonitor monitor) { if (mode == 0) { return alignLazyWithROI(data, shifts, roi, monitor); } int nsets = data.getShape()[0] / mode; RectangularROIList rois = new RectangularROIList(); rois.add(roi); if (shifts == null) shifts = new ArrayList<>(); // save on a temp file String file = FileUtils.getTempFilePath("aligned.h5"); String path = "/entry/data/"; String name = "aligned"; File tmpFile = new File(file); if (tmpFile.exists()) { try { HDF5FileFactory.releaseFile(file, true); } catch (ScanFileHolderException e) { logger.error("Could not close file {}", file, e); } tmpFile.delete(); } int[] shape = data.getShape(); int[] chunking = new int[] {1, shape[1], shape[2]}; ILazyWriteableDataset lazy = HDF5Utils.createLazyDataset(file, path, name, shape, null, chunking, Dataset.FLOAT32, null, false); int nr = rois.size(); if (nr > 0) { if (nr < mode) { // clean up roi list if (mode == 2) { rois.add(rois.get(0)); } else { switch (nr) { case 1: rois.add(rois.get(0)); rois.add(rois.get(0)); rois.add(rois.get(0)); break; case 2: case 3: rois.add(2, rois.get(0)); rois.add(3, rois.get(1)); break; } } } IDataset[] tImages = new IDataset[nsets]; List<Dataset> shifted = new ArrayList<>(nsets); // align first images across columns: // Example: [0,1,2]-[3,4,5]-[6,7,8]-[9,10,11] for 12 images on 4 columns // with images 0,3,6,9 as the top images of each column. Dataset anchor = null; int index = 0; int idx = 0; for (int p = 0; p < mode; p++) { RectangularROI croi = rois.get(p); if (monitor != null) { monitor.subTask("Loading images"); } for (int i = 0; i < nsets; i++) { try { tImages[i] = data.getSlice(monitor, new Slice(index++, index)).squeeze(); } catch (DatasetException e) { logger.error("Could not get slice of image", e); } } shifted.clear(); try { // align a column List<double[]> nshifts = align(tImages, shifted, croi, null, monitor); // create anchor by summing Dataset canchor = DatasetFactory.zeros(DoubleDataset.class, tImages[0].getShape()); for (Dataset d : shifted) { canchor.iadd(d); } if (anchor == null) { anchor = canchor; } else { // align with first column's anchor double[] topShift = align(new IDataset[] {anchor, canchor}, null, croi, null, monitor).get(1); logger.info("Top shift: {}", Arrays.toString(topShift)); // align with new pre-shift if (monitor != null) { monitor.subTask("Re-shift images to first image"); } for (int i = 0; i < nsets; i++) { double[] cshift = nshifts.get(i); cshift[0] += topShift[0]; cshift[1] += topShift[1]; logger.info("New shifts for {}: {}", i, Arrays.toString(cshift)); Dataset cimage = RegisterImage2.shiftImage(DatasetUtils.convertToDataset(tImages[i]), cshift); cimage.setName(shifted.get(i).getName()); shifted.set(i, cimage); if (monitor != null) { if (monitor.isCancelled()) { return lazy; } monitor.worked(1); } } } shifts.add(nshifts); if (monitor != null) { monitor.subTask("Writing shifted images"); } for (int i = 0; i < nsets; i++) { appendDataset(lazy, shifted.get(i), idx, monitor); idx++; } } catch (DatasetException e) { logger.warn("Problem with alignment: " + e); return null; } } } try { HDF5FileFactory.releaseFile(file, true); } catch (ScanFileHolderException e) { logger.error("Could not close file {}", file, e); } return lazy; } /** * Aligns images from a lazy dataset and returns a lazy dataset. This alignment process saves the aligned data in an * hdf5 file saved on disk and this method can be used without running into a MemoryOverflowError. * <p> * This is the modeless alignment routine that uses all the images at once. * * @param data * original list of dataset to be aligned * @param shifts * output where to put resulting shifts * @param roi * rectangular ROI used for the alignment * @param monitor * @return aligned list of dataset */ private static ILazyDataset alignLazyWithROI(ILazyDataset data, List<List<double[]>> shifts, RectangularROI roi, IMonitor monitor) { int nsets = data.getShape()[0]; if (shifts == null) shifts = new ArrayList<>(); // save on a temp file String file = FileUtils.getTempFilePath("aligned.h5"); String path = "/entry/data/"; String name = "aligned"; File tmpFile = new File(file); if (tmpFile.exists()) { try { HDF5FileFactory.releaseFile(file, true); } catch (ScanFileHolderException e) { logger.error("Could not close file {}", file, e); } tmpFile.delete(); } int[] shape = data.getShape(); int[] chunking = new int[] {1, shape[1], shape[2]}; ILazyWriteableDataset lazy = HDF5Utils.createLazyDataset(file, path, name, shape, null, chunking, Dataset.FLOAT32, null, false); IDataset[] tImages = new IDataset[nsets]; List<Dataset> shifted = new ArrayList<>(nsets); int index = 0; int idx = 0; if (monitor != null) { monitor.subTask("Loading images"); } for (int i = 0; i < nsets; i++) { try { tImages[i] = data.getSlice(monitor, new Slice(index++, index)).squeeze(); } catch (DatasetException e) { logger.error("Could not get slice of image", e); } } shifted.clear(); try { // align a column List<double[]> nshifts = align(tImages, shifted, roi, null, monitor); if (monitor != null) { monitor.subTask("Writing shifted images"); } shifts.add(nshifts); for (int i = 0; i < nsets; i++) { appendDataset(lazy, shifted.get(i), idx, monitor); idx++; } } catch (DatasetException e) { logger.warn("Problem with alignment: " + e); return null; } if (monitor != null) { if (monitor.isCancelled()) { return lazy; } monitor.worked(1); } try { HDF5FileFactory.releaseFile(file, true); } catch (ScanFileHolderException e) { logger.error("Could not close file {}", file, e); } return lazy; } private static void appendDataset(ILazyWriteableDataset lazy, IDataset data, int idx, IMonitor monitor) throws DatasetException { int[] shape = lazy.getShape(); SliceND ndSlice = new SliceND(shape, new int[] {idx, 0, 0}, new int[] {idx + 1, shape[1], shape[2]}, null); lazy.setSlice(monitor, data, ndSlice); } }