teaching machines

CS 1: Lecture 34 – Volume Slicer

Dear students,

When we last met, we made a slideshow application. This was our first graphical application that we wrote from scratch. We organized the code into two camps: model code and view code. This separation made the model code reusable under many different circumstances. Let’s do all that again today, but in a different context and in more detail. We will create a volume slicer, which will depict a single 2D slice of a 3D dataset.

You probably haven’t gotten your hands on a lot of 3D data. But you might have gotten your hands in one. If you’ve ever had an MRI or CT scan, those machines essentially produced a stack of 2D images at increasing depths—a 3D array. And if you recorded many of these over time, you’d get a 4D array.

Visualizing 3D data is possible, but if you’ve ever had an ultrasound, you know that a lot of measurement and analysis is done in 2D. We’ll make a rudimentary tool that loads in the full 3D volume of data, but only projects a single plane of it. We’ll add a slider so we can change which plane is projected.

Let’s follow this checklist as we implement our slicer:

  • Create a Volume class that encapsulates a 3D array holding data from a CT scan or MRI.
  • Read in the density data from a binary file.
  • Create a Slicer class that is a window.
  • Add a slider to Slicer to change which plane is displayed.
  • Discuss interface, local classes, and lambdas.
  • Load the currently selected slice of the volume into a JLabel as a grayscale image.
  • Add a TransferFunction class for abstracting a colormap.
  • Colorize the selected slice using the transfer function.

Here’s your TODO to complete before we meet again:

  • CS 145, lab 11 is posted. Feel free to start early.
  • The current Google Doodle is a coding exercise. I’ve solved all 6 levels, and gotten the shortest code medal on 5/6. Any challengers?

See you next class!

Sincerely,

P.S. It’s time for a haiku!

Numbers to pictures
Pictures to interactions
To better humans

P.P.S. Here’s the code we wrote together…

Volume.java

package lecture1204;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Volume {
  private int[][][] densities;
  
  public Volume(String path) throws IOException {
    DataInputStream in = new DataInputStream(new FileInputStream(path));
  
    int width = in.readInt();
    int height = in.readInt();
    int depth = in.readInt();
    
    densities = new int[depth][height][width];
    
    for (int z = 0; z < depth; ++z) {
      for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
          densities[z][y][x] = Byte.toUnsignedInt(in.readByte());
        }
      }
    }
    
    in.close();
  }
  
  public int getDepth() {
    return densities.length;
  }
  
  public int getHeight() {
    return densities[0].length;
  }
  
  public int getWidth() {
    return densities[0][0].length;
  }
  
  public int get(int x, int y, int z) {
    return densities[z][y][x];
  }
}

SliderListener.java

package lecture1204;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SliderListener implements ChangeListener {
  public void stateChanged(ChangeEvent e) {
    System.out.println(e);
  }
}

Slicer.java

package lecture1204;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;

public class Slicer extends JFrame {
  private JLabel label;
  private Volume volume;

  public Slicer(Volume volume) {
    this.volume = volume;

    JSlider slider = new JSlider(0, 100);
    label = new JLabel("foobag");
    add(slider, BorderLayout.NORTH);
    add(label, BorderLayout.CENTER);

    slider.addChangeListener(e -> {
      load(slider.getValue());
    });
    load(slider.getValue());

    pack();
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setVisible(true);
  }

  private void load(int z) {
    BufferedImage image = new BufferedImage(volume.getWidth(), volume.getHeight(), BufferedImage.TYPE_INT_RGB);

    for (int y = 0; y < image.getHeight(); ++y) {
      for (int x = 0; x < image.getWidth(); ++x) {
        int density = volume.get(x, y, z);
        Color color = new Color(density, density, density);
        image.setRGB(x, y, color.getRGB());
      }
    }

    label.setIcon(new ImageIcon(image));
  }
}

Main.java

package lecture1204;

import java.io.IOException;

public class Main {
  public static void main(String[] args) throws IOException {
    Volume vismale = new Volume("/Users/johnch/Desktop/vismale.f0");
    Slicer slicer = new Slicer(vismale);
  }
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *