Skip to content

Commit

Permalink
Merge branch 'issue99-figure-gridlines' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
akorosov committed Apr 17, 2015
2 parents 73585cb + 7bd4e2f commit bb9aa96
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 44 deletions.
168 changes: 124 additions & 44 deletions nansat/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from nansat.tools import add_logger


class Figure():
class Figure(object):
'''Perform opeartions with graphical files: create, append legend, save.
Figure instance is created in the Nansat.write_figure method
Expand Down Expand Up @@ -65,8 +65,8 @@ class Figure():

latGrid = None
lonGrid = None
nGridLines = 10
latlonLabels = 0
lonTicks = 5
latTicks = 5

transparency = None

Expand Down Expand Up @@ -369,7 +369,7 @@ def add_latlon_grids(self, **kwargs):
Compute step of the grid
Make matrices with binarized lat/lon
Find edge (make line)
Convert to maks
Convert to mask
Add mask to PIL
Parameters
Expand All @@ -379,8 +379,12 @@ def add_latlon_grids(self, **kwargs):
array with values of latitudes
lonGrid : numpy array
array with values of longitudes
nGridLines : int
lonTicks : int or list
number of lines to draw
or locations of gridlines
latTicks : int or list
number of lines to draw
or locations of gridlines
Modifies
---------
Expand All @@ -389,45 +393,85 @@ def add_latlon_grids(self, **kwargs):
'''
# modify default values
self._set_defaults(kwargs)

# test availability of grids
if (self.latGrid is None or self.lonGrid is None or
self.nGridLines is None or self.nGridLines == 0):
if (self.latGrid is None or self.lonGrid is None):
return
# get number of grid lines
llSpacing = self.nGridLines
# get vectors for grid lines
latVec = np.linspace(self.latGrid.min(),
self.latGrid.max(), llSpacing)
lonVec = np.linspace(self.lonGrid.min(),
self.lonGrid.max(), llSpacing)

# get vectors with ticks based on input
latTicks = self._get_auto_ticks(self.latTicks, self.latGrid)
lonTicks = self._get_auto_ticks(self.lonTicks, self.lonGrid)

# convert lat/lon grids to indeces
latI = np.zeros(self.latGrid.shape, 'int8')
lonI = np.zeros(self.latGrid.shape, 'int8')
# convert lat/lon to indeces
for i in range(len(latVec)):
latI[self.latGrid > latVec[i]] = i
lonI[self.lonGrid > lonVec[i]] = i
# find pixels on the rgid lines (binarize)
latI = np.diff(latI)
lonI = np.diff(lonI)
for latTick in latTicks:
latI[self.latGrid > latTick] += 1
for lonTick in lonTicks:
lonI[self.lonGrid > lonTick] += 1

# find pixels on the grid lines (binarize)
latI = np.diff(latI, axis=0)[:,:-1] + np.diff(latI, axis=1)[:-1,:]
lonI = np.diff(lonI, axis=0)[:,:-1] + np.diff(lonI, axis=1)[:-1,:]

# make grid from both lat and lon
latI += lonI
latI[latI != 0] = 1

# add mask to the image
self.apply_mask(mask_array=latI, mask_lut={1: [255, 255, 255]})

def _get_auto_ticks(self, ticks, grid):
''' Automatically create a list of lon or lat ticks from number of list
Parameters
----------
ticks : int or list
number or location of ticks
grid : ndarray
grid with lon or lat
Returns
-------
ticks : list
location of ticks
'''
gridMin = grid.min()
gridMax = grid.max()

if type(ticks) is int:
ticks = np.linspace(gridMin, gridMax, ticks)
elif type(ticks) in [list, tuple]:
newTicks = []
for tick in ticks:
if tick >= gridMin and tick <= gridMax:
newTicks.append(tick)
ticks = newTicks
else:
raise OptionError('Incorrect type of ticks')

return ticks

def add_latlon_labels(self, **kwargs):
'''Add lat/lon labels along upper and left side
Compute step of lables
Get lat/lon for these labels from latGrid, lonGrid
Print lables to PIL
Print lables to PIL in white
Parameters
----------
Figure__init__() parameters:
Any of Figure__init__() parameters:
latGrid : numpy array
array with values of latitudes
lonGrid : numpy array
latlonLabels : int
array with values of longitudes
lonTicks : int or list
number of lines to draw
or locations of gridlines
latTicks : int or list
number of lines to draw
or locations of gridlines
Modifies
---------
Expand All @@ -436,27 +480,65 @@ def add_latlon_labels(self, **kwargs):
'''
# modify default values
self._set_defaults(kwargs)

