3

I have a gray-scale image of Mars1 that seems to have odd pixels mixed in it.

Is there a Swing component for high-lighting anomalous image pixels? If not, how would you go about making such a component?


  1. For the original (768x1536 px) image of the Sirenum Fossae Trough - MGS MOC Release No. MOC2-248 of Mars, see the Mars Global Surveyor - Mars Orbiter Camera page.

It shows spots on the northern lip of the crater that drew my attention. Here are some zooms and crops of the images to highlight the 3 dark spots. Most zooms are showing the image from further away (smaller) but the last shows the section at double size (2x zoom in).

1/4 Zoom 1/2 Zoom 2 x Zoom

All of the spots are on the border between the crater and ravine. One is near the right of the last image, while the other two are over to the left.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • See also this [related Q&A](http://space.stackexchange.com/q/2379/238) on [Space Exploration](http://space.stackexchange.com/) beta. – Andrew Thompson Oct 12 '13 at 18:42

2 Answers2

3

I'd use a JTable for displaying and sorting the pixels, and an image with small translucent cross-hairs to show the selected pixel(s). Display the image in a JLabel, and there is a basic workable component that can be customized to need.

Results

All images are seen at a browser based 8X zoom scale (FF will dither it smoothly, which makes the lines less sharp, but almost as clear).

The information in pixel x/y, RGB & 'gray scale difference' between that pixel and the surrounding pixels was calculated and shown in a sortable table (of 3200 rows).

Each titled section shows an image, as well as the first six rows of the table, indicating both the sort keys used, and the rows selected.

Image of interest - nothing selected.

The 3 darkest pixels seem obvious, without any further prompt..

X   Y   RGB     Diff.   Select
0   0   192     20.75   -
1   0   186     21.625  -
2   0   174     25.625  -
3   0   177     23.375  -
4   0   175     24.375  -
5   0   181     24.25   -
6   0   189     19.75   -

Darkest Pixels

But here are the darkest pixels high-lit with green cross-hairs just to check our logic.

X   Y   RGB ▲   Diff.   Select
16  25  0       240.625 TRUE
6   30  0       240.125 TRUE
74  7   6       230.875 TRUE
78  31  112     37.875  -
79  34  112     22.75   -
75  37  115     31.375  -

Next Darkest Pixels

The difference in gray scale between the 'next darkest' pixels and their immediate neighbors is much less obvious. Not nearly as obvious as the darkest 3 pixels.

X   Y   RGB ▲   Diff.   Select
16  25  0       240.625 -
6   30  0       240.125 -
74  7   6       230.875 -
78  31  112     37.875  TRUE
79  34  112     22.75   TRUE
75  37  115     31.375  TRUE

Next Biggest Difference

The next biggest difference in pixels by gray scale is similarly nondescript. The difference drops from over 200 pixels to less than 50.

X   Y   RGB     Diff. ▼ Select
16  25  0       240.625 -
6   30  0       240.125 -
74  7   6       230.875 -
37  18  205     47.875  TRUE
31  20  209     45.375  TRUE
59  13  196     44.875  TRUE

Conclusion

Based on the data it seems those 3 darkest pixels are outliers & anomalous, so I'd say "image glitch".

Code

This is the Java code used to display/sort the brightness & color difference, & generate the images. It hot-links to the first, cropped image.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.event.*;
import javax.swing.table.*;

import java.io.*;
import java.net.URL;

class ImageBrightnessFilter {

    BufferedImage bi;
    JPanel gui = null;
    ArrayList<Point> points;
    JLabel l;

    ImageBrightnessFilter(BufferedImage bi) {
        this.bi = bi;
    }

    public static double getNeighborDifference(BufferedImage bi, int x, int y) {
        Color c = new Color(bi.getRGB(x, y));
        int r = c.getRed();

        int tot = 0;
        for (int xx = x - 1; xx < x + 2; xx++) {
            for (int yy = y - 1; yy < y + 2; yy++) {
                try {
                    tot += new Color(bi.getRGB(xx, yy)).getRed();
                } catch (ArrayIndexOutOfBoundsException aioobe) {
                    tot += r;
                }
            }
        }

        return (tot / 8d) - r;
    }

    public JPanel getGui() {
        if (gui == null) {
            gui = new JPanel(new BorderLayout(2, 2));

            ImageTableModel itm = new ImageTableModel(bi);
            final JTable table = new JTable(itm);
            table.setAutoCreateRowSorter(true);
            JScrollPane tableScroll = new JScrollPane(table);
            Dimension d = tableScroll.getPreferredSize();
            Dimension shortTable = new Dimension(
                    (int) d.getWidth(),
                    table.getRowHeight() * 8
                    + table.getTableHeader().getPreferredSize().height);
            tableScroll.setPreferredSize(shortTable);
            gui.add(tableScroll, BorderLayout.CENTER);

            l = new JLabel(
                    new ImageIcon(
                    (bi.getSubimage(0, 0, 80, 40)).getScaledInstance(640, 320, 0)));
            l.setBorder(new LineBorder(Color.BLACK));
            gui.add(l, BorderLayout.PAGE_START);

            ListSelectionListener lsl = new ListSelectionListener() {

                @Override
                public void valueChanged(ListSelectionEvent e) {
                    points = new ArrayList<Point>();
                    if (!e.getValueIsAdjusting()) {
                        int[] rows = table.getSelectedRows();
                        for (int row : rows) {
                            int index = row;
                            int x = (Integer) table.getValueAt(index, 0);
                            int y = (Integer) table.getValueAt(index, 1);
                            Point p = new Point(x, y);
                            points.add(p);
                        }
                    }
                    l.setIcon(new ImageIcon(getHighlightImage().getScaledInstance(640, 320, 0)));
                    l.repaint();
                }
            };
            table.getSelectionModel().addListSelectionListener(lsl);

            JToolBar tb = new JToolBar();
            gui.add(tb, BorderLayout.PAGE_END);
            JButton saveImage = new JButton("Save");
            tb.add(saveImage);
            ActionListener saveListener = new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    String s = JOptionPane.showInputDialog(gui, "name");
                    File home = new File(System.getProperty("user.home"));
                    File f = new File(home,s + ".png");
                    try {
                        ImageIO.write(getHighlightImage(), "png", f);
                    } catch (IOException ex) {
                        Logger.getLogger(
                                ImageBrightnessFilter.class.getName()).log(
                                Level.SEVERE, null, ex);
                    }
                }
            };
            saveImage.addActionListener(saveListener);
        }

        return gui;

    }

    public BufferedImage getHighlightImage() {
        BufferedImage b = new BufferedImage(
                bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = b.createGraphics();
        g.drawImage(bi, 0, 0, null);

        g.setColor(new Color(0, 255, 0, 95));
        if (points!=null) {
            for (Point point : points) {
                g.drawLine(point.x - 4, point.y, point.x - 2, point.y);
                g.drawLine(point.x + 4, point.y, point.x + 2, point.y);
                g.drawLine(point.x, point.y - 4, point.x, point.y - 2);
                g.drawLine(point.x, point.y + 4, point.x, point.y + 2);
            }
        }

        g.dispose();

        return b;
    }

    public static void main(String[] args) throws Exception {
        String s = "I:\\pics\\Space\\Stellar\\2\\MarsSthHemisphereInSpring.gif";
        File f = new File(s);
        URL url = new URL("https://i.stack.imgur.com/z8U5w.png");
        BufferedImage bi0 =
            //ImageIO.read(f);
            ImageIO.read(url);
        final BufferedImage bi =
            //bi0.getSubimage(160, 799, 80, 40);
            bi0;
        Runnable r = new Runnable() {

            @Override
            public void run() {
                ImageBrightnessFilter ibf = new ImageBrightnessFilter(bi);
                JOptionPane.showMessageDialog(null, ibf.getGui());
            }
        };
        SwingUtilities.invokeLater(r);
    }
}

class ImageTableModel extends DefaultTableModel {

    BufferedImage bi;
    final static String[] columnName = {
        "X",
        "Y",
        "RGB",
        "Diff"
    };

    public ImageTableModel(BufferedImage bi) {
        super();
        this.bi = bi;
    }

    @Override
    public Object getValueAt(final int rowIndex, final int columnIndex) {
        Object o = null;
        int x = rowIndex % bi.getWidth();
        int y = rowIndex / bi.getWidth();
        int rgb = bi.getRGB(x, y);
        Color c = new Color(rgb);
        int r = c.getRed();
        switch (columnIndex) {
            case 0:
                o = new Integer(x);
                break;
            case 1:
                o = new Integer(y);
                break;
            case 2:
                o = new Integer(r);
                break;
            case 3:
                o = new Double(ImageBrightnessFilter.getNeighborDifference(
                        bi, x, y));
                break;
            default:
                return null;
        }
        return o;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0:
            case 1:
            case 2:
                return Integer.class;
            case 3:
                return Double.class;
            default:
                return null;
        }
    }

    @Override
    public int getColumnCount() {
        return columnName.length;
    }

    @Override
    public String getColumnName(int columnIndex) {
        return columnName[columnIndex];
    }

    @Override
    public int getRowCount() {
        if (bi == null) {
            return 80 * 40;
        }
        return bi.getHeight() * bi.getWidth();
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
3

For another view, I used the ImageJ command Analyze > Histogram to examine the original image. I focused on an 80 x 32 pixel area surrounding the anomalies; the results are shown below. A JFreeChart that uses ChartFactory.createHistogram() to render a HistogramDataset, illustrated here, might be a useful addition.

I also used Zoom to examine the ARGB values. The leftmost pair are opaque black, 0xFF000000, and the rightmost pixel is 0xFF070707. A similar tooltip approach may be helpful to display pixel values.

Histogram

List

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045