Images
[Agent practical 8 of 9]


Before we can generate an image, we need to process our data slightly: we need to re-range it between 0 and 255, and convert our 2D data array to a 1D array.


As we've seen, images work on numbers between zero and 255. We're going to take our data values and convert each to a greyscale pixel. As we want the maximum range of colours for our image, to give the best possible distinction between values, the first thing we need to do is to take our data, which could be any values, and stretch or shrink its range so that the smallest data value is converted to zero (black on our image) and the largest is converted to 255 (white on our image), with the others spread between. To do this, we're going to need a few methods in our Environment class. We'll give you some, and get you to write the others.

First up, we need methods to calculate the maximum and minimum in our dataset. We'll use the following method declarations:

public double getMaximum() {}
public double getMinimum() {}

Here's the algorithm for getMaximum() in psuedocode:

public double getMaximum() {

   // Declare a double variable "maximum" and make equal to data[0][0]

   // Loop through data rows with i
      // Loop across data rows with j
         // if data[i][j] > maximum, maximum = data[i][j]
      // }
   // }

   return maximum

}

Given this, implement the two missing methods in Environment and check they work by calling them from Model.java.


Once you have the two methods, the following re-ranging code will work. Check you understand it: essentially once we've identified the maximum and minimum we can remove the minimum across the dataset and divide through by the difference between the two to put all our data between zero and one. We then multiple by the difference between the new min and max, and add the new min to put the data in its final range. When we run this method we'll pass zero in as the newMinimum, and 255.0 in as the newMaximum:

double[][] getRerangedData (double newMinimum, double newMaximum) {

   double currentMaximum = getMaximum();
   double currentMinimum = getMinimum();

   double[][] tempArray = new double[data.length][data[0].length];

   if ((currentMaximum - currentMinimum) == 0.0) return tempArray;

   for (int i = 0; i < data.length; i++) {
      for (int j = 0; j < data[i].length; j++) {
         tempArray[i][j] = data[i][j];
         tempArray[i][j] = tempArray[i][j] - currentMinimum;
         tempArray[i][j] = tempArray[i][j] / (currentMaximum - currentMinimum);
         tempArray[i][j] = tempArray[i][j] * (newMaximum - newMinimum);
         tempArray[i][j] = tempArray[i][j] + newMinimum;
      }
   }

   return tempArray;
}

The if-statement is there to avoid dividing by zero, which computers really don't like; it just returns the array full of default 0.0s.

Doomed: The way computers deal with integer vs. floating-point numbers and maths is a minefield. Here's a brief program that allows you to experiment with the difference between floating point and int mathematics with regards zeros: MathsHorrors.java; it shows you some of the results of the "isNotANumber" methods which allow you to test whether a number is representable, and the "isInfinite" methods which do the same for infinity.

As we've pointed out, the best thing is to avoid putting any integers into any maths you don't want rounded as the maths is being calculated. There are, however, good reasons for avoiding floating-point numbers if you can. The largest is that they can't be guarenteed to be exactly the number you expect them to be. Because the decimal points needed can be larger than the space alotted for a variable, decimals are often rounded off at the point they run out of space. This isn't usually a problem, but can result in big issues if, for example, you repeatedly take the answer of a multiplication and put it back into the multiplication as one of the starting numbers. Prof. Kees Vuik of the Delft Centre for Computational Science and Engineering has pulled together a salutary set of examples which should make any programmer's palms sweat. The short advice is that anyone who is interested in computing with accurate floating point arithmetic should dig deeper into how these numbers are dealt with. There are some useful books on the books page ("Write Great Code I" is a good start, then "Hacker's Delight" if you're feeling brave), but a good overview is David Goldberg's classic paper What Every Computer Scientist Should Know About Floating-Point Arithmetic (updated).

 

Anyhow, cut and paste the method above into your Environment class, and then we'll go on to look at our final data processing method prior to making the image.