# test availability of grids
if (self.latGrid is None or self.lonGrid is None or
self.latlonLabels == 0):
if (self.latGrid is None or self.lonGrid is None):
return

draw = ImageDraw.Draw(self.pilImg)
font = ImageFont.truetype(self.fontFileName, self.fontSize)

# get number of labels; step of lables
llLabels = self.latlonLabels
llShape = self.latGrid.shape
latI = range(0, llShape[0], (llShape[0] / llLabels) - 1)
lonI = range(0, llShape[1], (llShape[1] / llLabels) - 1)
# get lons/lats from first row/column
#lats = self.latGrid[latI, 0]
#lons = self.lonGrid[0, lonI]
for i in range(len(latI)):
lat = self.latGrid[latI[i], 0]
lon = self.lonGrid[0, lonI[i]]
draw.text((0, 10 + latI[i]), '%4.2f' % lat, fill=255, font=font)
draw.text((50 + lonI[i], 0), '%4.2f' % lon, fill=255, font=font)
# get vectors with ticks based on input
latTicks = self._get_auto_ticks(self.latTicks, self.latGrid)
lonTicks = self._get_auto_ticks(self.lonTicks, self.lonGrid)

# get corresponding lons from upper edge and lats from left edge
lonTicksIdx =self._get_tick_index_from_grid(lonTicks, self.lonGrid,
1, self.lonGrid.shape[1])
latTicksIdx =self._get_tick_index_from_grid(latTicks, self.latGrid,
self.lonGrid.shape[0], 1)

# draw lons
lonsOffset = self.lonGrid.shape[1] / len(lonTicksIdx) / 8.
for lonTickIdx in lonTicksIdx:
lon = self.lonGrid[0, lonTickIdx]
draw.text((lonTickIdx+lonsOffset, 0), '%4.2f' % lon,
fill=255, font=font)

# draw lats
latsOffset = self.latGrid.shape[0] / len(latTicksIdx) / 8.
for latTickIdx in latTicksIdx:
lat = self.latGrid[latTickIdx, 0]
draw.text((0, latTickIdx+latsOffset), '%4.2f' % lat,
fill=255, font=font)

def _get_tick_index_from_grid(self, ticks, grid, rows, cols):
''' Get index of pixels from lon/lat grids closest given ticks
Parameters
----------
ticks : int or list
number or location of ticks
grid : ndarray
grid with lon or lat
rows : int
from which rows to return pixels
cols : int
from which cols to return pixels
Returns
-------
ticks : list
index of ticks
'''

newTicksIdx = []
for tick in ticks:
diff = np.abs(grid[:rows, :cols] - tick).flatten()
minDiffIdx = np.nonzero(diff == diff.min())[0][0]
if minDiffIdx > 0 :
newTicksIdx.append(minDiffIdx)
return newTicksIdx

