Package gov.lanl.adore.djatoka.kdu

Source Code of gov.lanl.adore.djatoka.kdu.KduExtractExe

/*
* Copyright (c) 2008 Los Alamos National Security, LLC.
*
* Los Alamos National Laboratory Research Library Digital Library Research &
* Prototyping Team
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

package gov.lanl.adore.djatoka.kdu;

import gov.lanl.adore.djatoka.DjatokaDecodeParam;
import gov.lanl.adore.djatoka.DjatokaException;
import gov.lanl.adore.djatoka.IExtract;
import gov.lanl.adore.djatoka.io.reader.PNMReader;
import gov.lanl.adore.djatoka.util.IOUtils;
import gov.lanl.adore.djatoka.util.ImageProcessingUtils;
import gov.lanl.adore.djatoka.util.ImageRecord;
import gov.lanl.adore.djatoka.util.JP2ImageInfo;
import gov.lanl.util.ExecuteStreamHandler;
import gov.lanl.util.PumpStreamHandler;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.martiansoftware.jsap.CommandLineTokenizer;

import kdu_jni.Jp2_family_src;
import kdu_jni.Jpx_source;
import kdu_jni.KduException;
import kdu_jni.Kdu_codestream;
import kdu_jni.Kdu_coords;
import kdu_jni.Kdu_dims;
import kdu_jni.Kdu_global;
import kdu_jni.Kdu_params;

/**
* Java bridge for kdu_expand application
*
* @author Ryan Chute
* @author <a href="mailto:ksclarke@gmail.com">Kevin S. Clarke</a>
*/
public class KduExtractExe implements IExtract {

    private static Logger LOGGER = LoggerFactory.getLogger(KduExtractExe.class);

    private static boolean isWindows = false;

    private static String env;

    private static String exe;

    private static String[] envParams;

    private final static BufferedImage OOB = getOutOfBoundsImage();

    /** Name of extraction executable */
    public static final String KDU_EXPAND_EXE = "kdu_expand";

    public final static String STDOUT = "/dev/stdout";

    public final static String STDIN = "/dev/stdin";

    static {
        env = System.getProperty("kakadu.home") + System.getProperty("file.separator");
        exe = env + ((System.getProperty("os.name").contains("Win")) ? KDU_EXPAND_EXE + ".exe" : KDU_EXPAND_EXE);

        if (System.getProperty("os.name").startsWith("Mac")) {
            envParams = new String[] { "DYLD_LIBRARY_PATH=" + System.getProperty("DYLD_LIBRARY_PATH") };
        } else if (System.getProperty("os.name").startsWith("Win")) {
            isWindows = true;
        } else if (System.getProperty("os.name").startsWith("Linux")) {
            envParams = new String[] { "LD_LIBRARY_PATH=" + System.getProperty("LD_LIBRARY_PATH") };
        } else if (System.getProperty("os.name").startsWith("Solaris")) {
            envParams = new String[] { "LD_LIBRARY_PATH=" + System.getProperty("LD_LIBRARY_PATH") };
        }

        LOGGER.debug("envParams: " + ((envParams != null) ? envParams[0] + " | " : "") + exe);
    }

    /**
     * Extracts region defined in DjatokaDecodeParam as BufferedImage
     *
     * @param input InputStream containing a JPEG 2000 image bitstream.
     * @param params DjatokaDecodeParam instance containing region and transform settings.
     * @return extracted region as a BufferedImage
     * @throws DjatokaException
     */
    public BufferedImage processUsingTemp(InputStream input, DjatokaDecodeParam params) throws DjatokaException {
        File in;

        // Copy to tmp file
        try {
            in = File.createTempFile("tmp", ".jp2");
            FileOutputStream fos = new FileOutputStream(in);
            in.deleteOnExit();
            IOUtils.copyStream(input, fos);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
            throw new DjatokaException(e.getMessage(), e);
        }

        BufferedImage bi = process(in.getAbsolutePath(), params);

        if (in != null) {
            if (!in.delete() && LOGGER.isWarnEnabled()) {
                LOGGER.warn("File not deleted: {}", in);
            }
        }

        return bi;
    }

    /**
     * Extracts region defined in DjatokaDecodeParam as BufferedImage
     *
     * @param is InputStream containing a JPEG 2000 image bitstream.
     * @param params DjatokaDecodeParam instance containing region and transform settings.
     * @return extracted region as a BufferedImage
     * @throws DjatokaException
     */
    public BufferedImage process(InputStream is, DjatokaDecodeParam params) throws DjatokaException {
        if (isWindows) {
            return processUsingTemp(is, params);
        }

        ArrayList<Double> dims = null;

        if (params.getRegion() != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copyStream(is, baos);
            byte[] bytes = baos.toByteArray();
            dims = getRegionMetadata(new ByteArrayInputStream(bytes), params);

            return process(new ByteArrayInputStream(baos.toByteArray()), dims, params);
        } else {
            return process(is, dims, params);
        }
    }

    /**
     * Extracts region defined in DjatokaDecodeParam as BufferedImage
     *
     * @param is InputStream containing a JPEG 2000 image bitstream.
     * @param dims region extraction dimensions
     * @param params DjatokaDecodeParam instance containing region and transform settings.
     * @return extracted region as a BufferedImage
     * @throws DjatokaException
     */
    public BufferedImage process(final InputStream is, ArrayList<Double> dims, DjatokaDecodeParam params)
            throws DjatokaException {
        String input = STDIN;
        String output = STDOUT;
        BufferedImage bi = null;

        try {
            String command = getKduExtractCommand(input, output, dims, params);
            String[] cmdParts = CommandLineTokenizer.tokenize(command);
            Process process = Runtime.getRuntime().exec(cmdParts, envParams, new File(env));
            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
            ByteArrayOutputStream stderr = new ByteArrayOutputStream();
            ExecuteStreamHandler streamHandler = new PumpStreamHandler(stdout, stderr, is);

            try {
                streamHandler.setProcessInputStream(process.getOutputStream());
                streamHandler.setProcessOutputStream(process.getInputStream());
                streamHandler.setProcessErrorStream(process.getErrorStream());
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);

                if (process != null) {
                    closeStreams(process);
                }

                throw e;
            }

            streamHandler.start();

            try {
                waitFor(process);
                final ByteArrayInputStream bais = new ByteArrayInputStream(stdout.toByteArray());
                bi = new PNMReader().open(bais);
                streamHandler.stop();
            } catch (ThreadDeath t) {
                LOGGER.error(t.getMessage(), t);
                process.destroy();
                throw t;
            } finally {
                if (process != null) {
                    closeStreams(process);
                }
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            throw new DjatokaException(e.getMessage(), e);
        }

        return bi;
    }

    /**
     * Extracts region defined in DjatokaDecodeParam as BufferedImage
     *
     * @param input absolute file path of JPEG 2000 image file.
     * @param params DjatokaDecodeParam instance containing region and transform settings.
     * @return extracted region as a BufferedImage
     * @throws DjatokaException
     */
    public BufferedImage process(String input, DjatokaDecodeParam params) throws DjatokaException {
        String output = STDOUT;
        File winOut = null;
        BufferedImage bi = null;

        if (isWindows) {
            try {
                winOut = File.createTempFile("pipe_", ".ppm");
                winOut.deleteOnExit();
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);
                throw new DjatokaException(e.getMessage(), e);
            }

            output = winOut.getAbsolutePath();
        }

        Runtime rt = Runtime.getRuntime();