def clim_from_histogram(self, **kwargs):
'''Estimate min and max pixel values from histogram
Expand Down Expand Up @@ -774,8 +856,7 @@ def process(self, **kwargs):
self.apply_mask()

# add lat/lon grids lines if latGrid and lonGrid are given
if self.latGrid is not None and self.lonGrid is not None:
self.add_latlon_grids()
self.add_latlon_grids()

# append legend
if self.legend:
Expand All @@ -785,9 +866,7 @@ def process(self, **kwargs):
self.create_pilImage(**kwargs)

# add labels with lats/lons
if (self.latGrid is not None and self.lonGrid is not None and
self.latlonLabels > 0):
self.add_latlon_labels()
self.add_latlon_labels()

# add logo
if self.logoFileName is not None:
Expand Down Expand Up @@ -956,3 +1035,4 @@ def _set_defaults(self, idict):
setattr(self, key, [idict[key]])
else:
setattr(self, key, idict[key])

132 changes: 132 additions & 0 deletions nansat/tests/test_figure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#------------------------------------------------------------------------------
# Name: test_nansat.py
# Purpose: Test the Nansat class
#
# Author: Morten Wergeland Hansen, Asuka Yamakawa
# Modified: Morten Wergeland Hansen
#
# Created: 18.06.2014
# Last modified:16.03.2015 13:19
# Copyright: (c) NERSC
# Licence: This file is part of NANSAT. You can redistribute it or modify
# under the terms of GNU General Public License, v.3
# http://www.gnu.org/licenses/gpl-3.0.html
#------------------------------------------------------------------------------
import unittest
import warnings
import os
import sys
import glob
from types import ModuleType, FloatType
import datetime

import matplotlib.pyplot as plt
import numpy as np
from scipy.io.netcdf import netcdf_file

from nansat import Figure, Nansat, Domain
from nansat.tools import gdal, OptionError

import nansat_test_data as ntd

IS_CONDA = 'conda' in os.environ['PATH']


class FigureTest(unittest.TestCase):
def setUp(self):
self.test_file_gcps = os.path.join(ntd.test_data_path, 'gcps.tif')
plt.switch_backend('Agg')

if not os.path.exists(self.test_file_gcps):
raise ValueError('No test data available')

def test_init_array(self):
f = Figure(np.zeros((10,10)))

self.assertEqual(type(f), Figure)


def test_get_auto_ticks_number(self):
n = Nansat(self.test_file_gcps)
lon, lat = n.get_geolocation_grids()
f = Figure(lon)
lonTicks = f._get_auto_ticks(5, lon)
latTicks = f._get_auto_ticks(5, lat)

self.assertEqual(len(lonTicks), 5)
n.logger.error(str(lonTicks))
n.logger.error(str(latTicks))

def test_get_auto_ticks_vector(self):
n = Nansat(self.test_file_gcps)
lon, lat = n.get_geolocation_grids()
f = Figure(lon)
lonTicks = f._get_auto_ticks([28, 29, 30, 100], lon)

self.assertEqual(len(lonTicks), 3)

def test_add_latlon_grids_auto(self):
''' Should create figure with lon/lat gridlines spaced automatically '''
tmpfilename = os.path.join(ntd.tmp_data_path, 'figure_latlon_grids_auto.png')
n = Nansat(self.test_file_gcps)
b = n[1]
lon, lat = n.get_geolocation_grids()

f = Figure(b)
f.process(clim='hist', lonGrid=lon, latGrid=lat)
f.save(tmpfilename)

self.assertEqual(type(f), Figure)
self.assertTrue(os.path.exists(tmpfilename))

def test_add_latlon_grids_number(self):
''' Should create figure with lon/lat gridlines given manually '''
tmpfilename = os.path.join(ntd.tmp_data_path,
'figure_latlon_grids_number.png')
n = Nansat(self.test_file_gcps)
n.resize(3)
b = n[1]
lon, lat = n.get_geolocation_grids()

f = Figure(b)
f.process(cmax=100, lonGrid=lon,
latGrid=lat,
lonTicks=7,
latTicks=7)
f.save(tmpfilename)

self.assertEqual(type(f), Figure)
self.assertTrue(os.path.exists(tmpfilename))

def test_add_latlon_grids_list(self):
''' Should create figure with lon/lat gridlines given manually '''
tmpfilename = os.path.join(ntd.tmp_data_path,
'figure_latlon_grids_list.png')
n = Nansat(self.test_file_gcps)
b = n[1]
lon, lat = n.get_geolocation_grids()

f = Figure(b)
f.process(clim='hist', lonGrid=lon,
latGrid=lat,
lonTicks=[28, 29, 30],
latTicks=[70.5, 71, 71.5, 73])
f.save(tmpfilename)

self.assertEqual(type(f), Figure)
self.assertTrue(os.path.exists(tmpfilename))


def test_get_tick_index_from_grid(self):
''' Should return indeces of pixel closest to ticks '''
n = Nansat(self.test_file_gcps)
lon, lat = n.get_geolocation_grids()

f = Figure(lon)
lonTicksIdx = f._get_tick_index_from_grid([28.5, 29], lon, 1, lon.shape[1])
latTicksIdx = f._get_tick_index_from_grid([71, 71.5], lat, lat.shape[0], 1)
n.logger.error(str(lonTicksIdx))
n.logger.error(str(latTicksIdx))

if __name__ == "__main__":
unittest.main()

0 comments on commit bb9aa96

Please sign in to comment.