        try {
            ArrayList<Double> dims = getRegionMetadata(input, params);
            String command = getKduExtractCommand(input, output, dims, params);
            String[] cmdParts = CommandLineTokenizer.tokenize(command);
            final Process process = rt.exec(cmdParts, envParams, new File(env));

            if (output != null) {
                try {
                    if (output.equals(STDOUT)) {
                        bi = new PNMReader().open(new BufferedInputStream(process.getInputStream()));
                    } else if (isWindows) {
                        process.waitFor();

                        try {
                            bi = new PNMReader().open(new BufferedInputStream(new FileInputStream(new File(output))));
                        } catch (Exception e) {
                            LOGGER.error(e.getMessage(), e);

                            if (winOut != null) {
                                if (!winOut.delete() && LOGGER.isWarnEnabled()) {
                                    LOGGER.warn("File not deleted: {}", winOut);
                                }
                            }

                            throw e;
                        }

                        if (winOut != null) {
                            winOut.delete();
                        }
                    }
                } catch (RuntimeException details) {
                    LOGGER.debug("Request out of bounds: {}", details.getMessage());

                    bi = OOB;
                } catch (Exception e) {
                    String error = null;

                    try {
                        error = new String(IOUtils.getByteArray(process.getErrorStream()));
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }

                    LOGGER.error(error, e);

                    if (error != null) {
                        throw new DjatokaException(error);
                    } else {
                        throw new DjatokaException(e.getMessage(), e);
                    }
                } finally {
                    if (process != null) {
                        closeStreams(process);
                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
        }

        return bi;
    }

    /**
     * Extracts region defined in DjatokaDecodeParam as BufferedImage
     *
     * @param input ImageRecord wrapper containing file reference, inputstream, etc.
     * @param params DjatokaDecodeParam instance containing region and transform settings.
     * @return extracted region as a BufferedImage
     * @throws DjatokaException
     */
    public BufferedImage process(ImageRecord input, DjatokaDecodeParam params) throws DjatokaException {
        if (input.getImageFile() != null) {
            return process(input, params);
        } else if (input.getObject() != null) {
            return process(getStreamFromObject(input.getObject()), params);
        } else {
            throw new DjatokaException("File not defined and Input Object Type " +
                    input.getObject().getClass().getName() + " is not supported");
        }
    }

    /**
     * Gets Kdu Extract Command-line based on dims and params
     *
     * @param input absolute file path of JPEG 2000 image file.
     * @param output absolute file path of PGM output image
     * @param dims array of region parameters (i.e. y,x,h,w)
     * @param params contains rotate and level extraction information
     * @return command line string to extract region using kdu_extract
     */
    public final String getKduExtractCommand(String input, String output, ArrayList<Double> dims,
            DjatokaDecodeParam params) {
        StringBuffer command = new StringBuffer(exe);

        if (input.equals(STDIN)) {
            command.append(" -no_seek");
        }

        command.append(" -quiet -i ");
        command.append(escape(new File(input).getAbsolutePath()));
        command.append(" -o ");
        command.append(escape(new File(output).getAbsolutePath()));
        command.append(" ").append(toKduExtractArgs(params));

        if (dims != null && dims.size() == 4) {
            StringBuffer region = new StringBuffer();

            region.append("{").append(dims.get(0)).append(",");
            region.append(dims.get(1)).append("}").append(",");
            region.append("{").append(dims.get(2)).append(",");
            region.append(dims.get(3)).append("}");

            command.append("-region ").append(region.toString()).append(" ");
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(command.toString());
        }

        return command.toString();
    }

    /**
     * Returns populated JPEG 2000 ImageRecord instance
     *
     * @param r ImageRecord containing file path the JPEG 2000 image
     * @return a populated JPEG 2000 ImageRecord instance
     * @throws DjatokaException
     */
    public final ImageRecord getMetadata(ImageRecord r) throws DjatokaException {
        if (r == null) {
            throw new DjatokaException("ImageRecord is null");
        }

        if (r.getImageFile() == null && r.getObject() != null) {
            ImageRecord ir = getMetadata(getStreamFromObject(r.getObject()));
            ir.setObject(r.getObject());
            return ir;
        }

        File f = new File(r.getImageFile());

        if (!f.exists()) {
            throw new DjatokaException("Image Does Not Exist");
        }

        if (!ImageProcessingUtils.checkIfJp2(r.getImageFile())) {
            throw new DjatokaException("Not a JP2 image.");
        }

        if (f.length() <= 4096) {
            FileInputStream fis = null;

            // If < 4K bytes, image may be corrupt;
            // use safer pure Java Metadata gatherer.
            try {
                fis = new FileInputStream(f);
                return getMetadata(fis);
            } catch (Exception e) {
                throw new DjatokaException("Invalid file.");
            } finally {
                info.freelibrary.util.IOUtils.closeQuietly(fis);
            }
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Checking: java.library.path = {}", System.getProperty("java.library.path"));
        }

        Jpx_source inputSource = new Jpx_source();
        Jp2_family_src jp2_family_in = new Jp2_family_src();

        int ref_component = 0;

        try {
            jp2_family_in.Open(r.getImageFile(), true);
            inputSource.Open(jp2_family_in, true);
            Kdu_codestream codestream = new Kdu_codestream();
            codestream.Create(inputSource.Access_codestream(ref_component).Open_stream());

            int minLevels = codestream.Get_min_dwt_levels();
            int depth = codestream.Get_bit_depth(ref_component);
            int colors = codestream.Get_num_components();
            int[] frames = new int[1];
            inputSource.Count_compositing_layers(frames);
            Kdu_dims image_dims = new Kdu_dims();
            codestream.Get_dims(ref_component, image_dims);
            Kdu_coords imageSize = image_dims.Access_size();

            r.setWidth(imageSize.Get_x());
            r.setHeight(imageSize.Get_y());
            r.setDWTLevels(minLevels);

            int djatokaLevels = ImageProcessingUtils.getLevelCount(r.getWidth(), r.getHeight());

            r.setLevels((djatokaLevels > minLevels) ? minLevels : djatokaLevels);
            r.setBitDepth(depth);
            r.setNumChannels(colors);
            r.setCompositingLayerCount(frames[0]);

            int[] v = new int[1];
            Kdu_params p = codestream.Access_siz().Access_cluster("COD");

            if (p != null) {
                p.Get(Kdu_global.Clayers, 0, 0, v, true, true, true);

                if (v[0] > 0) {
                    r.setQualityLayers(v[0]);
                }
            }

            if (codestream.Exists()) {
                codestream.Destroy();
            }

            inputSource.Native_destroy();
            jp2_family_in.Native_destroy();
        } catch (KduException e) {
            LOGGER.error(e.getMessage(), e);
            throw new DjatokaException(e.getMessage(), e);
        }

        return r;
    }

    /**
     * Returns populated JPEG 2000 ImageRecord instance
     *
     * @param is an InputStream containing the JPEG 2000 codestream
     * @return a populated JPEG 2000 ImageRecord instance
     * @throws DjatokaException
     */
    public final ImageRecord getMetadata(final InputStream is) throws DjatokaException {
        JP2ImageInfo info;

        try {
            info = new JP2ImageInfo(is);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
            throw new DjatokaException(e.getMessage(), e);
        }

        return info.getImageRecord();
    }

    /**
     * Returns array of XMLBox records contained in JP2 resource.
     *
     * @param r an ImageRecord containing a file path to resource or has object defined
     * @return an array of XML records contained in JP2 XMLboxes
     */
    public final String[] getXMLBox(ImageRecord r) throws DjatokaException {
        String[] xml = null;

        try {
            if (r.getImageFile() == null && r.getObject() != null) {
                xml = new JP2ImageInfo(getStreamFromObject(r.getObject())).getXmlDocs();
            } else {
                xml = new JP2ImageInfo(new File(r.getImageFile())).getXmlDocs();
            }
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
        }

        return xml;
    }

    /**
     * Utility method to determine type of object stored in ImageRecord and to return it as an InputStream
     *
     * @param o
     * @return an InputStream for the resource contained in ImageRecord object
     */
    public static InputStream getStreamFromObject(Object o) {
        if (o instanceof BufferedInputStream) {
            return (InputStream) o;
        }

        if (o instanceof InputStream) {
            return new BufferedInputStream((InputStream) o);
        }

        if (o instanceof byte[]) {
            return new ByteArrayInputStream((byte[]) o);
        }

        LOGGER.error(o.getClass().getName() + " is not a supported ImageRecord object type.");

        return null;
    }

    private final ArrayList<Double> getRegionMetadata(InputStream input, DjatokaDecodeParam params)
            throws DjatokaException {
        ImageRecord r = getMetadata(input);
        return getRegionMetadata(r, params);
    }

    private final ArrayList<Double> getRegionMetadata(String input, DjatokaDecodeParam params)
            throws DjatokaException {
        ImageRecord r = getMetadata(new ImageRecord(input));
        return getRegionMetadata(r, params);
    }

    private final ArrayList<Double> getRegionMetadata(ImageRecord r, DjatokaDecodeParam params)
            throws DjatokaException {
        if (params.getLevel() >= 0) {
            int levels = ImageProcessingUtils.getLevelCount(r.getWidth(), r.getHeight());
            levels = (r.getDWTLevels() < levels) ? r.getDWTLevels() : levels;
            int reduce = levels - params.getLevel();
            params.setLevelReductionFactor((reduce >= 0) ? reduce : 0);
        } else if (params.getLevel() == -1 && params.getRegion() == null && params.getScalingDimensions() != null) {
            int width = params.getScalingDimensions()[0];
            int height = params.getScalingDimensions()[1];
            int levels = ImageProcessingUtils.getLevelCount(r.getWidth(), r.getHeight());
            int scale_level = ImageProcessingUtils.getScalingLevel(r.getWidth(), r.getHeight(), width, height);
            levels = (r.getDWTLevels() < levels) ? r.getDWTLevels() : levels;
            int reduce = levels - scale_level;
            params.setLevelReductionFactor((reduce >= 0) ? reduce : 0);
        }

        int reduce = 1 << params.getLevelReductionFactor();
        ArrayList<Double> dims = new ArrayList<Double>();

        if (params.getRegion() != null) {
            StringTokenizer st = new StringTokenizer(params.getRegion(), "{},");
            String token;

            // top
            if ((token = st.nextToken()).contains(".")) {
                dims.add(Double.parseDouble(token));
            } else {
                int t = Integer.parseInt(token);

                if (r.getHeight() < t) {
                    throw new DjatokaException("Region inset out of bounds: " + t + ">" + r.getHeight());
                }

                dims.add(Double.parseDouble(token) / r.getHeight());
            }

            // left
            if ((token = st.nextToken()).contains(".")) {
                dims.add(Double.parseDouble(token));
            } else {
                int t = Integer.parseInt(token);

                if (r.getWidth() < t) {
                    throw new DjatokaException("Region inset out of bounds: " + t + ">" + r.getWidth());
                }

                dims.add(Double.parseDouble(token) / r.getWidth());
            }

            // height
            if ((token = st.nextToken()).contains(".")) {
                dims.add(Double.parseDouble(token));
            } else {
                dims.add(Double.parseDouble(token) / (Double.valueOf(r.getHeight()) / Double.valueOf(reduce)));
            }

            // width
            if ((token = st.nextToken()).contains(".")) {
                dims.add(Double.parseDouble(token));
            } else {
                dims.add(Double.parseDouble(token) / (Double.valueOf(r.getWidth()) / Double.valueOf(reduce)));
            }
        }

        return dims;
    }

    private static BufferedImage getOutOfBoundsImage() {
        BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        int rgb = bi.getRGB(0, 0);
        int alpha = (rgb >> 24) & 0xff;
        bi.setRGB(0, 0, alpha);
        return bi;
    }

    private static String toKduExtractArgs(DjatokaDecodeParam params) {
        StringBuffer sb = new StringBuffer();

        if (params.getLevelReductionFactor() > 0) {
            sb.append("-reduce ").append(params.getLevelReductionFactor()).append(" ");
        }

        if (params.getRotationDegree() > 0) {
            sb.append("-rotate ").append(params.getRotationDegree()).append(" ");
        }

        if (params.getCompositingLayer() > 0) {
            sb.append("-jpx_layer ").append(params.getCompositingLayer()).append(" ");
        }

        return sb.toString();
    }

    private static final String escape(String path) {
        if (path.contains(" ")) {
            path = "\"" + path + "\"";
        }
        return path;
    }

    // Process Handler Utils
    private int waitFor(Process process) {
        try {
            process.waitFor();
            return process.exitValue();
        } catch (InterruptedException e) {
            process.destroy();
        }

        return 2;
    }

    private static void closeStreams(Process process) {
        close(process.getInputStream());
        close(process.getOutputStream());
        close(process.getErrorStream());
        process.destroy();
    }

    private static void close(InputStream device) {
        if (device != null) {
            try {
                device.close();
            } catch (IOException ioex) {
            }
        }
    }

    private static void close(OutputStream device) {
        if (device != null) {
            try {
                device.close();
            } catch (IOException ioex) {
            }
        }
    }
}
TOP

Related Classes of gov.lanl.adore.djatoka.kdu.KduExtractExe

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.