[GRASS-SVN] r57734 - sandbox/turek/scatter_plot
svn_grass at osgeo.org
svn_grass at osgeo.org
Wed Sep 18 01:57:09 PDT 2013
Author: turek
Date: 2013-09-18 01:57:08 -0700 (Wed, 18 Sep 2013)
New Revision: 57734
Modified:
sandbox/turek/scatter_plot/testing_patch.diff
Log:
scatter plot: get rid of scipy dependency -> using iclass_perimeters instead of it, update for changes in iclass groups managment
Modified: sandbox/turek/scatter_plot/testing_patch.diff
===================================================================
--- sandbox/turek/scatter_plot/testing_patch.diff 2013-09-18 08:03:43 UTC (rev 57733)
+++ sandbox/turek/scatter_plot/testing_patch.diff 2013-09-18 08:57:08 UTC (rev 57734)
@@ -1,2020 +1,2444 @@
-Index: include/defs/vedit.h
+Index: lib/imagery/scatt_sccats.c
===================================================================
---- include/defs/vedit.h (revision 57652)
-+++ include/defs/vedit.h (working copy)
-@@ -33,6 +33,8 @@
- int Vedit_merge_lines(struct Map_info *, struct ilist *);
-
- /* move.c */
-+int Vedit_move_areas(struct Map_info *, struct Map_info **, int,
-+ struct ilist *, double, double, double, int, double);
- int Vedit_move_lines(struct Map_info *, struct Map_info **, int,
- struct ilist *, double, double, double, int, double);
-
-Index: include/defs/imagery.h
-===================================================================
---- include/defs/imagery.h (revision 57652)
-+++ include/defs/imagery.h (working copy)
-@@ -110,6 +110,26 @@
- FILE *I_fopen_subgroup_ref_new(const char *, const char *);
- FILE *I_fopen_subgroup_ref_old(const char *, const char *);
-
-+/* scatt_sccats.c */
-+void I_sc_init_cats(struct scCats *, int, int);
-+void I_sc_free_cats(struct scCats *);
-+int I_sc_add_cat(struct scCats *);
-+int I_sc_insert_scatt_data(struct scCats *, struct scdScattData *, int, int);
+--- lib/imagery/scatt_sccats.c (revision 0)
++++ lib/imagery/scatt_sccats.c (working copy)
+@@ -0,0 +1,405 @@
++/*!
++ \file lib/imagery/scatt_cat_rast.c
+
-+void I_scd_init_scatt_data(struct scdScattData *, int, int, void *);
++ \brief Imagery library - functions for manipulation with scatter plot structs.
+
-+/* scatt.c */
-+int I_compute_scatts(struct Cell_head *, struct scCats *, const char **,
-+ const char **, int, struct scCats *, const char **);
++ Copyright (C) 2013 by the GRASS Development Team
+
-+int I_create_cat_rast(struct Cell_head *, const char *);
-+int I_insert_patch_to_cat_rast(const char *, struct Cell_head *, const char *);
++ This program is free software under the GNU General Public License
++ (>=v2). Read the file COPYING that comes with GRASS for details.
+
-+int I_id_scatt_to_bands(const int, const int, int *, int *);
-+int I_bands_to_id_scatt(const int, const int, const int, int *);
-+int I_merge_arrays(unsigned char *, unsigned char *, unsigned, unsigned, double);
-+int I_apply_colormap(unsigned char *, unsigned char *, unsigned, unsigned char *, unsigned char *);
++ \author Stepan Turek <stepan.turek at seznam.cz> (Mentor: Martin Landa)
++ */
+
- /* sig.c */
- int I_init_signatures(struct Signature *, int);
- int I_new_signature(struct Signature *);
-Index: include/imagery.h
-===================================================================
---- include/imagery.h (revision 57652)
-+++ include/imagery.h (working copy)
-@@ -135,6 +135,56 @@
-
- } IClass_statistics;
-
-+/* Scatter Plot backend */
++#include <grass/raster.h>
++#include <grass/imagery.h>
++#include <grass/gis.h>
+
-+#define SC_SCATT_DATA 0
-+#define SC_SCATT_CONDITIONS 1
++#include <stdio.h>
++#include <stdlib.h>
++#include <math.h>
++#include <string.h>
+
++/*!
++ \brief Compute band ids from scatter plot id.
+
-+/*! Holds list of all catagories.
-+ It can contain selected areas for scatter plots (SC_SCATT_CONDITIONS type) or computed scatter plots (SC_SCATT_DATA type).
-+*/
-+struct scCats
-+{
-+ int type; /*!< SC_SCATT_DATA -> computed scatter plots, SC_SCATT_CONDITIONS -> set conditions for scatter plots to be computed*/
++ Scatter plot id describes which bands defines the scatter plot.
+
-+ int n_cats; /*!< number of alocated categories */
-+
-+ int n_bands; /*!< number of analyzed bands */
-+ int n_scatts; /*!< number of possible scattter plot which can be created from bands */
++ Let say we have 3 bands, their ids are 0, 1 and 2.
++ Scatter plot with id 0 consists of band 1 (b_1_id) 0 and band 2 (b_2_id) 1.
++ All scatter plots:
++ scatt_id b_1_id b_2_id
++ 0 0 1
++ 1 0 2
++ 2 1 2
+
-+ int n_a_cats; /*!< number of used/active categories */
-+ int * cats_ids; /*!< (cat_idx->cat_id) array index is internal idx (position in cats_arr) and id is saved in it's position*/
-+ int * cats_idxs; /*!< (cat_id->cat_idx) array index is id and internal idx is saved in it's position*/
++ \param scatt_id scatter plot id
++ \param n_bands number of bands
++ \param [out] b_1_id id of band1
++ \param[out] b_2_id id of band2
+
-+ struct scScatts ** cats_arr; /*!< array of pointers to struct scScatts */
-+};
++ \return 0
++ */
++int I_id_scatt_to_bands(const int scatt_id, const int n_bands, int * b_1_id, int * b_2_id)
++{
++ int n_b1 = n_bands - 1;
+
++ * b_1_id = (int) ((2 * n_b1 + 1 - sqrt((double)((2 * n_b1 + 1) * (2 * n_b1 + 1) - 8 * scatt_id))) / 2);
+
-+/*! Holds list of all scatter plots, which belongs to category.
-+*/
-+struct scScatts
-+{
-+ int n_a_scatts; /*!< number of used/active scatter plots*/
-+
-+ int * scatts_bands; /*!< array of bands for which represents the scatter plots,
-+ every scatter plot has assigned two bads (size of array is n_a_scatts * 2)*/
-+ int * scatt_idxs; /*!< (scatt_id->scatt_idx) internal idx of the scatter plot (position in scatts_arr)*/
++ * b_2_id = scatt_id - ((* b_1_id) * (2 * n_b1 + 1) - (* b_1_id) * (* b_1_id)) / 2 + (* b_1_id) + 1;
+
-+ struct scdScattData ** scatts_arr; /*!< array of pointers to scdScattData */
-+};
++ return 0;
++}
+
-+/*! Holds scatter plot data.
-+*/
-+struct scdScattData
-+{
-+ int n_vals; /*!< Number of values in scatter plot. */
+
-+ unsigned char * b_conds_arr; /*!< array of selected areas (used for SC_SCATT_CONDITIONS type) otherwise NULL */
-+ unsigned int * scatt_vals_arr; /*!< array of computed areas (used for SC_SCATT_DATA type) otherwise NULL */
-+};
++/*!
++ \brief Compute scatter plot id from band ids.
+
++ See also I_id_scatt_to_bands().
+
- #define SIGNATURE_TYPE_MIXED 1
-
- #define GROUPFILE "CURGROUP"
-Index: gui/icons/grass/polygon.png
-===================================================================
-Cannot display: file marked as a binary type.
-svn:mime-type = application/octet-stream
-Index: gui/icons/grass/polygon.png
-===================================================================
---- gui/icons/grass/polygon.png (revision 57652)
-+++ gui/icons/grass/polygon.png (working copy)
-
-Property changes on: gui/icons/grass/polygon.png
-___________________________________________________________________
-Added: svn:mime-type
-## -0,0 +1 ##
-+application/octet-stream
-\ No newline at end of property
-Index: gui/wxpython/scatt_plot/core_c.py
-===================================================================
---- gui/wxpython/scatt_plot/core_c.py (revision 0)
-+++ gui/wxpython/scatt_plot/core_c.py (working copy)
-@@ -0,0 +1,203 @@
-+"""!
-+ at package scatt_plot.scatt_plot
++ \param n_bands number of bands
++ \param b_1_id id of band1
++ \param b_1_id id of band2
++ \param [out] scatt_id scatter plot id
+
-+ at brief Functions which work with scatter plot c code.
++ \return 0
++ */
++int I_bands_to_id_scatt(const int b_1_id, const int b_2_id, const int n_bands, int * scatt_id)
++{
++ int n_b1 = n_bands - 1;
+
-+Classes:
++ * scatt_id = (b_1_id * (2 * n_b1 + 1) - b_1_id * b_1_id) / 2 + b_2_id - b_1_id - 1;
+
-+(C) 2013 by the GRASS Development Team
++ return 0;
++}
+
-+This program is free software under the GNU General Public License
-+(>=v2). Read the file COPYING that comes with GRASS for details.
++/*!
++ \brief Initialize structure for storing scatter plots data.
+
-+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
-+"""
++ \param cats pointer to scCats struct
++ \param n_bands number of bands
++ \param type SC_SCATT_DATA - stores scatter plots
++ \param type SC_SCATT_CONDITIONS - stores selected areas in scatter plots
++ */
++void I_sc_init_cats(struct scCats * cats, int n_bands, int type)
++{
++ int i_cat;
+
-+#TODO just for testing
-+import time
++ cats->type = type;
+
-+import sys
-+from multiprocessing import Process, Queue
++ cats->n_cats = 100;
++ cats->n_a_cats = 0;
+
-+from ctypes import *
-+try:
-+ from grass.lib.imagery import *
-+ from grass.lib.gis import Cell_head, G_get_window
-+except ImportError, e:
-+ sys.stderr.write(_("Loading ctypes libs failed"))
++ cats->n_bands = n_bands;
++ cats->n_scatts = (n_bands - 1) * n_bands / 2;
+
-+from core.gcmd import GException
++ cats->cats_arr = (struct scScatts **) G_malloc(cats->n_cats * sizeof(struct scScatts *));
++ memset(cats->cats_arr, 0, cats-> n_cats * sizeof(struct scScatts *));
+
-+def ApplyColormap(vals, vals_mask, colmap, out_vals):
-+
-+ c_uint8_p = POINTER(c_uint8)
-+ c_double_p = POINTER(c_double)
++ cats->cats_ids = (int *) G_malloc(cats->n_cats * sizeof(int));
++ cats->cats_idxs =(int *) G_malloc(cats->n_cats * sizeof(int));
+
++ for(i_cat = 0; i_cat < cats->n_cats; i_cat++)
++ cats->cats_idxs[i_cat] = -1;
+
-+ vals_p = vals.ctypes.data_as(c_uint8_p)
-+ vals_mask_p = vals_mask.ctypes.data_as(c_uint8_p)
-+ colmap_p = colmap.ctypes.data_as(c_uint8_p)
-+ out_vals_p = out_vals.ctypes.data_as(c_uint8_p)
++ return;
++}
+
-+ vals_size = vals.reshape((-1)).shape[0]
-+ I_apply_colormap(vals_p, vals_mask_p, vals_size, colmap_p, out_vals_p)
++/*!
++ \brief Free data of struct scCats, the structure itself remains alocated.
+
-+def MergeArrays(merged_arr, overlay_arr, alpha):
-+ if merged_arr.shape != overlay_arr.shape:
-+ GException("MergeArrays: merged_arr.shape != overlay_arr.shape")
++ \param cats pointer to existing scCats struct
++ */
++void I_sc_free_cats(struct scCats * cats)
++{
++ int i_cat;
+
-+ c_uint8_p = POINTER(c_uint8)
-+ merged_p = merged_arr.ctypes.data_as(c_uint8_p)
-+ overlay_p = overlay_arr.ctypes.data_as(c_uint8_p)
++ for(i_cat = 0; i_cat < cats->n_a_cats; i_cat++)
++ {
++ if(cats->cats_arr[i_cat])
++ {
++ G_free(cats->cats_arr[i_cat]->scatt_idxs);
++ G_free(cats->cats_arr[i_cat]->scatts_bands);
++ G_free(cats->cats_arr[i_cat]->scatts_arr);
++ G_free(cats->cats_arr[i_cat]);
++ }
++ }
+
-+ I_merge_arrays(merged_p, overlay_p, merged_arr.shape[0], merged_arr.shape[1], alpha)
++ G_free(cats->cats_ids);
++ G_free(cats->cats_idxs);
++ G_free(cats->cats_arr);
+
-+def MergeArrays(merged_arr, overlay_arr, alpha):
-+ if merged_arr.shape != overlay_arr.shape:
-+ GException("MergeArrays: merged_arr.shape != overlay_arr.shape")
++ cats->n_cats = 0;
++ cats->n_a_cats = 0;
++ cats->n_bands = 0;
++ cats->n_scatts = 0;
++ cats->type = -1;
+
-+ c_uint8_p = POINTER(c_uint8)
-+ merged_p = merged_arr.ctypes.data_as(c_uint8_p)
-+ overlay_p = overlay_arr.ctypes.data_as(c_uint8_p)
++ return;
++}
+
-+ I_merge_arrays(merged_p, overlay_p, merged_arr.shape[0], merged_arr.shape[1], alpha)
++#if 0
++void I_sc_get_active_categories(int * a_cats_ids, int * n_a_cats, struct scCats * cats)
++{
++ a_cats_ids = cats->cats_ids;
++ * n_a_cats = cats->n_a_cats;
++}
++#endif
+
-+def ComputeScatts(region, scatt_conds, bands, n_bands, scatts, cats_rasts_conds, cats_rasts):
-+
-+ q = Queue()
-+ p = Process(target=_computeScattsProcess, args=(region, scatt_conds, bands,
-+ n_bands, scatts, cats_rasts_conds, cats_rasts, q))
-+ p.start()
-+ ret = q.get()
-+ p.join()
++/*!
++ \brief Add category.
+
-+ return ret[0], ret[1]
++ Category represents group of scatter plots.
+
-+def UpdateCatRast(patch_rast, region, cat_rast):
-+ q = Queue()
-+ p = Process(target=_updateCatRastProcess, args=(patch_rast, region, cat_rast, q))
-+ p.start()
-+ ret = q.get()
-+ p.join()
++ \param cats pointer to scCats struct
+
-+ return ret
++ \return assigned category id (starts with 0)
++ \return -1 if maximum nuber of categories was reached
++ */
++int I_sc_add_cat(struct scCats * cats)
++{
++ int i_scatt, i_cat_id, cat_id;
++ int n_a_cats = cats->n_a_cats;
+
-+def CreateCatRast(region, cat_rast):
-+ cell_head = _regionToCellHead(region)
-+ I_create_cat_rast(pointer(cell_head), cat_rast)
++ if(cats->n_a_cats >= cats->n_cats)
++ return -1;
+
-+def _computeScattsProcess(region, scatt_conds, bands, n_bands, scatts, cats_rasts_conds, cats_rasts, output_queue):
++ for(i_cat_id = 0; i_cat_id < cats->n_cats; i_cat_id++)
++ if(cats->cats_idxs[i_cat_id] < 0) {
++ cat_id = i_cat_id;
++ break;
++ }
+
-+ #TODO names for types not 0 and 1?
-+ sccats_c, cats_rasts_c, refs = _getComputationStruct(scatts, cats_rasts, SC_SCATT_DATA, n_bands)
-+ scatt_conds_c, cats_rasts_conds_c, refs2 = _getComputationStruct(scatt_conds, cats_rasts_conds, SC_SCATT_CONDITIONS, n_bands)
++ cats->cats_ids[n_a_cats] = cat_id;
++ cats->cats_idxs[cat_id] = n_a_cats;
++
++ cats->cats_arr[n_a_cats] = (struct scScatts *) G_malloc(sizeof(struct scScatts));
+
-+ char_bands = _stringListToCharArr(bands)
-+
-+ cell_head = _regionToCellHead(region)
++ cats->cats_arr[n_a_cats]->scatts_arr = (struct scdScattData **) G_malloc(cats->n_scatts * sizeof(struct scdScattData *));
++ memset((cats->cats_arr[n_a_cats]->scatts_arr), 0, cats->n_scatts * sizeof(struct scdScattData *));
++
++ cats->cats_arr[n_a_cats]->n_a_scatts = 0;
+
-+ ret = I_compute_scatts(pointer(cell_head),
-+ pointer(scatt_conds_c),
-+ pointer(cats_rasts_conds_c),
-+ pointer(char_bands),
-+ n_bands,
-+ pointer(sccats_c),
-+ pointer(cats_rasts_c))
++ cats->cats_arr[n_a_cats]->scatts_bands = (int *) G_malloc(cats->n_scatts * 2 * sizeof(int));
++
++ cats->cats_arr[n_a_cats]->scatt_idxs = (int *) G_malloc(cats->n_scatts * sizeof(int));
++ for(i_scatt = 0; i_scatt < cats->n_scatts; i_scatt++)
++ cats->cats_arr[n_a_cats]->scatt_idxs[i_scatt] = -1;
++
++ ++cats->n_a_cats;
+
-+ I_sc_free_cats(pointer(sccats_c))
-+ I_sc_free_cats(pointer(scatt_conds_c))
++ return cat_id;
++}
+
-+ output_queue.put((ret, scatts))
++#if 0
++int I_sc_delete_cat(struct scCats * cats, int cat_id)
++{
++ int cat_idx, i_cat;
+
-+def _getBandcRange( band_info):
-+ band_c_range = struct_Range()
++ if(cat_id < 0 || cat_id >= cats->n_cats)
++ return -1;
+
-+ band_c_range.max = band_info['max']
-+ band_c_range.min = band_info['min']
++ cat_idx = cats->cats_idxs[cat_id];
++ if(cat_idx < 0)
++ return -1;
+
-+ return band_c_range
++ G_free(cats->cats_arr[cat_idx]->scatt_idxs);
++ G_free(cats->cats_arr[cat_idx]->scatts_bands);
++ G_free(cats->cats_arr[cat_idx]->scatts_arr);
++ G_free(cats->cats_arr[cat_idx]);
+
-+def _regionToCellHead(region):
-+ cell_head = struct_Cell_head()
-+ G_get_window(pointer(cell_head))
++ for(i_cat = cat_idx; i_cat < cats->n_a_cats - 1; i_cat++)
++ {
++ cats->cats_arr[i_cat] = cats->cats_arr[i_cat + 1];
++ cats->cats_ids[i_cat] = cats->cats_ids[i_cat + 1];
++ }
++ cats->cats_idxs[cat_id] = -1;
+
-+ convert_dict = {'n' : 'north', 'e' : 'east',
-+ 'w' : 'west', 's' : 'south',
-+ 'nsres' : 'ns_res',
-+ 'ewres' : 'ew_res'}
-+
-+ for k, v in region.iteritems():
-+ if k in ["rows", "cols", "cells"]:
-+ v = int(v)
-+ else:
-+ v = float(v)
-+
-+ if convert_dict.has_key(k):
-+ k = convert_dict[k]
-+
-+ setattr(cell_head, k, v)
-+
-+ return cell_head
-+
-+def _stringListToCharArr(str_list):
-+
-+ arr = c_char_p * len(str_list)
-+ char_arr = arr()
-+ for i, st in enumerate(str_list):
-+ if st:
-+ char_arr[i] = st
-+ else:
-+ char_arr[i] = None
-+
-+ return char_arr
-+
-+def _getComputationStruct(cats, cats_rasts, cats_type, n_bands):
-+
-+ sccats = struct_scCats()
-+ I_sc_init_cats(pointer(sccats), c_int(n_bands), c_int(cats_type));
-+
-+ refs = []
-+ cats_rasts_core = []
++ --cats->n_a_cats;
+
-+ for cat_id, scatt_ids in cats.iteritems():
-+ cat_c_id = I_sc_add_cat(pointer(sccats))
-+ cats_rasts_core.append(cats_rasts[cat_id])
++ return 0;
++}
++#endif
+
-+ for scatt_id, dt in scatt_ids.iteritems():
-+ # if key is missing condition is always True (full scatter plor is computed)
-+ vals = dt['np_vals']
++/*!
++ \brief Insert scatter plot data .
++ Inserted scatt_data struct must have same type as cats struct (SC_SCATT_DATA or SC_SCATT_CONDITIONS).
+
-+ scatt_vals = scdScattData()
++ \param cats pointer to scCats struct
++ \param cat_id id number of category.
++ \param scatt_id id number of scatter plot.
+
-+ c_void_p = ctypes.POINTER(ctypes.c_void_p)
++ \return 0 on success
++ \return -1 on failure
++ */
++int I_sc_insert_scatt_data(struct scCats * cats, struct scdScattData * scatt_data, int cat_id, int scatt_id)
++{
++ int band_1, band_2, cat_idx, n_a_scatts;
++ struct scScatts * scatts;
+
-+ if cats_type == SC_SCATT_DATA:
-+ vals[:] = 0
-+ elif cats_type == SC_SCATT_CONDITIONS:
-+ pass
-+ else:
-+ return None
-+ data_p = vals.ctypes.data_as(c_void_p)
-+ I_scd_init_scatt_data(pointer(scatt_vals), cats_type, len(vals), data_p)
++ if(cat_id < 0 || cat_id >= cats->n_cats)
++ return -1;
+
-+ refs.append(scatt_vals)
++ cat_idx = cats->cats_idxs[cat_id];
++ if(cat_idx < 0)
++ return -1;
+
-+ I_sc_insert_scatt_data(pointer(sccats),
-+ pointer(scatt_vals),
-+ cat_c_id, scatt_id)
++ if(scatt_id < 0 && scatt_id >= cats->n_scatts)
++ return -1;
+
-+ cats_rasts_c = _stringListToCharArr(cats_rasts_core)
++ scatts = cats->cats_arr[cat_idx];
++ if(scatts->scatt_idxs[scatt_id] >= 0)
++ return -1;
+
-+ return sccats, cats_rasts_c, refs
++ if(!scatt_data->b_conds_arr && cats->type == SC_SCATT_CONDITIONS)
++ return -1;
+
-+def _updateCatRastProcess(patch_rast, region, cat_rast, output_queue):
-+ cell_head = _regionToCellHead(region)
-+
-+
-+ ret = I_insert_patch_to_cat_rast(patch_rast,
-+ pointer(cell_head),
-+ cat_rast)
++ if(!scatt_data->scatt_vals_arr && cats->type == SC_SCATT_DATA)
++ return -1;
+
-+ output_queue.put(ret)
++ n_a_scatts = scatts->n_a_scatts;
+
++ scatts->scatt_idxs[scatt_id] = n_a_scatts;
+
-Index: gui/wxpython/scatt_plot/frame.py
-===================================================================
---- gui/wxpython/scatt_plot/frame.py (revision 0)
-+++ gui/wxpython/scatt_plot/frame.py (working copy)
-@@ -0,0 +1,714 @@
-+"""!
-+ at package scatt_plot.dialogs
++ I_id_scatt_to_bands(scatt_id, cats->n_bands, &band_1, &band_2);
+
-+ at brief GUI.
++ scatts->scatts_bands[n_a_scatts * 2] = band_1;
++ scatts->scatts_bands[n_a_scatts * 2 + 1] = band_2;
+
-+Classes:
++ scatts->scatts_arr[n_a_scatts] = scatt_data;
++ ++scatts->n_a_scatts;
+
-+(C) 2013 by the GRASS Development Team
++ return 0;
++}
+
-+This program is free software under the GNU General Public License
-+(>=v2). Read the file COPYING that comes with GRASS for details.
++#if 0
++int I_sc_remove_scatt_data(struct scCats * cats, struct scdScattData * scatt_data, int cat_id, int scatt_id)
++{
++ int cat_idx, scatt_idx, n_init_scatts, i_scatt;
++ struct scScatts * scatts;
+
-+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
-+"""
-+import os
-+import sys
++ if(cat_id < 0 && cat_id >= cats->n_cats)
++ return -1;
+
-+import wx
-+import wx.lib.scrolledpanel as scrolled
-+import wx.lib.mixins.listctrl as listmix
++ cat_idx = cats->cats_idxs[cat_id];
++ if(cat_idx < 0)
++ return -1;
+
-+from core import globalvar
-+from core.gcmd import GException, GError, RunCommand
++ if(scatt_id < 0 || scatt_id >= cats->n_scatts)
++ return -1;
+
-+from gui_core.gselect import Select
-+from gui_core.dialogs import SetOpacityDialog
++ scatts = cats->cats_arr[cat_idx];
++ if(scatts->scatt_idxs[scatt_id] < 0)
++ return -1;
+
-+from scatt_plot.controllers import ScattsManager
-+from scatt_plot.toolbars import MainToolbar, EditingToolbar, CategoryToolbar
-+from scatt_plot.scatt_core import idScattToidBands
-+from scatt_plot.plots import ScatterPlotWidget
-+from vnet.dialogs import VnetStatusbar
++ scatt_data = scatts->scatts_arr[scatt_idx];
+
-+try:
-+ from agw import aui
-+except ImportError:
-+ import wx.lib.agw.aui as aui
++ for(i_scatt = scatt_idx; i_scatt < scatts->n_a_scatts - 1; i_scatt++)
++ {
++ scatts->scatts_arr[i_scatt] = scatts->scatts_arr[i_scatt + 1];
++ scatts->scatts_bands[i_scatt * 2] = scatts->scatts_bands[(i_scatt + 1)* 2];
++ scatts->scatts_bands[i_scatt * 2 + 1] = scatts->scatts_bands[(i_scatt + 1) * 2 + 1];
++ }
++ scatts->scatts_arr[scatts->n_a_scatts] = NULL;
+
++ scatts->scatt_idxs[scatt_id] = -1;
+
-+class IClassIScattPanel(wx.Panel):
-+ def __init__(self, parent, giface, iclass_mapwin = None,
-+ id = wx.ID_ANY, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
++ scatt_data = scatts->scatts_arr[scatt_id];
++ scatts->n_a_scatts--;
+
-+ #wx.SplitterWindow.__init__(self, parent = parent, id = id,
-+ # style = wx.SP_LIVE_UPDATE)
-+ wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY, **kwargs)
++ return 0;
++}
+
-+ self.scatt_mgr = self._createScattMgr(guiparent=parent, giface=giface,
-+ iclass_mapwin=iclass_mapwin)
++int I_sc_set_value(struct scCats * cats, int cat_id, int scatt_id, int value_idx, int value)
++{
++ int n_a_scatts = cats->cats_arr[cat_id]->n_a_scatts;
++ int cat_idx, scatt_idx, ret;
+
-+ # toobars
-+ self.toolbars = {}
-+ self.toolbars['mainToolbar'] = self._createMainToolbar()
-+ self.toolbars['editingToolbar'] = EditingToolbar(parent = self, scatt_mgr = self.scatt_mgr)
-+
-+ self._createCategoryPanel(self)
++ cat_idx = cats->cats_idxs[cat_id];
++ if(cat_idx < 0)
++ return -1;
++
++ if(cats->cats_arr[cat_idx]->scatt_idxs[scatt_id] < 0)
++ return -1;
+
-+ self.plot_panel = ScatterPlotsPanel(self, self.scatt_mgr)
++ cat_idx = cats->cats_idxs[cat_id];
++ scatt_idx = cats->cats_arr[cat_idx]->scatt_idxs[scatt_id];
+
-+ # statusbar
-+ self.stPriors = {'high' : 5, 'info' : 1}
-+ self.stBar = VnetStatusbar(parent = self, style = 0)
-+ self.stBar.SetFieldsCount(number = 1)
++ I_scd_set_value(cats->cats_arr[cat_idx]->scatts_arr[scatt_idx], value_idx, value);
+
-+ self.mainsizer = wx.BoxSizer(wx.VERTICAL)
-+ self.mainsizer.Add(item = self.toolbars['mainToolbar'], proportion = 0, flag = wx.EXPAND)
-+ self.mainsizer.Add(item = self.toolbars['editingToolbar'], proportion = 0, flag = wx.EXPAND)
-+ self.mainsizer.Add(item = self.catsPanel, proportion = 0, flag = wx.EXPAND | wx.LEFT | wx.RIGHT , border = 5)
-+ self.mainsizer.Add(item = self.plot_panel, proportion = 1, flag = wx.EXPAND)
++ return 0;
++}
++#endif
+
-+ self.mainsizer.Add(item = self.stBar, proportion = 0)
++/*!
++ \brief Insert scatter plot data.
++
++ \param scatt_data pointer to existing struct scdScattData
++ \param type SC_SCATT_DATA for scatter plots or SC_SCATT_CONDITIONS for selected areas in scatter plot
++ \param n_vals number of data values
++ \param data array of values (unsigned char for SC_SCATT_CONDITIONS, unsigned int for SC_SCATT_DATA)
++ */
++void I_scd_init_scatt_data(struct scdScattData * scatt_data, int type, int n_vals, void * data)
++{
++ scatt_data->n_vals = n_vals;
+
-+ self.catsPanel.Hide()
-+ self.toolbars['editingToolbar'].Hide()
++ if(type == SC_SCATT_DATA)
++ {
++ if(data)
++ scatt_data->scatt_vals_arr = (unsigned int *) data;
++ else {
++ scatt_data->scatt_vals_arr = (unsigned int *) G_malloc(n_vals * sizeof(unsigned int));
++ memset(scatt_data->scatt_vals_arr, 0, n_vals * sizeof(unsigned int));
++ }
++ scatt_data->b_conds_arr = NULL;
++ }
++ else if(type == SC_SCATT_CONDITIONS)
++ {
++ if(data)
++ scatt_data->b_conds_arr = (unsigned char *) data;
++ else {
++ scatt_data->b_conds_arr = (unsigned char *) G_malloc(n_vals * sizeof(unsigned char));
++ memset(scatt_data->b_conds_arr, 0, n_vals * sizeof(unsigned char));
++ }
++ scatt_data->scatt_vals_arr = NULL;
++ }
+
-+ self.SetSizer(self.mainsizer)
-+
-+ self.scatt_mgr.cursorPlotMove.connect(self.CursorPlotMove)
-+ self.scatt_mgr.renderingStarted.connect(lambda : self.stBar.AddStatusItem(text=_("Rendering..."),
-+ key='comp',
-+ priority=self.stPriors['high']))
-+ self.scatt_mgr.renderingFinished.connect(lambda : self.stBar.RemoveStatusItem(key='comp'))
++ return;
++}
+
-+ self.scatt_mgr.computingStarted.connect(lambda : self.stBar.AddStatusItem(text=_("Computing data..."),
-+ key='comp',
-+ priority=self.stPriors['high']))
+
-+ #self.SetSashGravity(0.5)
-+ #self.SplitHorizontally(self.head_panel, self.plot_panel, -50)
-+ self.Layout()
++#if 0
++void I_scd_get_range_min_max(struct scdScattData * scatt_data, CELL * band_1_min, CELL * band_1_max, CELL * band_2_min, CELL * band_2_max)
++{
+
-+ def _selCatInIScatt(self):
-+ return False
++ Rast_get_range_min_max(&(scatt_data->band_1_range), band_1_min, band_2_min);
++ Rast_get_range_min_max(&(scatt_data->band_2_range), band_2_min, band_2_max);
+
-+ def _createMainToolbar(self):
-+ return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr)
++ return;
++}
++s
++void * I_scd_get_data_ptr(struct scdScattData * scatt_data)
++{
++ if(!scatt_data->b_conds_arr)
++ return scatt_data->b_conds_arr;
++ else if(!scatt_data->scatt_vals_arr)
++ return scatt_data->scatt_vals_arr;
+
-+ def _createScattMgr(self, guiparent, giface, iclass_mapwin):
-+ return ScattsManager(guiparent=self, giface=giface, iclass_mapwin=iclass_mapwin)
++ return NULL;
++}
+
-+ def CursorPlotMove(self, x, y):
-+ x = int(round(x))
-+ y = int(round(y))
-+ self.stBar.AddStatusItem(text="%d; %d" % (x, y),
-+ key='coords',
-+ priority=self.stPriors['info'])
++int I_scd_set_value(struct scdScattData * scatt_data, unsigned int val_idx, unsigned int val)
++{
++ if(val_idx < 0 && val_idx > scatt_data->n_vals)
++ return -1;
+
-+ def NewScatterPlot(self, scatt_id, transpose):
-+ return self.plot_panel.NewScatterPlot(scatt_id, transpose)
++ if(scatt_data->b_conds_arr)
++ scatt_data->b_conds_arr[val_idx] = val;
++ else if(scatt_data->scatt_vals_arr)
++ scatt_data->scatt_vals_arr[val_idx] = val;
++ else
++ return -1;
+
-+ def ShowPlotEditingToolbar(self, show):
-+ self.toolbars["editingToolbar"].Show(show)
-+ self.Layout()
++ return 0;
++}
++#endif
+Index: lib/imagery/scatt.c
+===================================================================
+--- lib/imagery/scatt.c (revision 0)
++++ lib/imagery/scatt.c (working copy)
+@@ -0,0 +1,871 @@
++/*!
++ \file lib/imagery/scatt.c
+
-+ def ShowCategoryPanel(self, show):
-+ self.catsPanel.Show(show)
-+
-+ #if show:
-+ # self.SetSashSize(5)
-+ #else:
-+ # self.SetSashSize(0)
-+ self.plot_panel.SetVirtualSize(self.plot_panel.GetBestVirtualSize())
-+ self.Layout()
++ \brief Imagery library - functions for wx Scatter Plot Tool.
+
-+ def _createCategoryPanel(self, parent):
-+ self.catsPanel = wx.Panel(parent=parent)
-+ self.cats_list = CategoryListCtrl(parent=self.catsPanel,
-+ cats_mgr=self.scatt_mgr.GetCategoriesManager(),
-+ sel_cats_in_iscatt=self._selCatInIScatt())
++ Low level functions used by wx Scatter Plot Tool.
+
-+ self.catsPanel.SetMaxSize((-1, 150))
++ Copyright (C) 2013 by the GRASS Development Team
+
-+ box_capt = wx.StaticBox(parent=self.catsPanel, id=wx.ID_ANY,
-+ label=' %s ' % _("Classes manager for scatter plots"),)
-+ catsSizer = wx.StaticBoxSizer(box_capt, wx.VERTICAL)
++ This program is free software under the GNU General Public License
++ (>=v2). Read the file COPYING that comes with GRASS for details.
+
-+ self.toolbars['categoryToolbar'] = self._createCategoryToolbar(self.catsPanel)
++ \author Stepan Turek <stepan.turek at seznam.cz> (Mentor: Martin Landa)
++ */
++#include <stdio.h>
++#include <stdlib.h>
++#include <math.h>
++#include <string.h>
+
-+ catsSizer.Add(item=self.cats_list, proportion=1, flag=wx.EXPAND)
-+ if self.toolbars['categoryToolbar']:
-+ catsSizer.Add(item=self.toolbars['categoryToolbar'], proportion=0)
++#include <grass/gis.h>
++#include <grass/vector.h>
++#include <grass/raster.h>
++#include <grass/imagery.h>
++#include <grass/glocale.h>
+
-+ self.catsPanel.SetSizer(catsSizer)
++#include "iclass_local_proto.h"
+
-+ def CleanUp(self):
-+ pass
++struct rast_row
++{
++ CELL * row;
++ char * null_row;
++ struct Range rast_range; /*Range of whole raster.*/
++};
+
-+ def _createCategoryToolbar(self, parent):
-+ return None
++/*!
++ \brief Create pgm header.
+
-+class IScattDialog(wx.Dialog):
-+ def __init__(self, parent, giface, title=_("GRASS GIS Interactive Scatter Plot Tool"),
-+ id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE, **kwargs):
-+ wx.Dialog.__init__(self, parent, id, style=style, title = title, **kwargs)
-+ self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
++ Scatter plot internally generates pgm files. These pgms have header in format created by this function.
++
++ \param region region to be pgm header generated for
++ \param [out] header header of pgm file
++ */
++static int get_cat_rast_header(struct Cell_head * region, char * header){
++ return sprintf(header, "P5\n%d\n%d\n1\n", region->cols, region->rows);
++}
+
-+ self.iscatt_panel = MapDispIScattPanel(self, giface)
++/*!
++ \brief Create category raster conditions file.
++
++ \param cat_rast_region region to be file generated for
++ \param cat_rast path of generated category raster file
++ */
++int I_create_cat_rast(struct Cell_head * cat_rast_region, const char * cat_rast)
++{
++ FILE * f_cat_rast;
++ char cat_rast_header[1024];//TODO magic number
++ int i_row, i_col;
++ int head_nchars;
+
-+ mainsizer = wx.BoxSizer(wx.VERTICAL)
-+ mainsizer.Add(item=self.iscatt_panel, proportion=1, flag=wx.EXPAND)
++ unsigned char * row_data;
+
-+ self.SetSizer(mainsizer)
++ f_cat_rast = fopen(cat_rast, "wb");
++ if(!f_cat_rast) {
++ G_warning("Unable to create category raster condition file <%s>.", cat_rast);
++ return -1;
++ }
+
-+ self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
++ head_nchars = get_cat_rast_header(cat_rast_region, cat_rast_header);
+
-+ self.SetMinSize((300, 300))
++ fwrite(cat_rast_header, sizeof(char), head_nchars/sizeof(char), f_cat_rast);
++ if (ferror(f_cat_rast)){
++ fclose(f_cat_rast);
++ G_warning(_("Unable to write header into category raster condition file <%s>."), cat_rast);
++ return -1;
++ }
+
-+ def OnCloseWindow(self, event):
-+ event.Skip()
-+ #self.
++ row_data = (unsigned char *) G_malloc(cat_rast_region->cols * sizeof(unsigned char));
++ for(i_col = 0; i_col < cat_rast_region->cols; i_col++)
++ row_data[i_col] = 0 & 255;
+
-+class MapDispIScattPanel(IClassIScattPanel):
-+ def __init__(self, parent, giface,
-+ id=wx.ID_ANY, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
-+ IClassIScattPanel.__init__(self, parent=parent, giface=giface,
-+ id=id, style=style, **kwargs)
++ for(i_row = 0; i_row < cat_rast_region->rows; i_row++) {
++ fwrite(row_data, sizeof(unsigned char), (cat_rast_region->cols)/sizeof(unsigned char), f_cat_rast);
++ if (ferror(f_cat_rast))
++ {
++ fclose(f_cat_rast);
++ G_warning(_("Unable to write into category raster condition file <%s>."), cat_rast);
++ return -1;
++ }
++ }
+
-+ def _createCategoryToolbar(self, parent):
-+ return CategoryToolbar(parent=parent,
-+ scatt_mgr=self.scatt_mgr,
-+ cats_list=self.cats_list)
++ fclose(f_cat_rast);
++ return 0;
++}
+
-+ def _createScattMgr(self, guiparent, giface, iclass_mapwin):
-+ return ScattsManager(guiparent = self, giface = giface)
++static int print_reg(struct Cell_head * intersec, const char * pref, int dbg_level)
++{
++ G_debug(dbg_level, "%s:\n n:%f\ns:%f\ne:%f\nw:%f\nns_res:%f\new_res:%f", pref, intersec->north, intersec->south,
++ intersec->east, intersec->west, intersec->ns_res, intersec->ew_res);
++}
+
-+ def _createMainToolbar(self):
-+ return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr, opt_tools=['add_group'])
++/*!
++ \brief Find intersection region of two regions.
+
-+ def _selCatInIScatt(self):
-+ return True
++ \param A pointer to intersected region
++ \param B pointer to intersected region
++ \param [out] intersec pointer to intersection region of regions A B (relevant params of the region are: south, north, east, west)
+
-+class ScatterPlotsPanel(scrolled.ScrolledPanel):
-+ def __init__(self, parent, scatt_mgr, id = wx.ID_ANY):
-+
-+ scrolled.ScrolledPanel.__init__(self, parent)
-+ self.SetupScrolling(scroll_x = False, scroll_y = True, scrollToTop = False)
++ \return 0 if interection exists
++ \return -1 if regions does not intersect
++ */
++static int regions_intersecion(struct Cell_head * A, struct Cell_head * B, struct Cell_head * intersec)
++{
+
-+ self.scatt_mgr = scatt_mgr
++ if(B->north < A->south) return -1;
++ else if(B->north > A->north) intersec->north = A->north;
++ else intersec->north = B->north;
+
-+ self.mainPanel = wx.Panel(parent = self, id = wx.ID_ANY)
++ if(B->south > A->north) return -1;
++ else if(B->south < A->south) intersec->south = A->south;
++ else intersec->south = B->south;
+
-+ #self._createCategoryPanel()
-+ # Fancy gui
-+ self._mgr = aui.AuiManager(self.mainPanel)
-+ #self._mgr.SetManagedWindow(self)
++ if(B->east < A->west) return -1;
++ else if(B->east > A->east) intersec->east = A->east;
++ else intersec->east = B->east;
+
-+ self._mgr.Update()
++ if(B->west > A->east) return -1;
++ else if(B->west < A->west) intersec->west = A->west;
++ else intersec->west = B->west;
+
-+ self._doLayout()
-+ self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
-+ self.Bind(wx.EVT_SCROLL_CHANGED, self.OnScrollChanged)
++ if(intersec->north == intersec->south) return -1;
+
-+ self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPlotPaneClosed)
++ if(intersec->east == intersec->west) return -1;
+
-+ dlgSize = (-1, 400)
-+ #self.SetBestSize(dlgSize)
-+ #self.SetInitialSize(dlgSize)
-+ self.SetAutoLayout(1)
-+ #fix goutput's pane size (required for Mac OSX)
-+ #if self.gwindow:
-+ # self.gwindow.SetSashPosition(int(self.GetSize()[1] * .75))
-+ self.ignore_scroll = 0
-+ self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
++ return 0;
+
-+ self.scatt_i = 1
-+ self.scatt_id_scatt_i = {}
++}
+
-+ self.Bind(wx.EVT_CLOSE, self.OnClose)
++/*!
++ \brief Get rows and cols numbers, which defines intersection of the regions.
++
++ \param A pointer to intersected region
++ \param B pointer to intersected region (A and B must have same resolution)
++ \param [out] A_bounds rows and cols numbers of A stored in south, north, east, west, which defines intersection of A and B
++ \param [out] B_bounds rows and cols numbers of B stored in south, north, east, west, which defines intersection of A and B
+
-+ def ScatterPlotClosed(self, scatt_id):
++ \return 0 if interection exists
++ \return -1 if regions do not intersect
++ \return -2 resolution of regions is not same
++*/
++static int get_rows_and_cols_bounds(struct Cell_head * A, struct Cell_head * B, struct Cell_head * A_bounds, struct Cell_head * B_bounds)
++{
++ float ns_res, ew_res;
+
-+ scatt_i = self.scatt_id_scatt_i[scatt_id]
++ struct Cell_head intersec;
+
-+ name = self._getScatterPlotName(scatt_i)
-+ pane = self._mgr.GetPane(name)
++ /* TODO is it right check? */
++ if(abs(A->ns_res - B->ns_res) > GRASS_EPSILON) {
++ G_debug(0, "'get_rows_and_cols_bounds' ns_res does not fit, A->ns_res: %f B->ns_res: %f", A->ns_res, B->ns_res);
++ return -2;
++ }
+
-+ if pane.IsOk():
-+ self._mgr.ClosePane(pane)
-+ self._mgr.Update()
++ if(abs(A->ew_res - B->ew_res) > GRASS_EPSILON) {
++ G_debug(0, "'get_rows_and_cols_bounds' ew_res does not fit, A->ew_res: %f B->ew_res: %f", A->ew_res, B->ew_res);
++ return -2;
++ }
+
-+ def OnMouseWheel(self, event):
-+ #TODO very ugly find some better solution
-+ self.ignore_scroll = 3
-+ event.Skip()
++ ns_res = A->ns_res;
++ ew_res = A->ew_res;
+
-+ def ScrollChildIntoView(self, child):
-+ #For aui manager it does not work and returns position always to the top -> deactivated.
-+ pass
++ if(regions_intersecion(A, B, &intersec) == -1)
++ return -1;
+
-+ def OnPlotPaneClosed(self, event):
-+ if isinstance(event.pane.window, ScatterPlotWidget):
-+ event.pane.window.CleanUp()
++ A_bounds->north = ceil((A->north - intersec.north - ns_res * 0.5) / ns_res);
++ A_bounds->south = ceil((A->north - intersec.south - ns_res * 0.5) / ns_res);
+
-+ def OnScrollChanged(self, event):
-+ wx.CallAfter(self.Layout)
++ A_bounds->east = ceil((intersec.east - A->west - ew_res * 0.5) / ew_res);
++ A_bounds->west = ceil((intersec.west - A->west - ew_res * 0.5) / ew_res);
+
-+ def OnScroll(self, event):
-+ if self.ignore_scroll > 0:
-+ self.ignore_scroll -= 1
-+ else:
-+ event.Skip()
++ B_bounds->north = ceil((B->north - intersec.north - ns_res * 0.5) / ns_res);
++ B_bounds->south = ceil((B->north - intersec.south - ns_res * 0.5) / ns_res);
+
-+ #wx.CallAfter(self._mgr.Update)
-+ #wx.CallAfter(self.Layout)
++ B_bounds->east = ceil((intersec.east - B->west - ew_res * 0.5) / ew_res);
++ B_bounds->west = ceil((intersec.west - B->west - ew_res * 0.5) / ew_res);
+
-+ def _doLayout(self):
++ return 0;
++}
+
-+ mainsizer = wx.BoxSizer(wx.VERTICAL)
-+ mainsizer.Add(item = self.mainPanel, proportion = 1, flag = wx.EXPAND)
-+ self.SetSizer(mainsizer)
++/*!
++ \brief Insert raster map patch into pgm file.
++ Warning: calls Rast_set_window
+
-+ self.Layout()
-+ self.SetupScrolling()
++ \param patch_rast name of raster map
++ \param cat_rast_region region of category raster file
++ \param cat_rast path to category raster file
+
-+ def OnClose(self, event):
-+ """!Close dialog"""
-+ #TODO
-+ print "closed"
-+ self.scatt_mgr.CleanUp()
-+ self.Destroy()
++ \return 0 on success
++ \return -1 on failure
++*/
++int I_insert_patch_to_cat_rast(const char * patch_rast, struct Cell_head * cat_rast_region, const char * cat_rast)
++{
+
-+ def OnSettings(self, event):
-+ pass
++ FILE * f_cat_rast;
++ struct Cell_head patch_region, patch_bounds, cat_rast_bounds;
++ char cat_rast_header[1024];//TODO magic number
++ int i_row, i_col, ncols, nrows, cat_rast_col, patch_col, val;
++ int head_nchars, ret;
++ int fd_patch_rast, init_shift, step_shift;
++ unsigned char * patch_data;
+
-+ def _newScatterPlotName(self, scatt_id):
-+ name = self._getScatterPlotName(self.scatt_i)
-+ self.scatt_id_scatt_i[scatt_id] = self.scatt_i
-+ self.scatt_i += 1
-+ return name
++ char * null_chunk_row;
+
-+ def _getScatterPlotName(self, i):
-+ name = "scatter plot %d" % i
-+ return name
++ const char *mapset;
+
-+ def NewScatterPlot(self, scatt_id, transpose):
-+ #TODO needs to be resolved (should be in this class)
++ struct Cell_head patch_lines, cat_rast_lines;
+
-+ scatt = ScatterPlotWidget(parent = self.mainPanel,
-+ scatt_mgr = self.scatt_mgr,
-+ scatt_id = scatt_id,
-+ transpose = transpose)
-+ scatt.plotClosed.connect(self.ScatterPlotClosed)
++ unsigned char * row_data;
+
-+ bands = self.scatt_mgr.GetBands()
++ f_cat_rast = fopen(cat_rast, "rb+");
++ if(!f_cat_rast) {
++ G_warning(_("Unable to open category raster condtions file <%s>."), cat_rast);
++ return -1;
++ }
+
-+ #TODO too low level
-+ b1_id, b2_id = idScattToidBands(scatt_id, len(bands))
++ head_nchars = get_cat_rast_header(cat_rast_region, cat_rast_header);
++ if ((mapset = G_find_raster(patch_rast,"")) == NULL) {
++ fclose(f_cat_rast);
++ G_warning(_("Unable to find patch raster <%s>."), patch_rast);
++ return -1;
++ }
+
-+ x_b = bands[b1_id].split('@')[0]
-+ y_b = bands[b2_id].split('@')[0]
++ Rast_get_cellhd(patch_rast, mapset, &patch_region);
++ Rast_set_window(&patch_region);
++
++ if ((fd_patch_rast = Rast_open_old(patch_rast, mapset)) < 0) {
++ fclose(f_cat_rast);
++ return -1;
++ }
+
-+ if transpose:
-+ tmp = x_b
-+ x_b = y_b
-+ y_b = tmp
++ ret = get_rows_and_cols_bounds(cat_rast_region, &patch_region, &cat_rast_bounds, &patch_bounds);
++ if(ret == -2) {
++ G_warning(_("Resolutions of patch <%s> and patched file <%s> are not same."), patch_rast, cat_rast);
+
-+ caption = "%s x: %s y: %s" % (_("scatter plot"), x_b, y_b)
++ Rast_close(fd_patch_rast);
++ fclose(f_cat_rast);
+
-+ self._mgr.AddPane(scatt,
-+ aui.AuiPaneInfo().Dockable(True).Floatable(True).
-+ Name(self._newScatterPlotName(scatt_id)).MinSize((-1, 300)).
-+ Caption(caption).
-+ Center().Position(1).MaximizeButton(True).
-+ MinimizeButton(True).CaptionVisible(True).
-+ CloseButton(True).Layer(0))
++ return -1;
++ }
++ else if (ret == -1){
+
-+ self._mgr.Update()
-+
-+ self.SetVirtualSize(self.GetBestVirtualSize())
-+ self.Layout()
++ Rast_close(fd_patch_rast);
++ fclose(f_cat_rast);
+
-+ return scatt
++ return 0;
++ }
+
-+ def GetScattMgr(self):
-+ return self.scatt_mgr
++ ncols = cat_rast_bounds.east - cat_rast_bounds.west;
++ nrows = cat_rast_bounds.south - cat_rast_bounds.north;
+
-+class CategoryListCtrl(wx.ListCtrl,
-+ listmix.ListCtrlAutoWidthMixin,
-+ listmix.TextEditMixin):
++ patch_data = (unsigned char *) G_malloc(ncols * sizeof(unsigned char));
+
-+ def __init__(self, parent, cats_mgr, sel_cats_in_iscatt, id = wx.ID_ANY):
++ init_shift = head_nchars + cat_rast_region->cols * cat_rast_bounds.north + cat_rast_bounds.west;
+
-+ wx.ListCtrl.__init__(self, parent, id,
-+ style = wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|
-+ wx.LC_VRULES|wx.LC_SINGLE_SEL)
-+ self.columns = ((_('Class name'), 'name'),
-+ (_('Color'), 'color'))
++ if(fseek(f_cat_rast, init_shift, SEEK_SET) != 0) {
++ G_warning(_("Corrupted category raster conditions file <%s> (fseek failed)"), cat_rast);
+
-+ self.sel_cats_in_iscatt = sel_cats_in_iscatt
++ Rast_close(fd_patch_rast);
++ G_free(null_chunk_row);
++ fclose(f_cat_rast);
+
-+ self.Populate(columns = self.columns)
-+
-+ self.cats_mgr = cats_mgr
-+ self.SetItemCount(len(self.cats_mgr.GetCategories()))
++ return -1;
++ }
+
-+ self.rightClickedItemIdx = wx.NOT_FOUND
-+
-+ listmix.ListCtrlAutoWidthMixin.__init__(self)
++ step_shift = cat_rast_region->cols - ncols;
+
-+ listmix.TextEditMixin.__init__(self)
-+
-+ self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnCategoryRightUp) #wxMSW
-+ self.Bind(wx.EVT_RIGHT_UP, self.OnCategoryRightUp) #wxGTK
++ null_chunk_row = Rast_allocate_null_buf();
++
++ for(i_row = 0; i_row < nrows; i_row++) {
++ Rast_get_null_value_row (fd_patch_rast, null_chunk_row, i_row + patch_bounds.north);
+
-+ self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
-+ self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSel)
-+
-+ self.cats_mgr.setCategoryAttrs.connect(self.Update)
-+ self.cats_mgr.deletedCategory.connect(self.Update)
-+ self.cats_mgr.addedCategory.connect(self.Update)
++ for(i_col = 0; i_col < ncols; i_col++) {
++ patch_col = patch_bounds.west + i_col;
+
-+ def Update(self, **kwargs):
-+ self.SetItemCount(len(self.cats_mgr.GetCategories()))
-+ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
++ if(null_chunk_row[patch_col] != 1)
++ patch_data[i_col] = 1 & 255;
++ else {
++ patch_data[i_col] = 0 & 255;
++ }
++ }
+
-+ def InitCoreCats(self):
-+ self.SetItemCount(len(self.cats_mgr.GetCategories()))
-+ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
-+
-+ def SetVirtualData(self, row, column, text):
-+ attr = self.columns[column][1]
-+ if attr == 'name':
-+ try:
-+ text.encode('ascii')
-+ except UnicodeEncodeError:
-+ GMessage(parent = self, message = _("Please use only ASCII characters."))
-+ return
-+
-+ cat_id = self.cats_mgr.GetCategories()[row]
-+
-+ self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
-+ self.cats_mgr.SetCategoryAttrs(cat_id, {attr : text})
-+ self.cats_mgr.setCategoryAttrs.connect(self.Update)
-+
-+ self.Select(row)
-+
-+ def Populate(self, columns):
-+ for i, col in enumerate(columns):
-+ self.InsertColumn(i, col[0])#wx.LIST_FORMAT_RIGHT
-+
-+ self.SetColumnWidth(0, 100)
-+ self.SetColumnWidth(1, 100)
-+
-+ def AddCategory(self):
-+
-+ self.cats_mgr.addedCategory.disconnect(self.Update)
-+ cat_id = self.cats_mgr.AddCategory()
-+ self.cats_mgr.addedCategory.connect(self.Update)
-+
-+ if cat_id < 0:
-+ GError(_("Maximum limit of categories number was reached."))
-+ return
-+ self.SetItemCount(len(self.cats_mgr.GetCategories()))
-+
-+ def DeleteCategory(self):
-+ indexList = sorted(self.GetSelectedIndices(), reverse = True)
-+ cats = []
-+ for i in indexList:
-+ # remove temporary raster
-+ cat_id = self.cats_mgr.GetCategories()[i]
++ fwrite(patch_data, sizeof(unsigned char), (ncols)/sizeof(unsigned char), f_cat_rast);
++ if (ferror(f_cat_rast))
++ {
++ G_warning(_("Unable to write into category raster conditions file <%s>"), cat_rast);
+
-+ cats.append(cat_id)
++ Rast_close(fd_patch_rast);
++ G_free(null_chunk_row);
++ fclose(f_cat_rast);
+
-+ self.cats_mgr.deletedCategory.disconnect(self.Update)
-+ self.cats_mgr.DeleteCategory(cat_id)
-+ self.cats_mgr.deletedCategory.connect(self.Update)
++ return -1;
++ }
++ if(fseek(f_cat_rast, step_shift, SEEK_CUR) != 0) {
++ G_warning(_("Corrupted category raster conditions file <%s> (fseek failed)"), cat_rast);
+
-+ self.SetItemCount(len(self.cats_mgr.GetCategories()))
-+
-+ def OnSel(self, event):
-+ if self.sel_cats_in_iscatt:
-+ indexList = self.GetSelectedIndices()
-+ sel_cats = []
-+ cats = self.cats_mgr.GetCategories()
-+ for i in indexList:
-+ sel_cats.append(cats[i])
++ Rast_close(fd_patch_rast);
++ G_free(null_chunk_row);
++ fclose(f_cat_rast);
++
++ return -1;
++ }
++ }
+
-+ if sel_cats:
-+ self.cats_mgr.SetSelectedCat(sel_cats[0])
-+ event.Skip()
++ Rast_close(fd_patch_rast);
++ G_free(null_chunk_row);
++ fclose(f_cat_rast);
++ return 0;
++}
+
-+ def GetSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
-+ indices = []
-+ lastFound = -1
-+ while True:
-+ index = self.GetNextItem(lastFound, wx.LIST_NEXT_ALL, state)
-+ if index == -1:
-+ break
-+ else:
-+ lastFound = index
-+ indices.append(index)
-+ return indices
++/*!
++ \brief Updates scatter plots data in category by pixels which meets category conditions.
++
++ \param bands_rows data represents data describig one row from raster band
++ \param belongs_pix array which defines which pixels belongs to category (1 value) and which not (0 value)
++ \param [out] scatts pointer to scScatts struct of type SC_SCATT_DATA, which are modified according to values in belongs_pix
++*/
++static inline void update_cat_scatt_plts(struct rast_row * bands_rows, unsigned short * belongs_pix, struct scScatts * scatts)
++{
++ int band_axis_1, band_axis_2, i_scatt, array_idx, cat_idx, i_chunk_rows_pix, max_arr_idx;
+
-+ def OnEdit(self, event):
-+ currentItem = event.m_itemIndex
-+ currentCol = event.m_col
++ CELL * b_1_row;
++ CELL * b_2_row;
++ char * b_1_null_row,* b_2_null_row;
++ struct rast_row b_1_rast_row, b_2_rast_row;
+
-+ if currentCol == 1:
-+ dlg = wx.ColourDialog(self)
-+ dlg.GetColourData().SetChooseFull(True)
++ struct Range b_1_range, b_2_range;
++ int b_1_range_size;
+
-+ if dlg.ShowModal() == wx.ID_OK:
-+ color = dlg.GetColourData().GetColour().Get()
-+ color = ':'.join(map(str, color))
-+ self.SetVirtualData(currentItem, currentCol, color)
-+ dlg.Destroy()
-+ wx.CallAfter(self.SetFocus)
-+
-+ event.Skip()
-+
-+
-+ def DeselectAll(self):
-+ """!Deselect all items"""
-+ indexList = self.GetSelectedIndices()
-+ for i in indexList:
-+ self.Select(i, on = 0)
-+
-+ # no highlight
-+ self.OnCategorySelected(None)
-+
-+ def OnGetItemText(self, item, col):
-+ attr = self.columns[col][1]
-+ cat_id = self.cats_mgr.GetCategories()[item]
++ int row_size = Rast_window_cols();
+
-+ return self.cats_mgr.GetCategoryAttrs(cat_id)[attr]
++ int * scatts_bands = scatts->scatts_bands;
+
-+ def OnGetItemImage(self, item):
-+ return -1
++ for(i_scatt = 0; i_scatt < scatts->n_a_scatts; i_scatt++)
++ {
++ b_1_rast_row = bands_rows[scatts_bands[i_scatt * 2]];
++ b_2_rast_row = bands_rows[scatts_bands[i_scatt * 2 + 1]];
+
-+ def OnGetItemAttr(self, item):
-+ return None
++ b_1_row = b_1_rast_row.row;
++ b_2_row = b_2_rast_row.row;
+
-+ def OnCategoryRightUp(self, event):
-+ """!Show context menu on right click"""
-+ item, flags = self.HitTest((event.GetX(), event.GetY()))
-+ if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
-+ self.rightClickedItemIdx = item
++ b_1_null_row = b_1_rast_row.null_row;
++ b_2_null_row = b_2_rast_row.null_row;
+
-+ # generate popup-menu
-+ cat_idx = self.rightClickedItemIdx
-+
-+ cats = self.cats_mgr.GetCategories()
-+ cat_id = cats[cat_idx]
-+ showed = self.cats_mgr.GetCategoryAttrs(cat_id)['show']
++ b_1_range = b_1_rast_row.rast_range;
++ b_2_range = b_2_rast_row.rast_range;
+
-+ menu = wx.Menu()
++ b_1_range_size = b_1_range.max - b_1_range.min + 1;
++ max_arr_idx = (b_1_range.max - b_1_range.min + 1) * (b_2_range.max - b_2_range.min + 1);
+
-+ if showed:
-+ text = _("Hide")
-+ else:
-+ text = _("Show")
++ for(i_chunk_rows_pix = 0; i_chunk_rows_pix < row_size; i_chunk_rows_pix++)
++ {
++ /* pixel does not belongs to scatter plot or has null value in one of the bands */
++ if(!belongs_pix[i_chunk_rows_pix] ||
++ b_1_null_row[i_chunk_rows_pix] == 1 ||
++ b_2_null_row[i_chunk_rows_pix] == 1)
++ continue;
+
-+ item_id = wx.NewId()
-+ menu.Append(item_id, text = text)
-+ self.Bind(wx.EVT_MENU, lambda event : self._setCatAttrs(cat_id=cat_id,
-+ attrs={'show' : not showed}),
-+ id=item_id)
-+ menu.AppendSeparator()
-+
-+ if cat_idx != 0:
-+ item_id = wx.NewId()
-+ menu.Append(item_id, text=_("Move category up"))
-+ self.Bind(wx.EVT_MENU, self.OnMoveUp, id=item_id)
++ /* index in scatter plot array */
++ array_idx = b_1_row[i_chunk_rows_pix] - b_1_range.min + (b_2_row[i_chunk_rows_pix] - b_2_range.min) * b_1_range_size;
+
-+ if cat_idx != len(cats) - 1:
-+ item_id = wx.NewId()
-+ menu.Append(item_id, text=_("Move category down"))
-+ self.Bind(wx.EVT_MENU, self.OnMoveDown, id=item_id)
++ if(array_idx < 0 || array_idx >= max_arr_idx) {
++ G_warning ("Data inconsistent. Value computed for scatter plot is out of initialized range.");
++ continue;
++ }
+
-+ menu.AppendSeparator()
-+
-+ item_id = wx.NewId()
-+ menu.Append(item_id, text=_("Change opacity level"))
-+ self.Bind(wx.EVT_MENU, self.OnPopupOpacityLevel, id=item_id)
++ /* increment scatter plot value */
++ ++scatts->scatts_arr[i_scatt]->scatt_vals_arr[array_idx];
++ }
++ }
++}
+
-+ item_id = wx.NewId()
-+ menu.Append(item_id, text=_("Export raster"))
-+ self.Bind(wx.EVT_MENU, self.OnExportCatRast, id=item_id)
++/*!
++ \brief Computes scatter plots data from chunk_rows.
+
++ \param scatts pointer to scScatts struct of type SC_SCATT_DATA, where are computed scatter plots stored
++ \param scatt_conds pointer to scScatts struct of type SC_SCATT_CONDITIONS, where are selected areas (condtitions)stored
+
-+ self.PopupMenu(menu)
-+ menu.Destroy()
++ \param chunk_rows data arrays of chunk_rows from analyzed raster bands (all data in chunk_rows, null_chunk_rows and belongs_pix arrays represents same region)
++ \param null_chunk_rows null data arrays of chunk_rows from analyzed raster bands
++ \param row_size size of data in chunk_rows, null_chunk_rows and belongs_pix arrays
++ \param f_cats_rasts_conds file which stores selected areas (conditions) from mapwindow see I_create_cat_rast() and I_pa
++ \param fd_cats_rasts array of openedraster maps which represents all selected pixels for category
++ \param region analysis region
+
-+ def OnExportCatRast(self, event):
-+ """!Export training areas"""
-+ #TODO
-+ # GMessage(parent=self, message=_("No class raster to export."))
-+ # return
++ \return 0 on success
++ \return -1 on failure
++*/
++static inline int compute_scatts_from_chunk_row(struct Cell_head *region, struct scCats * scatt_conds,
++ FILE ** f_cats_rasts_conds, struct rast_row * bands_rows,
++ struct scCats * scatts, int * fd_cats_rasts)
++{
+
-+ cat_idx = self.rightClickedItemIdx
-+ cat_id = self.cats_mgr.GetCategories()[cat_idx]
++ int i_rows_pix, i_cat, i_scatt, n_a_scatts, n_pixs;
++ int cat_id, scatt_plts_cat_idx, array_idx, max_arr_idx;
++ char * b_1_null_row,* b_2_null_row;
++ struct rast_row b_1_rast_row, b_2_rast_row;
++ CELL * cat_rast_row;
+
-+ self.cats_mgr.ExportCatRast(cat_id)
++ struct scScatts * scatts_conds;
++ struct scScatts * scatts_scatt_plts;
++ struct scdScattData * conds;
+
-+ def OnMoveUp(self, event):
-+ cat_idx = self.rightClickedItemIdx
-+ cat_id = self.cats_mgr.GetCategories()[cat_idx]
-+ self.cats_mgr.ChangePosition(cat_id, cat_idx - 1)
-+ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
++ struct Range b_1_range, b_2_range;
++ int b_1_range_size;
+
-+ def OnMoveDown(self, event):
-+ cat_idx = self.rightClickedItemIdx
-+ cat_id = self.cats_mgr.GetCategories()[cat_idx]
-+ self.cats_mgr.ChangePosition(cat_id, cat_idx + 1)
-+ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
++ int * scatts_bands;
++ struct scdScattData ** scatts_arr;
+
-+ def OnPopupOpacityLevel(self, event):
-+ """!Popup opacity level indicator"""
++ CELL * b_1_row;
++ CELL * b_2_row;
++ unsigned char * i_scatt_conds;
+
-+ cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
-+ cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
-+ value = cat_attrs['opacity'] * 100
-+ name = cat_attrs['name']
++ int row_size = Rast_window_cols();
+
-+ dlg = SetOpacityDialog(self, opacity = value,
-+ title = _("Change opacity of class <%s>" % name))
++ unsigned short * belongs_pix = (unsigned short *) G_malloc(row_size * sizeof(unsigned short));
++ unsigned char * rast_pixs = (unsigned char *) G_malloc(row_size * sizeof(unsigned char));
++ cat_rast_row = Rast_allocate_c_buf();
+
-+ dlg.applyOpacity.connect(lambda value:
-+ self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value}))
-+ dlg.CentreOnParent()
++
++ for(i_cat = 0; i_cat < scatt_conds->n_a_cats; i_cat++)
++ {
++ scatts_conds = scatt_conds->cats_arr[i_cat];
+
-+ if dlg.ShowModal() == wx.ID_OK:
-+ self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value})
-+
-+ dlg.Destroy()
++ cat_id = scatt_conds->cats_ids[i_cat];
+
-+ def _setCatAttrs(self, cat_id, attrs):
-+ self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
-+ self.cats_mgr.SetCategoryAttrs(cat_id, attrs)
-+ self.cats_mgr.setCategoryAttrs.connect(self.Update)
++ scatt_plts_cat_idx = scatts->cats_idxs[cat_id];
++ if(scatt_plts_cat_idx < 0)
++ continue;
++ scatts_scatt_plts = scatts->cats_arr[scatt_plts_cat_idx];
+
++ G_zero(belongs_pix, row_size * sizeof(unsigned short));
+
-+class AddScattPlotDialog(wx.Dialog):
++ /* if category has no conditions defined, scatter plots without any constraint are computed (default scatter plots) */
++ if(!scatts_conds->n_a_scatts && !f_cats_rasts_conds[i_cat]) {
++ for(i_scatt = 0; i_scatt < scatts_scatt_plts->n_a_scatts; i_scatt++)
++ {
++ /* all pixels belongs */
++ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
++ belongs_pix[i_rows_pix] = 1;
++ }
++ }
++ /* compute belonging pixels for defined conditions */
++ else
++ {
++ scatts_bands = scatts_conds->scatts_bands;
+
-+ def __init__(self, parent, bands, id = wx.ID_ANY):
-+
-+ wx.Dialog.__init__(self, parent, title = ("Add scatter plot"), id = id)
++ /* check conditions from category raster condtitions file */
++ if(f_cats_rasts_conds[i_cat]) {
++ n_pixs = fread(rast_pixs, sizeof(unsigned char), (row_size)/sizeof(unsigned char), f_cats_rasts_conds[i_cat]);
+
-+ self.bands = bands
++ if (ferror(f_cats_rasts_conds[i_cat]))
++ {
++ G_free(rast_pixs);
++ G_free(belongs_pix);
++ G_warning(_("Unable to read from category raster condtition file."));
++ return -1;
++ }
++ if (n_pixs != n_pixs) {
++ G_free(rast_pixs);
++ G_free(belongs_pix);
++ G_warning(_("Invalid size of category raster conditions file."));
++ return -1;
+
-+ self.scatt_id = None
++ }
+
-+ self._createWidgets()
++ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
++ {
++ if(rast_pixs[i_rows_pix] != 0 & 255)
++ belongs_pix[i_rows_pix] = 1;
++ }
++ }
+
-+ def _createWidgets(self):
++ /* check condtions defined in scatter plots*/
++ for(i_scatt = 0; i_scatt < scatts_conds->n_a_scatts; i_scatt++)
++ {
++ b_1_rast_row = bands_rows[scatts_bands[i_scatt * 2]];
++ b_2_rast_row = bands_rows[scatts_bands[i_scatt * 2 + 1]];
+
-+ self.labels = {}
-+ self.params = {}
++ b_1_row = b_1_rast_row.row;
++ b_2_row = b_2_rast_row.row;
+
-+ self.band_1_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("x axis:"))
++ b_1_null_row = b_1_rast_row.null_row;
++ b_2_null_row = b_2_rast_row.null_row;
+
-+ self.band_1_ch = wx.ComboBox(parent = self, id = wx.ID_ANY,
-+ choices = self.bands,
-+ style = wx.CB_READONLY, size = (350, 30))
++ b_1_range = b_1_rast_row.rast_range;
++ b_2_range = b_2_rast_row.rast_range;
+
-+ self.band_2_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("y axis:"))
++ b_1_range_size = b_1_range.max - b_1_range.min + 1;
++ max_arr_idx = (b_1_range.max - b_1_range.min + 1) * (b_2_range.max - b_2_range.min + 1);
+
-+ self.band_2_ch = wx.ComboBox(parent = self, id = wx.ID_ANY,
-+ choices = self.bands,
-+ style = wx.CB_READONLY, size = (350, 30))
++ i_scatt_conds = scatts_conds->scatts_arr[i_scatt]->b_conds_arr;
+
-+ # buttons
-+ self.btn_close = wx.Button(parent = self, id = wx.ID_CANCEL)
-+
-+ self.btn_ok = wx.Button(parent = self, id = wx.ID_OK, label = _("&OK"))
++ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
++ {
++ /* pixels already belongs to category from category raster conditions file or contains null value in one of the bands */
++ if(belongs_pix[i_rows_pix] ||
++ b_1_null_row[i_rows_pix] == 1 ||
++ b_2_null_row[i_rows_pix] == 1)
++ continue;
+
-+ self._layout()
++ array_idx = b_1_row[i_rows_pix] - b_1_range.min + (b_2_row[i_rows_pix] - b_2_range.min) * b_1_range_size;
++ if(array_idx < 0 || array_idx >= max_arr_idx) {
++ G_warning ("Data inconsistent. Value computed for scatter plot is out of initialized range.");
++ continue;
++ }
++ /* pixels meets condtion defined in scatter plot */
++ if(i_scatt_conds[array_idx])
++ belongs_pix[i_rows_pix] = 1;
++ }
++ }
++ }
+
-+ def _layout(self):
++ /* update category raster with belonging pixels */
++ if(fd_cats_rasts[i_cat] >= 0) {
++ Rast_set_null_value(cat_rast_row, Rast_window_cols(), CELL_TYPE);
+
-+ border = wx.BoxSizer(wx.VERTICAL)
-+ dialogSizer = wx.BoxSizer(wx.VERTICAL)
++ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
++ if(belongs_pix[i_rows_pix])
++ cat_rast_row[i_rows_pix] = belongs_pix[i_rows_pix];
+
-+ regionSizer = wx.BoxSizer(wx.HORIZONTAL)
++ Rast_put_c_row (fd_cats_rasts[i_cat], cat_rast_row);
++ }
+
-+ dialogSizer.Add(item = self._addSelectSizer(title = self.band_1_label,
-+ sel = self.band_1_ch))
++ /* update scatter plots with belonging pixels */
++ update_cat_scatt_plts(bands_rows, belongs_pix, scatts_scatt_plts);
++ }
+
-+ dialogSizer.Add(item = self._addSelectSizer(title = self.band_2_label,
-+ sel = self.band_2_ch))
++ G_free(cat_rast_row);
++ G_free(rast_pixs);
++ G_free(belongs_pix);
+
-+ # buttons
-+ self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
++ return 0;
++}
+
-+ self.btnsizer.Add(item = self.btn_close, proportion = 0,
-+ flag = wx.ALL | wx.ALIGN_CENTER,
-+ border = 10)
-+
-+ self.btnsizer.Add(item = self.btn_ok, proportion = 0,
-+ flag = wx.ALL | wx.ALIGN_CENTER,
-+ border = 10)
++/*!
++ \brief Get list if bands needed to be opened for analysis from scCats struct.
++*/
++static void get_needed_bands(struct scCats * cats, int * b_needed_bands)
++{
++ // results in b_needed_bands - array of bools - if item has value 1, band (defined by item index) is needed to be opened
++ int i_cat, i_scatt, cat_id;
+
-+ dialogSizer.Add(item = self.btnsizer, proportion = 0,
-+ flag = wx.ALIGN_CENTER)
++ for(i_cat = 0; i_cat < cats->n_a_cats; i_cat++)
++ {
++ for(i_scatt = 0; i_scatt < cats->cats_arr[i_cat]->n_a_scatts; i_scatt++)
++ {
++ G_debug(3, "Active scatt %d in catt %d", i_scatt, i_cat);
+
-+ border.Add(item = dialogSizer, proportion = 0,
-+ flag = wx.ALL, border = 5)
++ b_needed_bands[cats->cats_arr[i_cat]->scatts_bands[i_scatt * 2]] = 1;
++ b_needed_bands[cats->cats_arr[i_cat]->scatts_bands[i_scatt * 2 + 1]] = 1;
++ }
++ }
++ return;
++}
+
-+ self.SetSizer(border)
-+ self.Layout()
-+ self.Fit()
++/*!
++ \brief Helper function for clean up.
++*/
++static void free_compute_scatts_data(int * fd_bands, struct rast_row * bands_rows, int n_a_bands, int * bands_ids,
++ int * fd_cats_rasts, FILE ** f_cats_rasts_conds, int n_a_cats)
++{
++ int i, band_id;
+
-+ # bindings
-+ self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
-+ self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
++ for(i = 0; i < n_a_bands; i++)
++ {
++ band_id = bands_ids[i];
++ if(band_id >= 0) {
++ Rast_close(fd_bands[i]);
++ G_free(bands_rows[band_id].row);
++ G_free(bands_rows[band_id].null_row);
++ }
++ }
++
++ if(f_cats_rasts_conds)
++ for(i = 0; i < n_a_cats; i++)
++ if(f_cats_rasts_conds[i])
++ fclose(f_cats_rasts_conds[i]);
+
-+ def _addSelectSizer(self, title, sel):
-+ """!Helper layout function.
-+ """
-+ selSizer = wx.BoxSizer(orient = wx.VERTICAL)
++ if(fd_cats_rasts)
++ for(i = 0; i < n_a_cats; i++)
++ if(fd_cats_rasts[i] >= 0)
++ Rast_close(fd_cats_rasts[i]);
+
-+ selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
-+ selTitleSizer.Add(item = title, proportion = 1,
-+ flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
++}
+
-+ selSizer.Add(item = selTitleSizer, proportion = 0,
-+ flag = wx.EXPAND)
++/*!
++ \brief Compute scatter plots data.
+
-+ selSizer.Add(item = sel, proportion = 1,
-+ flag = wx.EXPAND | wx.ALL| wx.ALIGN_CENTER_VERTICAL,
-+ border = 5)
++ If category has not defined no category raster condition file and no scatter plot with consdtion,
++ default scatter plot is computed.
++ Warning: calls Rast_set_window
+
-+ return selSizer
++ \param region analysis region, beaware that all input data must be prepared for this region (bands (their ranges), cats_rasts_conds rasters...)
++ \param region function calls Rast_set_window for this region
++ \param scatt_conds pointer to scScatts struct of type SC_SCATT_CONDITIONS, where are stored selected areas (conditions) in scatter plots
++ \param cats_rasts_conds paths to category raster conditions files representing selected areas (conditions) in rasters for every category
++ \param cats_rasts_conds index in array represents corresponding category id
++ \param cats_rasts_conds for manupulation with category raster conditions file see also I_id_scatt_to_bands() and I_insert_patch_to_cat_rast()
++ \param bands names of analyzed bands, order of bands is defined by their id
++ \param n_bands number of bands
++ \param [out] scatts pointer to scScatts struct of type SC_SCATT_DATA, where are computed scatter plots stored
++ \param [out] cats_rasts array of raster maps names where will be stored all selected pixels for every category
+
-+ def OnClose(self, event):
-+ """!Close dialog
-+ """
-+ if not self.IsModal():
-+ self.Destroy()
-+ event.Skip()
++ \return 0 on success
++ \return -1 on failure
++*/
++int I_compute_scatts(struct Cell_head *region, struct scCats * scatt_conds, const char ** cats_rasts_conds,
++ const char ** bands, int n_bands, struct scCats * scatts, const char ** cats_rasts)
++{
++ const char *mapset;
++ char header[1024];
+
-+ def OnOk(self, event):
-+ """!
-+ """
-+ band_1 = self.band_1_ch.GetSelection()
-+ band_2 = self.band_2_ch.GetSelection()
++ int fd_cats_rasts[scatt_conds->n_a_cats];
++ FILE * f_cats_rasts_conds[scatt_conds->n_a_cats];
+
++ struct rast_row bands_rows[n_bands];
+
-+ if band_1 == band_2:
-+ GError(_("Selected bands must be different."))
-+ return
-+
-+ #TODO axes selection
-+ if band_1 > band_2:
-+ tmp_band = band_2
-+ band_2 = band_1
-+ band_1 = band_2
++ RASTER_MAP_TYPE data_type;
+
-+ self.scatt_id = idBandsToidScatt(band_1, band_2, len(self.bands))
++ int nrows, i_band, n_a_bands, band_id,
++ i, i_row, head_nchars, i_cat, id_cat;
++
++ int fd_bands[n_bands];
++ int bands_ids[n_bands];
++ int b_needed_bands[n_bands];
+
-+ event.Skip()
++ Rast_set_window(region);
+
-+ def GetScattId(self):
-+ return self.scatt_id
-Index: gui/wxpython/scatt_plot/__init__.py
-===================================================================
---- gui/wxpython/scatt_plot/__init__.py (revision 0)
-+++ gui/wxpython/scatt_plot/__init__.py (working copy)
-@@ -0,0 +1,11 @@
-+all = [
-+ 'dialogs',
-+ 'controllers',
-+ 'frame',
-+ 'gthreading',
-+ 'plots',
-+ 'scatt_core',
-+ 'toolbars',
-+ 'scatt_core',
-+ 'core_c',
-+ ]
-Index: gui/wxpython/scatt_plot/plots.py
-===================================================================
---- gui/wxpython/scatt_plot/plots.py (revision 0)
-+++ gui/wxpython/scatt_plot/plots.py (working copy)
-@@ -0,0 +1,943 @@
-+"""!
-+ at package scatt_plot.dialogs
++ for(i_band = 0; i_band < n_bands; i_band++)
++ fd_bands[i_band] = -1;
++
++ for(i_band = 0; i_band < n_bands; i_band++)
++ bands_ids[i_band] = -1;
+
-+ at brief Ploting widgets.
++ if (n_bands != scatts->n_bands ||
++ n_bands != scatt_conds->n_bands)
++ return -1;
+
-+Classes:
++ memset(b_needed_bands, 0, (size_t)n_bands * sizeof(int));
+
-+(C) 2013 by the GRASS Development Team
++ get_needed_bands(scatt_conds, &b_needed_bands[0]);
++ get_needed_bands(scatts, &b_needed_bands[0]);
+
-+This program is free software under the GNU General Public License
-+(>=v2). Read the file COPYING that comes with GRASS for details.
++ n_a_bands = 0;
+
-+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
-+"""
-+import wx
-+import numpy as np
-+from math import ceil
-+#TODO testing
-+import time
-+from multiprocessing import Process, Queue
++ /* open band rasters, which are needed for computation */
++ for(band_id = 0; band_id < n_bands; band_id++)
++ {
++ if(b_needed_bands[band_id])
++ {
++ G_debug(3, "Opening raster no. %d with name: %s", band_id, bands[band_id]);
+
-+from copy import deepcopy
-+from scatt_plot.core_c import MergeArrays, ApplyColormap
-+from core.settings import UserSettings
++ if ((mapset = G_find_raster2(bands[band_id],"")) == NULL) {
++ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands,
++ bands_ids, NULL, NULL, scatt_conds->n_a_cats);
++ G_warning(_("Unbale to read find raster <%s>"), bands[band_id]);
++ return -1;
++ }
++
++ if ((fd_bands[n_a_bands] = Rast_open_old(bands[band_id], mapset)) < 0) {
++ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands,
++ bands_ids, NULL, NULL, scatt_conds->n_a_cats);
++ G_warning(_("Unbale to open raster <%s>"), bands[band_id]);
++ return -1;
++ }
+
-+try:
-+ import matplotlib
-+ matplotlib.use('WXAgg')
-+ from matplotlib.figure import Figure
-+ from matplotlib.backends.backend_wxagg import \
-+ FigureCanvasWxAgg as FigCanvas
-+ from matplotlib.lines import Line2D
-+ from matplotlib.artist import Artist
-+ from matplotlib.mlab import dist_point_to_segment
-+ from matplotlib.patches import Polygon, Ellipse, Rectangle
-+ import matplotlib.image as mi
-+ import matplotlib.colors as mcolors
-+ import matplotlib.cbook as cbook
-+except ImportError as e:
-+ raise ImportError(_("Unable to import matplotlib (try to install it).\n%s") % e)
++ data_type = Rast_get_map_type(fd_bands[n_a_bands]);
++ if(data_type != CELL_TYPE) {
++ G_warning(_("Raster <%s> type is not <%s>"), bands[band_id], "CELL");
++ return -1;
++ }
+
-+import grass.script as grass
-+from grass.pydispatch.signal import Signal
++ bands_rows[band_id].row = Rast_allocate_c_buf();
++ bands_rows[band_id].null_row = Rast_allocate_null_buf();
+
-+class ScatterPlotWidget(wx.Panel):
-+ def __init__(self, parent, scatt_id, scatt_mgr, transpose,
-+ id = wx.ID_ANY):
++ if(Rast_read_range(bands[band_id], mapset, &bands_rows[band_id].rast_range) != 1){
++ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands,
++ bands_ids, NULL, NULL, scatt_conds->n_a_cats);
++ G_warning(_("Unable to read range of raster <%s>"), bands[band_id]);
++ return -1;
++ }
+
-+ wx.Panel.__init__(self, parent, id)
++ bands_ids[n_a_bands] = band_id;
++ ++n_a_bands;
++ }
++ }
+
-+ self.parent = parent
-+ self.full_extend = None
-+ self.mode = None
++ /* open category rasters condition files and category rasters */
++ for(i_cat = 0; i_cat < scatts->n_a_cats; i_cat++)
++ {
++ id_cat = scatts->cats_ids[i_cat];
++ if(cats_rasts[id_cat]) {
++ fd_cats_rasts[i_cat] = Rast_open_new(cats_rasts[id_cat], CELL_TYPE);
++ }
++ else
++ fd_cats_rasts[i_cat] = -1;
+
-+ self._createWidgets()
-+ self._doLayout()
-+ self.scatt_id = scatt_id
-+ self.scatt_mgr = scatt_mgr
++ if(cats_rasts_conds[id_cat]) {
++ f_cats_rasts_conds[i_cat] = fopen(cats_rasts_conds[id_cat], "r");
++ if(!f_cats_rasts_conds[i_cat]) {
++ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands, bands_ids,
++ f_cats_rasts_conds, f_cats_rasts_conds, scatt_conds->n_a_cats);
++ G_warning(_("Unable to open category raster condtition file <%s>"), bands[band_id]);
++ return -1;
++ }
++ }
++ else
++ f_cats_rasts_conds[i_cat] = NULL;
++ }
+
-+ self.cidpress = None
-+ self.cidrelease = None
++ head_nchars = get_cat_rast_header(region, header);
++ for(i_cat = 0; i_cat < scatt_conds->n_a_cats; i_cat++)
++ if(f_cats_rasts_conds[i_cat])
++ if( fseek(f_cats_rasts_conds[i_cat] , head_nchars, SEEK_SET) != 0) {
++ G_warning(_("Corrupted category raster conditions file (fseek failed)"));
++ return -1;
++ }
+
-+ self.transpose = transpose
++ nrows = Rast_window_rows();
+
-+ self.inverse = False
++ /* analyze bands by rows */
++ for (i_row = 0; i_row < nrows; i_row++)
++ {
++ for(i_band = 0; i_band < n_a_bands; i_band++)
++ {
++ band_id = bands_ids[i_band];
++ Rast_get_c_row(fd_bands[i_band], bands_rows[band_id].row, i_row);
++ Rast_get_null_value_row (fd_bands[i_band], bands_rows[band_id].null_row, i_row);
++ }
++ if(compute_scatts_from_chunk_row(region, scatt_conds, f_cats_rasts_conds, bands_rows, scatts, fd_cats_rasts) == -1)
++ {
++ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands, bands_ids, fd_cats_rasts,
++ f_cats_rasts_conds, scatt_conds->n_a_cats);
++ return -1;
++ }
++
++ }
++ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands, bands_ids,
++ fd_cats_rasts, f_cats_rasts_conds, scatt_conds->n_a_cats);
++ return 0;
++}
+
-+ self.SetSize((200, 100))
-+ self.Layout()
++/*!
++ \brief Merge arrays according to opacity.
++ Every pixel in array must be represented by 4 values (RGBA).
++
+
-+ self.base_scale = 1.2
-+ self.Bind(wx.EVT_CLOSE,lambda event : self.CleanUp())
++ \param merged_arr array which will be overlayd with overlay_arr
++ \param overlay_arr array to be merged_arr overlayed with
++ \param rows number of rows for the both arrays
++ \param cols number of columns for the both arrays
++ \param alpha transparency of the overlay array for merging
+
-+ self.plotClosed = Signal("ScatterPlotWidget.plotClosed")
-+ self.cursorMove = Signal("ScatterPlotWidget.cursorMove")
++ \return 0
++*/
++int I_merge_arrays(unsigned char * merged_arr, unsigned char * overlay_arr, unsigned rows, unsigned cols, double alpha)
++{
++ unsigned int i_row, i_col, i_b;
++ unsigned int row_idx, col_idx, idx;
++ unsigned int c_a_i, c_a;
+
-+ self.contex_menu = ScatterPlotContextMenu(plot = self)
++ for(i_row = 0; i_row < rows; i_row++)
++ {
++ row_idx = i_row * cols;
++ for(i_col = 0; i_col < cols; i_col++)
++ {
++ col_idx = 4 * (row_idx + i_col);
++ idx = col_idx + 3;
+
-+ self.ciddscroll = None
++ c_a = overlay_arr[idx] * alpha;
++ c_a_i = 255 - c_a;
+
-+ self.canvas.mpl_connect('motion_notify_event', self.Motion)
-+ self.canvas.mpl_connect('button_press_event', self.OnPress)
-+ self.canvas.mpl_connect('button_release_event', self.OnRelease)
-+ self.canvas.mpl_connect('draw_event', self.draw_callback)
++ merged_arr[idx] = (c_a_i * (int)merged_arr[idx] + c_a * 255) / 255;
++
++ for(i_b = 0; i_b < 3; i_b++)
++ {
++ idx = col_idx + i_b;
++ merged_arr[idx] = (c_a_i * (int)merged_arr[idx] + c_a * (int)overlay_arr[idx]) / 255;
++ }
++ }
++ }
++ return 0;
++}
+
-+ def draw_callback(self, event):
-+ self.polygon_drawer.draw_callback(event)
-+ self.axes.draw_artist(self.zoom_rect)
+
-+ def _createWidgets(self):
++int I_apply_colormap(unsigned char * vals, unsigned char * vals_mask, unsigned vals_size, unsigned char * colmap, unsigned char * col_vals){
++ unsigned int i_val;
++ int v, i, i_cm;
+
-+ # Create the mpl Figure and FigCanvas objects.
-+ # 5x4 inches, 100 dots-per-inch
-+ #
-+ self.dpi = 100
-+ self.fig = Figure((1.0, 1.0), dpi=self.dpi)
-+ self.fig.autolayout = True
++ for(i_val = 0; i_val < vals_size; i_val++){
++ i_cm = 4 * i_val;
+
-+ self.canvas = FigCanvas(self, -1, self.fig)
-+
-+ self.axes = self.fig.add_axes([0.0,0.0,1,1])
++ v = vals[i_val];
+
-+ pol = Polygon(list(zip([0], [0])), animated=True)
-+ self.axes.add_patch(pol)
-+ self.polygon_drawer = PolygonDrawer(self.axes, pol = pol, empty_pol = True)
++ if(vals_mask && vals_mask[i_val])
++ for(i = 0; i < 4; i++) col_vals[i_cm + i] = colmap[258 * 4 + i];
++ else if(v > 255)
++ for(i = 0; i < 4; i++) col_vals[i_cm + i] = colmap[257 * 4 + i];
++ else if(v < 0)
++ for(i = 0; i < 4; i++) col_vals[i_cm + i] = colmap[256 * 4 + i];
++ else
++ for(i = 0; i < 4; i++){ col_vals[i_cm + i] = colmap[v * 4 + i];
++ }
++ }
++ return 0;
++}
+
-+ self.zoom_wheel_coords = None
-+ self.zoom_rect_coords = None
-+ self.zoom_rect = Polygon(list(zip([0], [0])), facecolor = 'none')
-+ self.zoom_rect.set_visible(False)
-+ self.axes.add_patch(self.zoom_rect)
+
-+ def ZoomToExtend(self):
-+ if self.full_extend:
-+ self.axes.axis(self.full_extend)
-+ self.canvas.draw()
++int I_rasterize(double * polygon, int pol_n_pts, unsigned char * rast, unsigned char val, struct Cell_head *rast_region){
++ int i;
++ int x0, x1, y;
++ int row, row_idx, i_col;
+
-+ def SetMode(self, mode):
-+ self._deactivateMode()
-+ if mode == 'zoom':
-+ self.ciddscroll = self.canvas.mpl_connect('scroll_event', self.ZoomWheel)
-+ self.mode = 'zoom'
-+ elif mode == 'zoom_extend':
-+ self.mode = 'zoom_extend'
-+ elif mode == 'pan':
-+ self.mode = 'pan'
-+ elif mode:
-+ self.polygon_drawer.SetMode(mode)
++ IClass_perimeter perimeter;
+
-+ def SetSelectionPolygonMode(self, activate):
-+ self.polygon_drawer.SetSelectionPolygonMode(activate)
++ struct line_pnts *pol;
++ pol = Vect_new_line_struct();
+
-+ def _deactivateMode(self):
-+ self.mode = None
-+ self.polygon_drawer.SetMode(None)
++ for(i = 0; i < pol_n_pts; i++) {
++ Vect_append_point(pol,
++ polygon[i*2],
++ polygon[i*2 + 1],
++ 0.0);
++ }
+
-+ if self.ciddscroll:
-+ self.canvas.mpl_disconnect(self.ciddscroll)
++ Rast_set_window(rast_region);
+
-+ self.zoom_rect.set_visible(False)
-+ self._stopCategoryEdit()
++ make_perimeter(pol, &perimeter, rast_region);
++ for (i = 1; i < perimeter.npoints; i += 2) {
++ y = perimeter.points[i].y;
++ if (y != perimeter.points[i - 1].y) {
++ G_warning(_("prepare_signature: scan line %d has odd number of points."),
++ (i + 1) / 2);
++ return 1;
++ }
+
-+ def GetCoords(self):
++ x0 = perimeter.points[i - 1].x;
++ x1 = perimeter.points[i].x;
+
-+ coords = self.polygon_drawer.GetCoords()
-+ if coords is None:
-+ return
++ if (x0 > x1) {
++ G_warning(_("signature: perimeter points out of order."));
++ return 1;
++ }
+
-+ if self.transpose:
-+ for c in coords:
-+ tmp = c[0]
-+ c[0] = c[1]
-+ c[1] = tmp
++ row = (rast_region->rows - y);
++ if(row < 0 || row >= rast_region->rows) {
++ continue;
++ }
+
-+ return coords
++ row_idx = rast_region->cols * row;
+
-+ def SetEmpty(self):
-+ return self.polygon_drawer.SetEmpty()
++ for(i_col = x0; i_col <= x1; i_col++) {
++ if(i_col < 0 || i_col >= rast_region->cols) {
++ continue;
++ }
++ rast[row_idx + i_col] = val;
++ }
++ }
+
-+ def OnRelease(self, event):
-+ if not self.mode == "zoom": return
-+ self.zoom_rect.set_visible(False)
-+ self.ZoomRectangle(event)
-+ self.canvas.draw()
-+
-+ def OnPress(self, event):
-+ 'on button press we will see if the mouse is over us and store some data'
-+ if not event.inaxes:
-+ return
-+ if self.mode == "zoom_extend":
-+ self.ZoomToExtend()
++ Vect_destroy_line_struct(pol);
++ G_free(perimeter.points);
++ return 0;
++}
+Index: include/defs/vedit.h
+===================================================================
+--- include/defs/vedit.h (revision 57728)
++++ include/defs/vedit.h (working copy)
+@@ -33,6 +33,8 @@
+ int Vedit_merge_lines(struct Map_info *, struct ilist *);
+
+ /* move.c */
++int Vedit_move_areas(struct Map_info *, struct Map_info **, int,
++ struct ilist *, double, double, double, int, double);
+ int Vedit_move_lines(struct Map_info *, struct Map_info **, int,
+ struct ilist *, double, double, double, int, double);
+
+Index: include/defs/imagery.h
+===================================================================
+--- include/defs/imagery.h (revision 57728)
++++ include/defs/imagery.h (working copy)
+@@ -111,6 +111,28 @@
+ FILE *I_fopen_subgroup_ref_new(const char *, const char *);
+ FILE *I_fopen_subgroup_ref_old(const char *, const char *);
+
++/* scatt_sccats.c */
++void I_sc_init_cats(struct scCats *, int, int);
++void I_sc_free_cats(struct scCats *);
++int I_sc_add_cat(struct scCats *);
++int I_sc_insert_scatt_data(struct scCats *, struct scdScattData *, int, int);
+
-+ if event.xdata and event.ydata:
-+ self.zoom_wheel_coords = { 'x' : event.xdata, 'y' : event.ydata}
-+ self.zoom_rect_coords = { 'x' : event.xdata, 'y' : event.ydata}
-+ else:
-+ self.zoom_wheel_coords = None
-+ self.zoom_rect_coords = None
++void I_scd_init_scatt_data(struct scdScattData *, int, int, void *);
+
-+ def _stopCategoryEdit(self):
-+ 'disconnect all the stored connection ids'
++/* scatt.c */
++int I_compute_scatts(struct Cell_head *, struct scCats *, const char **,
++ const char **, int, struct scCats *, const char **);
+
-+ if self.cidpress:
-+ self.canvas.mpl_disconnect(self.cidpress)
-+ if self.cidrelease:
-+ self.canvas.mpl_disconnect(self.cidrelease)
-+ #self.canvas.mpl_disconnect(self.cidmotion)
++int I_create_cat_rast(struct Cell_head *, const char *);
++int I_insert_patch_to_cat_rast(const char *, struct Cell_head *, const char *);
+
-+ def _doLayout(self):
-+
-+ self.main_sizer = wx.BoxSizer(wx.VERTICAL)
-+ self.main_sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
-+ self.SetSizer(self.main_sizer)
-+ self.main_sizer.Fit(self)
-+
-+ def Plot(self, cats_order, scatts, ellipses, styles):
-+ """ Redraws the figure
-+ """
++int I_id_scatt_to_bands(const int, const int, int *, int *);
++int I_bands_to_id_scatt(const int, const int, const int, int *);
+
-+ callafter_list = []
++int I_merge_arrays(unsigned char *, unsigned char *, unsigned, unsigned, double);
++int I_apply_colormap(unsigned char *, unsigned char *, unsigned, unsigned char *, unsigned char *);
++int I_rasterize(double *, int, unsigned char *, unsigned char, struct Cell_head *);
+
-+ if self.full_extend:
-+ cx = self.axes.get_xlim()
-+ cy = self.axes.get_ylim()
-+ c = cx + cy
-+ else:
-+ c = None
+ /* sig.c */
+ int I_init_signatures(struct Signature *, int);
+ int I_new_signature(struct Signature *);
+Index: include/imagery.h
+===================================================================
+--- include/imagery.h (revision 57728)
++++ include/imagery.h (working copy)
+@@ -135,6 +135,56 @@
+
+ } IClass_statistics;
+
++/* Scatter Plot backend */
+
-+ #q = Queue()
-+ #p = Process(target=MergeImg, args=(cats_order, scatts, styles, self.transpose, q))
-+ #p.start()
-+ #merged_img, self.full_extend = q.get()
-+ #p.join()
-+
-+ merged_img, self.full_extend = MergeImg(cats_order, scatts, styles, None)
-+ self.axes.clear()
-+ self.axes.axis('equal')
++#define SC_SCATT_DATA 0
++#define SC_SCATT_CONDITIONS 1
+
-+ if self.transpose:
-+ merged_img = np.transpose(merged_img, (1, 0, 2))
+
-+ img = imshow(self.axes, merged_img,
-+ extent= [int(ceil(x)) for x in self.full_extend],
-+ origin='lower',
-+ interpolation='nearest',
-+ aspect="equal")
++/*! Holds list of all catagories.
++ It can contain selected areas for scatter plots (SC_SCATT_CONDITIONS type) or computed scatter plots (SC_SCATT_DATA type).
++*/
++struct scCats
++{
++ int type; /*!< SC_SCATT_DATA -> computed scatter plots, SC_SCATT_CONDITIONS -> set conditions for scatter plots to be computed*/
+
-+ callafter_list.append([self.axes.draw_artist, [img]])
-+ callafter_list.append([grass.try_remove, [merged_img.filename]])
++ int n_cats; /*!< number of alocated categories */
++
++ int n_bands; /*!< number of analyzed bands */
++ int n_scatts; /*!< number of possible scattter plot which can be created from bands */
+
-+ for cat_id in cats_order:
-+ if cat_id == 0:
-+ continue
-+ if not ellipses.has_key(cat_id):
-+ continue
-+
-+ e = ellipses[cat_id]
-+ if not e:
-+ continue
++ int n_a_cats; /*!< number of used/active categories */
++ int * cats_ids; /*!< (cat_idx->cat_id) array index is internal idx (position in cats_arr) and id is saved in it's position*/
++ int * cats_idxs; /*!< (cat_id->cat_idx) array index is id and internal idx is saved in it's position*/
+
-+ colors = styles[cat_id]['color'].split(":")
-+ if self.transpose:
-+ e['theta'] = 360 - e['theta'] + 90
-+ if e['theta'] >= 360:
-+ e['theta'] = abs(360 - e['theta'])
-+
-+ e['pos'] = [e['pos'][1], e['pos'][0]]
++ struct scScatts ** cats_arr; /*!< array of pointers to struct scScatts */
++};
+
-+ ellip = Ellipse(xy=e['pos'],
-+ width=e['width'],
-+ height=e['height'],
-+ angle=e['theta'],
-+ edgecolor="w",
-+ linewidth=1.5,
-+ facecolor='None')
-+ self.axes.add_artist(ellip)
-+ callafter_list.append([self.axes.draw_artist, [ellip]])
+
-+ color = map(lambda v : int(v)/255.0, styles[cat_id]['color'].split(":"))
-+
-+ ellip = Ellipse(xy=e['pos'],
-+ width=e['width'],
-+ height=e['height'],
-+ angle=e['theta'],
-+ edgecolor=color,
-+ linewidth=1,
-+ facecolor='None')
-+
-+ self.axes.add_artist(ellip)
-+ callafter_list.append([self.axes.draw_artist, [ellip]])
-+
-+ center = Line2D([e['pos'][0]], [e['pos'][1]],
-+ marker='x',
-+ markeredgecolor='w',
-+ #markerfacecolor=color,
-+ markersize=2)
-+ self.axes.add_artist(center)
-+ callafter_list.append([self.axes.draw_artist, [center]])
-+
-+ callafter_list.append([self.fig.canvas.blit, []])
-+
-+ if c:
-+ self.axes.axis(c)
-+ wx.CallAfter(lambda : self.CallAfter(callafter_list))
++/*! Holds list of all scatter plots, which belongs to category.
++*/
++struct scScatts
++{
++ int n_a_scatts; /*!< number of used/active scatter plots*/
+
-+ def CallAfter(self, funcs_list):
-+ while funcs_list:
-+ fcn, args = funcs_list.pop(0)
-+ fcn(*args)
++ int * scatts_bands; /*!< array of bands for which represents the scatter plots,
++ every scatter plot has assigned two bads (size of array is n_a_scatts * 2)*/
++ int * scatt_idxs; /*!< (scatt_id->scatt_idx) internal idx of the scatter plot (position in scatts_arr)*/
+
-+ self.canvas.draw()
++ struct scdScattData ** scatts_arr; /*!< array of pointers to scdScattData */
++};
+
-+ def CleanUp(self):
-+ self.plotClosed.emit(scatt_id = self.scatt_id)
-+ self.Destroy()
++/*! Holds scatter plot data.
++*/
++struct scdScattData
++{
++ int n_vals; /*!< Number of values in scatter plot. */
+
-+ def ZoomWheel(self, event):
-+ # get the current x and y limits
-+ if not event.inaxes:
-+ return
-+ # tcaswell
-+ # http://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel
-+ cur_xlim = self.axes.get_xlim()
-+ cur_ylim = self.axes.get_ylim()
-+
-+ xdata = event.xdata
-+ ydata = event.ydata
-+ if event.button == 'up':
-+ scale_factor = 1/self.base_scale
-+ elif event.button == 'down':
-+ scale_factor = self.base_scale
-+ else:
-+ scale_factor = 1
++ unsigned char * b_conds_arr; /*!< array of selected areas (used for SC_SCATT_CONDITIONS type) otherwise NULL */
++ unsigned int * scatt_vals_arr; /*!< array of computed areas (used for SC_SCATT_DATA type) otherwise NULL */
++};
+
-+ extend = (xdata - (xdata - cur_xlim[0]) * scale_factor,
-+ xdata + (cur_xlim[1] - xdata) * scale_factor,
-+ ydata - (ydata - cur_ylim[0]) * scale_factor,
-+ ydata + (cur_ylim[1] - ydata) * scale_factor)
+
-+ self.axes.axis(extend)
-+
-+ self.canvas.draw()
+ #define SIGNATURE_TYPE_MIXED 1
+
+ #define GROUPFILE "CURGROUP"
+Index: gui/icons/grass/polygon.png
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+Index: gui/icons/grass/polygon.png
+===================================================================
+--- gui/icons/grass/polygon.png (revision 57728)
++++ gui/icons/grass/polygon.png (working copy)
+
+Property changes on: gui/icons/grass/polygon.png
+___________________________________________________________________
+Added: svn:mime-type
+## -0,0 +1 ##
++application/octet-stream
+\ No newline at end of property
+Index: gui/wxpython/vdigit/wxdigit.py
+===================================================================
+--- gui/wxpython/vdigit/wxdigit.py (revision 57728)
++++ gui/wxpython/vdigit/wxdigit.py (working copy)
+@@ -17,7 +17,7 @@
+ (and NumPy would be an excellent candidate for acceleration via
+ e.g. OpenCL or CUDA; I'm surprised it hasn't happened already).
+
+-(C) 2007-2011 by the GRASS Development Team
++(C) 2007-2011, 2013 by the GRASS Development Team
+
+ This program is free software under the GNU General Public License
+ (>=v2). Read the file COPYING that comes with GRASS for details.
+@@ -27,6 +27,8 @@
+
+ import grass.script.core as grass
+
++from grass.pydispatch.signal import Signal
+
-+ def ZoomRectangle(self, event):
-+ # get the current x and y limits
-+ if not self.mode == "zoom": return
-+ if event.inaxes is None: return
-+ if event.button != 1: return
+ from core.gcmd import GError
+ from core.debug import Debug
+ from core.settings import UserSettings
+@@ -176,7 +178,21 @@
+
+ if self.poMapInfo:
+ self.InitCats()
+-
+
-+ cur_xlim = self.axes.get_xlim()
-+ cur_ylim = self.axes.get_ylim()
-+
-+ x1, y1 = event.xdata, event.ydata
-+ x2 = deepcopy(self.zoom_rect_coords['x'])
-+ y2 = deepcopy(self.zoom_rect_coords['y'])
++ self.emit_signals = False
+
-+ if x1 == x2 or y1 == y2:
-+ return
++ # signals which describes features changes during digitization,
++ # activate them using EmitSignals method
++ #TODO signal for errors?
++ self.featureAdded = Signal('IVDigit.featureAdded')
++ self.areasDeleted = Signal('IVDigit.areasDeleted')
++ self.vertexMoved = Signal('IVDigit.vertexMoved')
++ self.vertexAdded = Signal('IVDigit.vertexAdded')
++ self.vertexRemoved = Signal('IVDigit.vertexRemoved')
++ self.featuresDeleted = Signal('IVDigit.featuresDeleted')
++ self.featuresMoved = Signal('IVDigit.featuresMoved')
++ self.lineEdited = Signal('IVDigit.lineEdited')
+
-+ self.axes.axis((x1, x2, y1, y2))
-+ #self.axes.set_xlim(x1, x2)#, auto = True)
-+ #self.axes.set_ylim(y1, y2)#, auto = True)
-+ self.canvas.draw()
+ def __del__(self):
+ Debug.msg(1, "IVDigit.__del__()")
+ Vect_destroy_line_struct(self.poPoints)
+@@ -188,7 +204,12 @@
+ Vect_close(self.poBgMapInfo)
+ self.poBgMapInfo = self.popoBgMapInfo = None
+ del self.bgMapInfo
+-
++
++ def EmitSignals(self, emit):
++ """!Activate/deactivate signals which describes features changes during digitization.
++ """
++ self.emit_signals = emit
+
-+ def Motion(self, event):
-+ self.PanMotion(event)
-+ self.ZoomRectMotion(event)
-+
-+ if event.inaxes is None:
-+ return
-+
-+ self.cursorMove.emit(x=event.xdata, y=event.ydata)
+ def CloseBackgroundMap(self):
+ """!Close background vector map"""
+ if not self.poBgMapInfo:
+@@ -394,7 +415,6 @@
+
+ @return tuple (number of added features, feature ids)
+ """
+-
+ layer = self._getNewFeaturesLayer()
+ cat = self._getNewFeaturesCat()
+
+@@ -419,10 +439,14 @@
+ return (-1, None)
+
+ self.toolbar.EnableUndo()
+-
+- return self._addFeature(vtype, points, layer, cat,
+- self._getSnapMode(), self._display.GetThreshold())
+-
+
-+ def PanMotion(self, event):
-+ 'on mouse movement'
-+ if not self.mode == "pan":
-+ return
-+ if event.inaxes is None:
-+ return
-+ if event.button != 1:
-+ return
++ ret = self._addFeature(vtype, points, layer, cat,
++ self._getSnapMode(), self._display.GetThreshold())
++ if ret[0] > -1 and self.emit_signals:
++ self.featureAdded.emit(new_bboxs = [self._createBbox(points)], new_areas_cats = [[{layer : [cat]}, None]])
+
-+ cur_xlim = self.axes.get_xlim()
-+ cur_ylim = self.axes.get_ylim()
++ return ret
+
-+ x,y = event.xdata, event.ydata
-+
-+ mx = (x - self.zoom_wheel_coords['x']) * 0.6
-+ my = (y - self.zoom_wheel_coords['y']) * 0.6
+ def DeleteSelectedLines(self):
+ """!Delete selected features
+
+@@ -434,16 +458,27 @@
+ # collect categories for deleting if requested
+ deleteRec = UserSettings.Get(group = 'vdigit', key = 'delRecord', subkey = 'enabled')
+ catDict = dict()
+
-+ extend = (cur_xlim[0] - mx, cur_xlim[1] - mx, cur_ylim[0] - my, cur_ylim[1] - my)
++ old_bboxs = []
++ old_areas_cats = []
+ if deleteRec:
+ for i in self._display.selected['ids']:
++
+ if Vect_read_line(self.poMapInfo, None, self.poCats, i) < 0:
+ self._error.ReadLine(i)
+
+- cats = self.poCats.contents
+- for j in range(cats.n_cats):
+- if cats.field[j] not in catDict.keys():
+- catDict[cats.field[j]] = list()
+- catDict[cats.field[j]].append(cats.cat[j])
++ if self.emit_signals:
++ ret = self._getLineAreaBboxCats(i)
++ if ret:
++ old_bboxs += ret[0]
++ old_areas_cats += ret[1]
++
++ # catDict was not used -> put into comment
++ #cats = self.poCats.contents
++ #for j in range(cats.n_cats):
++ # if cats.field[j] not in catDict.keys():
++ # catDict[cats.field[j]] = list()
++ # catDict[cats.field[j]].append(cats.cat[j])
+
+ poList = self._display.GetSelectedIList()
+ nlines = Vedit_delete_lines(self.poMapInfo, poList)
+@@ -456,7 +491,10 @@
+ self._deleteRecords(catDict)
+ self._addChangeset()
+ self.toolbar.EnableUndo()
+-
+
-+ self.zoom_wheel_coords['x'] = x
-+ self.zoom_wheel_coords['y'] = y
++ if self.emit_signals:
++ self.featuresDeleted.emit(old_bboxs = old_bboxs, old_areas_cats = old_areas_cats)
+
-+ self.axes.axis(extend)
-+
-+ #self.canvas.copy_from_bbox(self.axes.bbox)
-+ #self.canvas.restore_region(self.background)
-+ self.canvas.draw()
+ return nlines
+
+ def _deleteRecords(self, cats):
+@@ -512,22 +550,173 @@
+
+ @return number of deleted
+ """
++ if len(self._display.selected['ids']) < 1:
++ return 0
+
-+ def ZoomRectMotion(self, event):
-+ if not self.mode == "zoom": return
-+ if event.inaxes is None: return
-+ if event.button != 1: return
+ poList = self._display.GetSelectedIList()
+ cList = poList.contents
+
+ nareas = 0
++ old_bboxs = []
++ old_areas_cats = []
+
-+ x1, y1 = event.xdata, event.ydata
-+ self.zoom_rect.set_visible(True)
-+ x2 = self.zoom_rect_coords['x']
-+ y2 = self.zoom_rect_coords['y']
+ for i in range(cList.n_values):
+
-+ self.zoom_rect.xy = ((x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1))
+ if Vect_get_line_type(self.poMapInfo, cList.value[i]) != GV_CENTROID:
+ continue
+-
+
-+ #self.axes.draw_artist(self.zoom_rect)
-+ self.canvas.draw()
++ if self.emit_signals:
++ area = Vect_get_centroid_area(self.poMapInfo, cList.value[i]);
++ if area > 0:
++ bbox, cats = self._getaAreaBboxCats(area)
++ old_bboxs += bbox
++ old_areas_cats += cats
+
-+class ScatterPlotContextMenu:
-+ def __init__(self, plot):
+ nareas += Vedit_delete_area_centroid(self.poMapInfo, cList.value[i])
+
+ if nareas > 0:
+ self._addChangeset()
+ self.toolbar.EnableUndo()
++ if self.emit_signals:
++ self.areasDeleted.emit(old_bboxs = old_bboxs, old_areas_cats = old_areas_cats)
+
-+ self.plot = plot
-+ self.canvas = plot.canvas
-+ self.cidpress = self.canvas.mpl_connect(
-+ 'button_press_event', self.ContexMenu)
++ return nareas
+
-+ def ContexMenu(self, event):
-+ if not event.inaxes:
-+ return
++ def _getLineAreaBboxCats(self, ln_id):
++ ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
+
-+ if event.button == 3:
-+ menu = wx.Menu()
-+ menu_items = [["zoom_to_extend", _("Zoom to scatter plot extend"), lambda event : self.plot.ZoomToExtend()]]
++ if ltype == GV_CENTROID:
++ #TODO centroid opttimization, can be adited also its area -> it will appear two times in new_ lists
++ return self._getCentroidAreaBboxCats(ln_id)
++ else:
++ return [self._getBbox(ln_id)], [self._getLineAreasCategories(ln_id)]
+
-+ for item in menu_items:
-+ item_id = wx.ID_ANY
-+ menu.Append(item_id, text = item[1])
-+ menu.Bind(wx.EVT_MENU, item[2], id = item_id)
+
-+ wx.CallAfter(self.ShowMenu, menu)
-+
-+ def ShowMenu(self, menu):
-+ self.plot.PopupMenu(menu)
-+ menu.Destroy()
-+ self.plot.ReleaseMouse()
++ def _getCentroidAreaBboxCats(self, centroid):
++ if not Vect_line_alive(self.poMapInfo, centroid):
++ return None
+
-+class PolygonDrawer:
-+ """
-+ An polygon editor.
-+ """
-+ def __init__(self, ax, pol, empty_pol):
-+ if pol.figure is None:
-+ raise RuntimeError('You must first add the polygon to a figure or canvas before defining the interactor')
-+ self.ax = ax
-+ self.canvas = pol.figure.canvas
-+
-+ self.showverts = True
-+
-+ self.pol = pol
-+ self.empty_pol = empty_pol
-+
-+ x, y = zip(*self.pol.xy)
-+
-+ style = self._getPolygonStyle()
-+
-+ self.line = Line2D(x, y, marker='o', markerfacecolor='r', animated=True)
-+ self.ax.add_line(self.line)
-+ #self._update_line(pol)
-+
-+ cid = self.pol.add_callback(self.poly_changed)
-+ self.moving_ver_idx = None # the active vert
-+
-+ self.mode = None
-+
-+ if self.empty_pol:
-+ self._show(False)
-+
-+ #self.canvas.mpl_connect('draw_event', self.draw_callback)
-+ self.canvas.mpl_connect('button_press_event', self.OnButtonPressed)
-+ self.canvas.mpl_connect('button_release_event', self.button_release_callback)
-+ self.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)
-+
-+ self.it = 0
-+
-+ def _getPolygonStyle(self):
-+ style = {}
-+ style['sel_pol'] = UserSettings.Get(group='scatt',
-+ key='selection',
-+ subkey='sel_pol')
-+ style['sel_pol_vertex'] = UserSettings.Get(group='scatt',
-+ key='selection',
-+ subkey='sel_pol_vertex')
-+
-+ style['sel_pol'] = [i / 255.0 for i in style['sel_pol']]
-+ style['sel_pol_vertex'] = [i / 255.0 for i in style['sel_pol_vertex']]
-+
-+ return style
-+
-+ def _getSnapTresh(self):
-+ return UserSettings.Get(group='scatt',
-+ key='selection',
-+ subkey='snap_tresh')
-+
-+ def SetMode(self, mode):
-+ self.mode = mode
-+
-+ def SetSelectionPolygonMode(self, activate):
-+
-+ self.Show(activate)
-+ if not activate and self.mode:
-+ self.SetMode(None)
-+
-+ def Show(self, show):
-+ if show:
-+ if not self.empty_pol:
-+ self._show(True)
++ area = Vect_get_centroid_area(self.poMapInfo, centroid)
++ if area > 0:
++ return self._getaAreaBboxCats(area)
+ else:
-+ self._show(False)
-+
-+ def GetCoords(self):
-+ if self.empty_pol:
+ return None
+
-+ coords = deepcopy(self.pol.xy)
-+ return coords
++ def _getaAreaBboxCats(self, area):
+
-+ def SetEmpty(self):
-+ self._setEmptyPol(True)
++ po_b_list = Vect_new_list()
++ Vect_get_area_boundaries(self.poMapInfo, area, po_b_list);
++ b_list = po_b_list.contents
+
-+ def _setEmptyPol(self, empty_pol):
-+ self.empty_pol = empty_pol
-+ self._show(not empty_pol)
++ geoms = []
++ areas_cats = []
+
-+ def _show(self, show):
++ if b_list.n_values > 0:
++ for i_line in range(b_list.n_values):
+
-+ self.show = show
++ line = b_list.value[i_line];
+
-+ self.line.set_visible(self.show)
-+ self.pol.set_visible(self.show)
++ geoms.append(self._getBbox(abs(line)))
++ areas_cats.append(self._getLineAreasCategories(abs(line)))
+
+- return nareas
++ Vect_destroy_list(po_b_list);
+
-+ self.Redraw()
++ return geoms, areas_cats
+
-+ def Redraw(self):
-+ if self.show:
-+ self.ax.draw_artist(self.pol)
-+ self.ax.draw_artist(self.line)
-+ self.canvas.blit(self.ax.bbox)
-+ self.canvas.draw()
++ def _getLineAreasCategories(self, ln_id):
++ if not Vect_line_alive (self.poMapInfo, ln_id):
++ return []
+
-+ def draw_callback(self, event):
++ ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
++ if ltype != GV_BOUNDARY:
++ return []
+
-+ style=self._getPolygonStyle()
-+ self.pol.set_facecolor(style['sel_pol'])
-+ self.line.set_markerfacecolor(style['sel_pol_vertex'])
++ cats = [None, None]
+
-+ self.background = self.canvas.copy_from_bbox(self.ax.bbox)
-+ self.ax.draw_artist(self.pol)
-+ self.ax.draw_artist(self.line)
-+
-+ def poly_changed(self, pol):
-+ 'this method is called whenever the polygon object is called'
-+ # only copy the artist props to the line (except visibility)
-+ vis = self.line.get_visible()
-+ Artist.update_from(self.line, pol)
-+ self.line.set_visible(vis) # don't use the pol visibility state
++ left = c_int()
++ right = c_int()
+
-+ def get_ind_under_point(self, event):
-+ 'get the index of the vertex under point if within treshold'
++ if Vect_get_line_areas(self.poMapInfo, ln_id, pointer(left), pointer(right)) == 1:
++ areas = [left.value, right.value]
+
-+ # display coords
-+ xy = np.asarray(self.pol.xy)
-+ xyt = self.pol.get_transform().transform(xy)
-+ xt, yt = xyt[:, 0], xyt[:, 1]
-+ d = np.sqrt((xt-event.x)**2 + (yt-event.y)**2)
-+ indseq = np.nonzero(np.equal(d, np.amin(d)))[0]
-+ ind = indseq[0]
++ for i, a in enumerate(areas):
++ if a > 0:
++ centroid = Vect_get_area_centroid(self.poMapInfo, a)
++ if centroid <= 0:
++ continue
++ c = self._getCategories(centroid)
++ if c:
++ cats[i] = c
+
-+ if d[ind]>=self._getSnapTresh():
-+ ind = None
++ return cats
+
-+ return ind
++ def _getCategories(self, ln_id):
++ if not Vect_line_alive (self.poMapInfo, ln_id):
++ return none
+
-+ def OnButtonPressed(self, event):
-+ if not event.inaxes:
-+ return
++ poCats = Vect_new_cats_struct()
++ if Vect_read_line(self.poMapInfo, None, poCats, ln_id) < 0:
++ Vect_destroy_cats_struct(poCats)
++ return None
+
-+ if event.button in [2, 3]:
-+ return
++ cCats = poCats.contents
+
-+ if self.mode == "delete_vertex":
-+ self._deleteVertex(event)
-+ elif self.mode == "add_boundary_vertex":
-+ self._addVertexOnBoundary(event)
-+ elif self.mode == "add_vertex":
-+ self._addVertex(event)
-+ elif self.mode == "remove_polygon":
-+ self.SetEmpty()
-+ self.moving_ver_idx = self.get_ind_under_point(event)
++ cats = {}
++ for j in range(cCats.n_cats):
++ if cats.has_key(cCats.field[j]):
++ cats[cCats.field[j]].append(cCats.cat[j])
++ else:
++ cats[cCats.field[j]] = [cCats.cat[j]]
+
++ Vect_destroy_cats_struct(poCats)
++ return cats
+
-+ def button_release_callback(self, event):
-+ 'whenever a mouse button is released'
-+ if not self.showverts: return
-+ if event.button != 1: return
-+ self.moving_ver_idx = None
++ def _getBbox(self, ln_id):
++ if not Vect_line_alive (self.poMapInfo, ln_id):
++ return None
+
-+ def ShowVertices(self, show):
-+ self.showverts = show
-+ self.line.set_visible(self.showverts)
-+ if not self.showverts: self.moving_ver_idx = None
++ poPoints = Vect_new_line_struct()
++ if Vect_read_line(self.poMapInfo, poPoints, None, ln_id) < 0:
++ Vect_destroy_line_struct(poPoints)
++ return []
+
-+ def _deleteVertex(self, event):
-+ ind = self.get_ind_under_point(event)
++ geom = self._convertGeom(poPoints)
++ bbox = self._createBbox(geom)
++ Vect_destroy_line_struct(poPoints)
++ return bbox
+
-+ if ind is None or self.empty_pol:
-+ return
++ def _createBbox(self, points):
+
-+ if len(self.pol.xy) <= 2:
-+ self.empty_pol = True
-+ self._show(False)
-+ return
-+
-+ coords = []
-+ for i,tup in enumerate(self.pol.xy):
-+ if i == ind:
++ bbox = {}
++ for pt in points:
++ if not bbox.has_key('maxy'):
++ bbox['maxy'] = pt[1]
++ bbox['miny'] = pt[1]
++ bbox['maxx'] = pt[0]
++ bbox['minx'] = pt[0]
+ continue
-+ elif i == 0 and ind == len(self.pol.xy) - 1:
-+ continue
-+ elif i == len(self.pol.xy) - 1 and ind == 0:
-+ continue
++
++ if bbox['maxy'] < pt[1]:
++ bbox['maxy'] = pt[1]
++ elif bbox['miny'] > pt[1]:
++ bbox['miny'] = pt[1]
++
++ if bbox['maxx'] < pt[0]:
++ bbox['maxx'] = pt[0]
++ elif bbox['minx'] > pt[0]:
++ bbox['minx'] = pt[0]
++ return bbox
+
-+ coords.append(tup)
++ def _convertGeom(self, poPoints):
+
-+ self.pol.xy = coords
-+ self.line.set_data(zip(*self.pol.xy))
++ Points = poPoints.contents
+
-+ self.Redraw()
++ pts_geom = []
++ for j in range(Points.n_points):
++ pts_geom.append((Points.x[j], Points.y[j]))
+
-+ def _addVertexOnBoundary(self, event):
-+ if self.empty_pol:
-+ return
++ return pts_geom
+
-+ xys = self.pol.get_transform().transform(self.pol.xy)
-+ p = event.x, event.y # display coords
-+ for i in range(len(xys)-1):
-+ s0 = xys[i]
-+ s1 = xys[i+1]
-+ d = dist_point_to_segment(p, s0, s1)
+ def MoveSelectedLines(self, move):
+ """!Move selected features
+
+@@ -536,16 +725,45 @@
+ if not self._checkMap():
+ return -1
+
++ nsel = len(self._display.selected['ids'])
++ if nsel < 1:
++ return -1
++
+ thresh = self._display.GetThreshold()
+ snap = self._getSnapMode()
+
+ poList = self._display.GetSelectedIList()
+
-+ if d<=self._getSnapTresh():
-+ self.pol.xy = np.array(
-+ list(self.pol.xy[:i + 1]) +
-+ [(event.xdata, event.ydata)] +
-+ list(self.pol.xy[i + 1:]))
-+ self.line.set_data(zip(*self.pol.xy))
-+ break
++ if self.emit_signals:
++ old_bboxs = []
++ old_areas_cats = []
++ for sel_id in self._display.selected['ids']:
++ ret = self._getLineAreaBboxCats(sel_id)
++ if ret:
++ old_bboxs += ret[0]
++ old_areas_cats += ret[1]
++
++ Vect_set_updated(self.poMapInfo, 1)
++ n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
++
+ nlines = Vedit_move_lines(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
+ poList,
+ move[0], move[1], 0,
+ snap, thresh)
+
-+ self.Redraw()
+ Vect_destroy_list(poList)
+-
+
-+ def _addVertex(self, event):
++ if nlines > 0 and self.emit_signals:
++ new_bboxs = []
++ new_areas_cats = []
++ n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
++ for i in range(n_up_lines_old, n_up_lines):
++ new_id = Vect_get_updated_line(self.poMapInfo, i)
++ ret = self._getLineAreaBboxCats(new_id)
++ if ret:
++ new_bboxs += ret[0]
++ new_areas_cats += ret[1]
+
-+ if self.empty_pol:
-+ pt = (event.xdata, event.ydata)
-+ self.pol.xy = np.array([pt, pt])
-+ self._show(True)
-+ self.empty_pol = False
-+ else:
-+ self.pol.xy = np.array(
-+ [(event.xdata, event.ydata)] +
-+ list(self.pol.xy[1:]) +
-+ [(event.xdata, event.ydata)])
+ if nlines > 0 and self._settings['breakLines']:
+ for i in range(1, nlines):
+ self._breakLineAtIntersection(nlines + i, None, changeset)
+@@ -553,7 +771,13 @@
+ if nlines > 0:
+ self._addChangeset()
+ self.toolbar.EnableUndo()
+-
++
++ if self.emit_signals:
++ self.featuresMoved.emit(new_bboxs = new_bboxs,
++ old_bboxs = old_bboxs,
++ old_areas_cats = old_areas_cats,
++ new_areas_cats = new_areas_cats)
+
-+ self.line.set_data(zip(*self.pol.xy))
-+
-+ self.Redraw()
+ return nlines
+
+ def MoveSelectedVertex(self, point, move):
+@@ -571,12 +795,21 @@
+
+ if len(self._display.selected['ids']) != 1:
+ return -1
+-
+
-+ def motion_notify_callback(self, event):
-+ 'on mouse movement'
-+ if not self.mode == "move_vertex": return
-+ if not self.showverts: return
-+ if self.empty_pol: return
-+ if self.moving_ver_idx is None: return
-+ if event.inaxes is None: return
-+ if event.button != 1: return
++ # move only first found vertex in bbox
++ poList = self._display.GetSelectedIList()
+
-+ self.it += 1
++ if self.emit_signals:
++ cList = poList.contents
++ old_bboxs = [self._getBbox(cList.value[0])]
++ old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
+
-+ x,y = event.xdata, event.ydata
++ Vect_set_updated(self.poMapInfo, 1)
++ n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
+
-+ self.pol.xy[self.moving_ver_idx] = x,y
-+ if self.moving_ver_idx == 0:
-+ self.pol.xy[len(self.pol.xy) - 1] = x,y
-+ elif self.moving_ver_idx == len(self.pol.xy) - 1:
-+ self.pol.xy[0] = x,y
+ Vect_reset_line(self.poPoints)
+ Vect_append_point(self.poPoints, point[0], point[1], 0.0)
+-
+- # move only first found vertex in bbox
+- poList = self._display.GetSelectedIList()
+
-+ self.line.set_data(zip(*self.pol.xy))
+ moved = Vedit_move_vertex(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
+ poList, self.poPoints,
+ self._display.GetThreshold(type = 'selectThresh'),
+@@ -584,7 +817,17 @@
+ move[0], move[1], 0.0,
+ 1, self._getSnapMode())
+ Vect_destroy_list(poList)
+-
+
-+ self.canvas.restore_region(self.background)
++ if moved > 0 and self.emit_signals:
++ n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
+
-+ self.Redraw()
++ new_bboxs = []
++ new_areas_cats = []
++ for i in range(n_up_lines_old, n_up_lines):
++ new_id = Vect_get_updated_line(self.poMapInfo, i)
++ new_bboxs.append(self._getBbox(new_id))
++ new_areas_cats.append(self._getLineAreasCategories(new_id))
+
-+class ModestImage(mi.AxesImage):
-+ """
-+ Computationally modest image class.
+ if moved > 0 and self._settings['breakLines']:
+ self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
+ None)
+@@ -592,7 +835,13 @@
+ if moved > 0:
+ self._addChangeset()
+ self.toolbar.EnableUndo()
+-
+
-+ ModestImage is an extension of the Matplotlib AxesImage class
-+ better suited for the interactive display of larger images. Before
-+ drawing, ModestImage resamples the data array based on the screen
-+ resolution and view window. This has very little affect on the
-+ appearance of the image, but can substantially cut down on
-+ computation since calculations of unresolved or clipped pixels
-+ are skipped.
++ if self.emit_signals:
++ self.vertexMoved.emit(new_bboxs = new_bboxs,
++ new_areas_cats = new_areas_cats,
++ old_areas_cats = old_areas_cats,
++ old_bboxs = old_bboxs)
+
-+ The interface of ModestImage is the same as AxesImage. However, it
-+ does not currently support setting the 'extent' property. There
-+ may also be weird coordinate warping operations for images that
-+ I'm not aware of. Don't expect those to work either.
+ return moved
+
+ def AddVertex(self, coords):
+@@ -681,6 +930,10 @@
+ self._error.ReadLine(line)
+ return -1
+
++ if self.emit_signals:
++ old_bboxs = [self._getBbox(line)]
++ old_areas_cats = [self._getLineAreasCategories(line)]
+
-+ Author: Chris Beaumont <beaumont at hawaii.edu>
-+ """
-+ def __init__(self, minx=0.0, miny=0.0, *args, **kwargs):
-+ if 'extent' in kwargs and kwargs['extent'] is not None:
-+ raise NotImplementedError("ModestImage does not support extents")
+ # build feature geometry
+ Vect_reset_line(self.poPoints)
+ for p in coords:
+@@ -696,6 +949,9 @@
+
+ newline = Vect_rewrite_line(self.poMapInfo, line, ltype,
+ self.poPoints, self.poCats)
++ if newline > 0 and self.emit_signals:
++ new_geom = [self._getBbox(newline)]
++ new_areas_cats = [self._getLineAreasCategories(newline)]
+
+ if newline > 0 and self._settings['breakLines']:
+ self._breakLineAtIntersection(newline, None)
+@@ -703,7 +959,13 @@
+ if newline > 0:
+ self._addChangeset()
+ self.toolbar.EnableUndo()
+-
++
++ if self.emit_signals:
++ self.lineEdited.emit(old_bboxs = old_bboxs,
++ old_areas_cats = old_areas_cats,
++ new_bboxs = new_bboxs,
++ new_areas_cats = new_areas_cats)
+
-+ self._full_res = None
-+ self._sx, self._sy = None, None
-+ self._bounds = (None, None, None, None)
-+ self.minx = minx
-+ self.miny = miny
+ return newline
+
+ def FlipLine(self):
+@@ -1514,6 +1776,16 @@
+ return 0
+
+ poList = self._display.GetSelectedIList()
+
-+ super(ModestImage, self).__init__(*args, **kwargs)
++ if self.emit_signals:
++ cList = poList.contents
++
++ old_bboxs = [self._getBbox(cList.value[0])]
++ old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
+
-+ def set_data(self, A):
-+ """
-+ Set the image array
++ Vect_set_updated(self.poMapInfo, 1)
++ n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
+
-+ ACCEPTS: numpy/PIL Image A
-+ """
-+ self._full_res = A
-+ self._A = A
+ Vect_reset_line(self.poPoints)
+ Vect_append_point(self.poPoints, coords[0], coords[1], 0.0)
+
+@@ -1525,15 +1797,35 @@
+ else:
+ ret = Vedit_remove_vertex(self.poMapInfo, poList,
+ self.poPoints, thresh)
+
-+ if self._A.dtype != np.uint8 and not np.can_cast(self._A.dtype,
-+ np.float):
-+ raise TypeError("Image data can not convert to float")
+ Vect_destroy_list(poList)
+
-+ if (self._A.ndim not in (2, 3) or
-+ (self._A.ndim == 3 and self._A.shape[-1] not in (3, 4))):
-+ raise TypeError("Invalid dimensions for image data")
++ if ret > 0 and self.emit_signals:
++ new_bboxs = []
++ new_areas_cats = []
+
-+ self._imcache =None
-+ self._rgbacache = None
-+ self._oldxslice = None
-+ self._oldyslice = None
-+ self._sx, self._sy = None, None
++ n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
++ for i in range(n_up_lines_old, n_up_lines):
++ new_id = Vect_get_updated_line(self.poMapInfo, i)
++ new_areas_cats.append(self._getLineAreasCategories(new_id))
++ new_bboxs.append(self._getBbox(new_id))
+
+ if not add and ret > 0 and self._settings['breakLines']:
+ self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
+ None)
+-
+
-+ def get_array(self):
-+ """Override to return the full-resolution array"""
-+ return self._full_res
+ if ret > 0:
+ self._addChangeset()
+-
+
-+ def _scale_to_res(self):
-+ """ Change self._A and _extent to render an image whose
-+ resolution is matched to the eventual rendering."""
++ if ret > 0 and self.emit_signals:
++ if add:
++ self.vertexAdded.emit(old_bboxs = old_bboxs, new_bboxs = new_bboxs)
++ else:
++ self.vertexRemoved.emit(old_bboxs = old_bboxs,
++ new_bboxs = new_bboxs,
++ old_areas_cats = old_areas_cats,
++ new_areas_cats = new_areas_cats)
+
-+ ax = self.axes
-+ ext = ax.transAxes.transform([1, 1]) - ax.transAxes.transform([0, 0])
-+ xlim, ylim = ax.get_xlim(), ax.get_ylim()
-+ dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
+ return 1
+
+ def GetLineCats(self, line):
+Index: gui/wxpython/vdigit/toolbars.py
+===================================================================
+--- gui/wxpython/vdigit/toolbars.py (revision 57728)
++++ gui/wxpython/vdigit/toolbars.py (working copy)
+@@ -17,6 +17,7 @@
+ import wx
+
+ from grass.script import core as grass
++from grass.pydispatch.signal import Signal
+
+ from gui_core.toolbars import BaseToolbar, BaseIcons
+ from gui_core.dialogs import CreateNewVector
+@@ -42,6 +43,8 @@
+ self.digit = None
+ self._giface = giface
+
++ self.editingStarted = Signal("VDigitToolbar.editingStarted")
+
-+ y0 = max(self.miny, ylim[0] - 5)
-+ y1 = min(self._full_res.shape[0] + self.miny, ylim[1] + 5)
-+ x0 = max(self.minx, xlim[0] - 5)
-+ x1 = min(self._full_res.shape[1] + self.minx, xlim[1] + 5)
-+ y0, y1, x0, x1 = map(int, [y0, y1, x0, x1])
+ # currently selected map layer for editing (reference to MapLayer instance)
+ self.mapLayer = None
+ # list of vector layers from Layer Manager (only in the current mapset)
+@@ -860,6 +863,7 @@
+ alpha = int(opacity * 255)
+ self.digit.GetDisplay().UpdateSettings(alpha = alpha)
+
++ self.editingStarted.emit(vectMap = mapLayer.GetName(), digit = self.digit)
+ return True
+
+ def StopEditing(self):
+Index: gui/wxpython/iclass/dialogs.py
+===================================================================
+--- gui/wxpython/iclass/dialogs.py (revision 57728)
++++ gui/wxpython/iclass/dialogs.py (working copy)
+@@ -469,13 +469,19 @@
+ toolbar.SetCategories(catNames = catNames, catIdx = cats)
+ if name in catNames:
+ toolbar.choice.SetStringSelection(name)
++ cat = toolbar.GetSelectedCategoryIdx()
+ elif catNames:
+ toolbar.choice.SetSelection(0)
+-
++ cat = toolbar.GetSelectedCategoryIdx()
++ else:
++ cat = None
+
-+ sy = int(max(1, min((y1 - y0) / 5., np.ceil(dy / ext[1]))))
-+ sx = int(max(1, min((x1 - x0) / 5., np.ceil(dx / ext[0]))))
+ if toolbar.choice.IsEmpty():
+ toolbar.EnableControls(False)
+ else:
+ toolbar.EnableControls(True)
+
-+ # have we already calculated what we need?
-+ if sx == self._sx and sy == self._sy and \
-+ x0 == self._bounds[0] and x1 == self._bounds[1] and \
-+ y0 == self._bounds[2] and y1 == self._bounds[3]:
-+ return
++ self.mapWindow.CategoryChanged(cat)
+ # don't forget to update maps, histo, ...
+
+ def GetSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
+Index: gui/wxpython/iclass/toolbars.py
+===================================================================
+--- gui/wxpython/iclass/toolbars.py (revision 57728)
++++ gui/wxpython/iclass/toolbars.py (working copy)
+@@ -46,9 +46,7 @@
+ 'importAreas' : MetaIcon(img = 'layer-import',
+ label = _('Import training areas from vector map')),
+ 'addRgb' : MetaIcon(img = 'layer-rgb-add',
+- label = _('Add RGB map layer')),
+- 'scatt_plot' : MetaIcon(img = 'layer-raster-analyze',
+- label = _('Open Scatter Plot Tool (EXPERIMENTAL GSoC 2013)')),
++ label = _('Add RGB map layer'))
+ }
+
+ class IClassMapToolbar(BaseToolbar):
+@@ -117,10 +115,7 @@
+ ("zoomBack", icons["zoomBack"],
+ self.parent.OnZoomBack),
+ ("zoomToMap", icons["zoomExtent"],
+- self.parent.OnZoomToMap),
+- (None, ),
+- ("scatt_plot", iClassIcons["scatt_plot"],
+- self.parent.OnScatterplot)
++ self.parent.OnZoomToMap)
+ ))
+ class IClassToolbar(BaseToolbar):
+ """!IClass toolbar
+Index: gui/wxpython/iclass/frame.py
+===================================================================
+--- gui/wxpython/iclass/frame.py (revision 57728)
++++ gui/wxpython/iclass/frame.py (working copy)
+@@ -64,6 +64,8 @@
+ IClassExportAreasDialog, IClassMapDialog
+ from iclass.plots import PlotPanel
+
++from grass.pydispatch.signal import Signal
+
-+ self._A = self._full_res[y0 - self.miny:y1 - self.miny:sy,
-+ x0 - self.minx:x1 - self.minx:sx]
+ class IClassMapFrame(DoubleMapFrame):
+ """! wxIClass main frame
+
+@@ -114,6 +116,10 @@
+ lambda:
+ self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(None))
+ self.SetSize(size)
+
-+ x1 = x0 + self._A.shape[1] * sx
-+ y1 = y0 + self._A.shape[0] * sy
++ self.groupSet = Signal("IClassMapFrame.groupSet")
++ self.categoryChanged = Signal('IClassMapFrame.categoryChanged')
+
-+ self.set_extent([x0 - .5, x1 - .5, y0 - .5, y1 - .5])
-+ self._sx = sx
-+ self._sy = sy
-+ self._bounds = (x0, x1, y0, y1)
-+ self.changed()
+ #
+ # Add toolbars
+ #
+@@ -177,7 +183,7 @@
+ self.dialogs['category'] = None
+
+ # PyPlot init
+- self.plotPanel = PlotPanel(self, stats_data = self.stats_data)
++ self.plotPanel = PlotPanel(self, giface = self._giface, stats_data = self.stats_data)
+
+ self._addPanes()
+ self._mgr.Update()
+@@ -237,7 +243,7 @@
+ return False
+
+ return vectorName
+-
++
+ def RemoveTempVector(self):
+ """!Removes temporary vector map with training areas"""
+ ret = RunCommand(prog = 'g.remove',
+@@ -334,11 +340,12 @@
+ self._addPaneMapWindow(name = 'preview')
+ self._addPaneToolbar(name = 'iClassTrainingMapManager')
+ self._addPaneMapWindow(name = 'training')
+-
+
-+ def draw(self, renderer, *args, **kwargs):
-+ self._scale_to_res()
-+ super(ModestImage, self).draw(renderer, *args, **kwargs)
++ self._mgr.SetDockSizeConstraint(0.5, 0.5)
+ self._mgr.AddPane(self.plotPanel, wx.aui.AuiPaneInfo().
+ Name("plots").Caption(_("Plots")).
+ Dockable(False).Floatable(False).CloseButton(False).
+- Left().Layer(1).BestSize((400, -1)))
++ Left().Layer(1).BestSize((300, -1)))
+
+ def _addPaneToolbar(self, name):
+ if name == 'iClassPreviewMapManager':
+@@ -489,12 +496,14 @@
+ group = grass.find_file(name=g, element='group')
+ self.g['group'] = group['name']
+ self.g['subgroup'] = s
++ self.groupSet.emit(group=self.g['group'],
++ subgroup=self.g['subgroup'])
+ break
+ else:
+ break
+
+ dlg.Destroy()
+-
+
-+def imshow(axes, X, cmap=None, norm=None, aspect=None,
-+ interpolation=None, alpha=None, vmin=None, vmax=None,
-+ origin=None, extent=None, shape=None, filternorm=1,
-+ filterrad=4.0, imlim=None, resample=None, url=None, **kwargs):
-+ """Similar to matplotlib's imshow command, but produces a ModestImage
+ def OnImportAreas(self, event):
+ """!Import training areas"""
+ # check if we have any changes
+@@ -771,17 +780,20 @@
+
+ Updates number of stddev, histograms, layer in preview display.
+ """
+- stat = self.stats_data.GetStatistics(currentCat)
+- nstd = stat.nstd
+- self.toolbars['iClass'].UpdateStddev(nstd)
+-
+- self.plotPanel.UpdateCategory(currentCat)
+- self.plotPanel.OnPlotTypeSelected(None)
++ if currentCat:
++ stat = self.stats_data.GetStatistics(currentCat)
++ nstd = stat.nstd
++ self.toolbars['iClass'].UpdateStddev(nstd)
++
++ self.plotPanel.UpdateCategory(currentCat)
++ self.plotPanel.OnPlotTypeSelected(None)
+
+- name = stat.rasterName
+- name = self.previewMapManager.GetAlias(name)
+- if name:
+- self.previewMapManager.SelectLayer(name)
++ name = stat.rasterName
++ name = self.previewMapManager.GetAlias(name)
++ if name:
++ self.previewMapManager.SelectLayer(name)
+
-+ Unlike matplotlib version, must explicitly specify axes
-+ Author: Chris Beaumont <beaumont at hawaii.edu>
-+ """
++ self.categoryChanged.emit(cat = currentCat)
+
+ def DeleteAreas(self, cats):
+ """!Removes all training areas of given categories
+@@ -808,12 +820,12 @@
+ def UpdateRasterName(self, newName, cat):
+ """!Update alias of raster map when category name is changed"""
+ origName = self.stats_data.GetStatistics(cat).rasterName
+- self.previewMapManager.SetAlias(origName, newName)
++ self.previewMapManager.SetAlias(origName, self._addSuffix(newName))
+
+ def StddevChanged(self, cat, nstd):
+ """!Standard deviation multiplier changed, rerender map, histograms"""
+ stat = self.stats_data.GetStatistics(cat)
+- stat.nstd = nstd
++ stat.SetStatistics({"nstd" : nstd})
+
+ if not stat.IsReady():
+ return
+@@ -925,7 +937,7 @@
+
+ self.ConvertToNull(name = stats.rasterName)
+ self.previewMapManager.AddLayer(name = stats.rasterName,
+- alias = stats.name, resultsLayer = True)
++ alias = self._addSuffix(stats.name), resultsLayer = True)
+ # write statistics
+ I_iclass_add_signature(self.signatures, statistics)
+
+@@ -938,7 +950,11 @@
+
+ self.UpdateChangeState(changes = False)
+ return True
+-
+
-+ if not axes._hold:
-+ axes.cla()
-+ if norm is not None:
-+ assert(isinstance(norm, mcolors.Normalize))
-+ if aspect is None:
-+ aspect = rcParams['image.aspect']
-+ axes.set_aspect(aspect)
++ def _addSuffix(self, name):
++ suffix = _('results')
++ return '_'.join((name, suffix))
+
-+ if extent:
-+ minx=extent[0]
-+ miny=extent[2]
-+ else:
-+ minx=0.0
-+ miny=0.0
+ def OnSaveSigFile(self, event):
+ """!Asks for signature file name and saves it."""
+ if not self.g['group']:
+@@ -1115,27 +1131,13 @@
+ self.GetFirstWindow().SetModePointer()
+ self.GetSecondWindow().SetModePointer()
+
+- def OnScatterplot(self, event):
+- """!Init interactive scatterplot tools
+- """
+- if self.dialogs['scatt_plot']:
+- self.dialogs['scatt_plot'].Raise()
+- return
++ def GetMapManagers(self):
++ """!Get map managers of wxIClass
+
+- try:
+- from scatt_plot.dialogs import ScattPlotMainDialog
+- except:
+- GError(parent = self, message = _("The Scatter Plot Tool is not installed."))
+- return
++ @return trainingMapManager, previewMapManager
++ """
++ return self.trainingMapManager, self.previewMapManager
+
+- self.dialogs['scatt_plot'] = ScattPlotMainDialog(parent=self, giface=self._giface, iclass_mapwin = self.GetFirstWindow())
+-
+- scatt_mgr = self.dialogs['scatt_plot'].GetScattMgr()
+- scatt_mgr.DigitDataChanged(self.toolbars['vdigit'].mapLayer.GetName(), self.GetFirstWindow().GetDigit())
+-
+- self.dialogs['scatt_plot'].CenterOnScreen()
+- self.dialogs['scatt_plot'].Show()
+-
+ class MapManager:
+ """! Class for managing map renderer.
+
+@@ -1178,7 +1180,6 @@
+ self.frame.Render(self.mapWindow)
+
+ if alias is not None:
+- alias = self._addSuffix(alias)
+ self.layerName[alias] = name
+ name = alias
+ else:
+@@ -1235,7 +1236,11 @@
+ self.toolbar.choice.SetSelection(0)
+
+ self.frame.Render(self.mapWindow)
+-
++
++ def Render(self):
++ """@todo giface shoud be used instead of this method"""
++ self.frame.Render(self.mapWindow)
+
-+ im = ModestImage(minx, miny, axes, cmap, norm, interpolation, origin, extent,
-+ filternorm=filternorm,
-+ filterrad=filterrad, resample=resample, **kwargs)
+ def RemoveLayer(self, name, idx):
+ """!Removes layer from Map and update toolbar"""
+ name = self.layerName[name]
+@@ -1291,11 +1296,7 @@
+ def _changeOpacity(self, layer, opacity):
+ self.map.ChangeOpacity(layer=layer, opacity=opacity)
+ self.frame.Render(self.mapWindow)
+-
+- def _addSuffix(self, name):
+- suffix = _('results')
+- return '_'.join((name, suffix))
+-
++
+ def GetAlias(self, name):
+ """!Returns alias for layer"""
+ name = [k for k, v in self.layerName.iteritems() if v == name]
+@@ -1306,11 +1307,11 @@
+ def SetAlias(self, original, alias):
+ name = self.GetAlias(original)
+ if name:
+- self.layerName[self._addSuffix(alias)] = original
++ self.layerName[alias] = original
+ del self.layerName[name]
+ idx = self.toolbar.choice.FindString(name)
+ if idx != wx.NOT_FOUND:
+- self.toolbar.choice.SetString(idx, self._addSuffix(alias))
++ self.toolbar.choice.SetString(idx, alias)
+
+ def test():
+ import core.render as render
+Index: gui/wxpython/iclass/plots.py
+===================================================================
+--- gui/wxpython/iclass/plots.py (revision 57728)
++++ gui/wxpython/iclass/plots.py (working copy)
+@@ -19,6 +19,7 @@
+ import wx.lib.plot as plot
+ import wx.lib.scrolledpanel as scrolled
+ from core.utils import _
++from core.gcmd import GError
+
+ class PlotPanel(scrolled.ScrolledPanel):
+ """!Panel for drawing multiple plots.
+@@ -28,7 +29,7 @@
+ for each band and for one category. Coincidence plots show min max range
+ of classes for each band.
+ """
+- def __init__(self, parent, stats_data):
++ def __init__(self, parent, giface, stats_data):
+ scrolled.ScrolledPanel.__init__(self, parent)
+
+ self.SetupScrolling(scroll_x = False, scroll_y = True)
+@@ -38,26 +39,71 @@
+ self.stats_data = stats_data
+ self.currentCat = None
+
++ self._giface = giface
+
-+ im.set_data(X)
-+ im.set_alpha(alpha)
-+ axes._set_artist_props(im)
+ self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+-
+
-+ if im.get_clip_path() is None:
-+ # image does not already have clipping set, clip to axes patch
-+ im.set_clip_path(axes.patch)
+ self._createControlPanel()
+-
++ self._createPlotPanel()
++ self._createScatterPlotPanel()
+
-+ #if norm is None and shape is None:
-+ # im.set_clim(vmin, vmax)
-+ if vmin is not None or vmax is not None:
-+ im.set_clim(vmin, vmax)
-+ else:
-+ im.autoscale_None()
-+ im.set_url(url)
+ self.SetSizer(self.mainSizer)
+ self.mainSizer.Fit(self)
+ self.Layout()
+-
+
-+ # update ax.dataLim, and, if autoscaling, set viewLim
-+ # to tightly fit the image, regardless of dataLim.
-+ im.set_extent(im.get_extent())
++ def _createPlotPanel(self):
+
-+ axes.images.append(im)
-+ im._remove_method = lambda h: axes.images.remove(h)
++ self.canvasPanel = wx.Panel(parent=self)
++ self.mainSizer.Add(item = self.canvasPanel, proportion = 1, flag = wx.EXPAND, border = 0)
++ self.canvasSizer = wx.BoxSizer(wx.VERTICAL)
++ self.canvasPanel.SetSizer(self.canvasSizer)
+
-+ return im
+ def _createControlPanel(self):
+ self.plotSwitch = wx.Choice(self, id = wx.ID_ANY,
+ choices = [_("Histograms"),
+- _("Coincident plots")])
++ _("Coincident plots"),
++ _("Scatter plots")])
+ self.mainSizer.Add(self.plotSwitch, proportion = 0, flag = wx.EXPAND|wx.ALL, border = 5)
+ self.plotSwitch.Bind(wx.EVT_CHOICE, self.OnPlotTypeSelected)
+-
++
++ def _createScatterPlotPanel(self):
++ """!Init interactive scatterplot tools
++ """
++ try:
++ from scatt_plot.frame import IClassIScattPanel
++ self.scatt_plot_panel = IClassIScattPanel(parent=self,
++ giface=self._giface,
++ iclass_mapwin = self.parent.GetFirstWindow())
++ self.mainSizer.Add(self.scatt_plot_panel, proportion = 1, flag = wx.EXPAND, border = 0)
++ self.scatt_plot_panel.Hide()
++ except ImportError as e:#TODO
++ self.scatt_error = _("Scatter plot functionality is disabled. Reason:\n" \
++ "Unable to import packages needed for scatter plot.\n%s" % e)
++ GError(self.scatt_error)
++ self.scatt_plot_panel = None
+
-+def MergeImg(cats_order, scatts, styles, output_queue):
+ def OnPlotTypeSelected(self, event):
+ """!Plot type selected"""
+
-+ start_time = time.clock()
-+ cmap_time = 0
-+ merge_time = 0
-+ mask_time = 0
-+ norm_time = 0
-+ max_time = 0
++ if self.plotSwitch.GetSelection() in [0, 1]:
++ self.SetupScrolling(scroll_x = False, scroll_y = True)
++ if self.scatt_plot_panel:
++ self.scatt_plot_panel.Hide()
++ self.canvasPanel.Show()
++ self.Layout()
+
-+ init = True
-+ merge_tmp = grass.tempfile()
-+ for cat_id in cats_order:
-+ if not scatts.has_key(cat_id):
-+ continue
-+ scatt = scatts[cat_id]
-+ #print "color map %d" % cat_id
-+ #TODO make more general
-+ if cat_id != 0 and (styles[cat_id]['opacity'] == 0.0 or \
-+ not styles[cat_id]['show']):
-+ continue
-+ if init:
-+
-+ b2_i = scatt['bands_info']['b1']
-+ b1_i = scatt['bands_info']['b2']
-+
-+ full_extend = (b1_i['min'] - 0.5, b1_i['max'] + 0.5, b2_i['min'] - 0.5, b2_i['max'] + 0.5)
-+
-+ if cat_id == 0:
-+ cmap = matplotlib.cm.jet
-+ cmap.set_bad('w',1.)
-+ cmap._init()
-+ cmap._lut[len(cmap._lut) - 1, -1] = 0
++ elif self.plotSwitch.GetSelection() == 2:
++ self.SetupScrolling(scroll_x = False, scroll_y = False)
++ if self.scatt_plot_panel:
++ self.scatt_plot_panel.Show()
+ else:
-+ colors = styles[cat_id]['color'].split(":")
++ GError(self.scatt_error)
++ self.canvasPanel.Hide()
++ self.Layout()
+
-+ cmap = matplotlib.cm.jet
-+ cmap.set_bad('w',1.)
-+ cmap._init()
-+ cmap._lut[len(cmap._lut) - 1, -1] = 0
-+ cmap._lut[:, 0] = int(colors[0])/255.0
-+ cmap._lut[:, 1] = int(colors[1])/255.0
-+ cmap._lut[:, 2] = int(colors[2])/255.0
+ if self.currentCat is None:
+ return
+-
+
-+ #if init:
-+ tmp = time.clock()
+ if self.plotSwitch.GetSelection() == 0:
+ stat = self.stats_data.GetStatistics(self.currentCat)
+ if not stat.IsReady():
+@@ -66,7 +112,10 @@
+ self.DrawHistograms(stat)
+ else:
+ self.DrawCoincidencePlots()
+-
+
-+ masked_cat = np.ma.masked_less_equal(scatt['np_vals'], 0)
-+ mask_time += time.clock() - tmp
++ self.Layout()
+
-+ tmp = time.clock()
-+ vmax = np.amax(masked_cat)
-+ max_time += time.clock() - tmp
+
-+ tmp = time.clock()
-+ masked_cat = np.uint8(masked_cat * (255.0 / float(vmax)))
-+ norm_time += time.clock() - tmp
+ def StddevChanged(self):
+ """!Standard deviation multiplier changed, redraw histograms"""
+ if self.plotSwitch.GetSelection() == 0:
+@@ -89,7 +138,7 @@
+ panel.Destroy()
+
+ self.canvasList = []
+-
+
-+ #print masked_cat
-+ #print masked_cat.shape
-+ #print masked_cat.mask.dtype
-+ #
-+ #print "colored_cat.shape"
-+ #print colored_cat.shape
-+ #merged_img = np.memmap(merge_tmp, dtype='uint8',, shape=colored_cat.shape)
-+ tmp = time.clock()
-+ cmap = np.uint8(cmap._lut * 255)
-+ sh =masked_cat.shape
+ def ClearPlots(self):
+ """!Clears plot canvases"""
+ for bandIdx in range(len(self.bandList)):
+@@ -104,15 +153,15 @@
+ def CreatePlotCanvases(self):
+ """!Create plot canvases according to the number of bands"""
+ for band in self.bandList:
+- canvas = plot.PlotCanvas(self)
++ canvas = plot.PlotCanvas(self.canvasPanel)
+ canvas.SetMinSize((-1, 140))
+ canvas.SetFontSizeTitle(10)
+ canvas.SetFontSizeAxis(8)
+ self.canvasList.append(canvas)
+
+- self.mainSizer.Add(item = canvas, proportion = 1, flag = wx.EXPAND, border = 0)
+-
+- self.SetVirtualSize(self.GetBestVirtualSize())
++ self.canvasSizer.Add(item = canvas, proportion = 1, flag = wx.EXPAND, border = 0)
++
++ self.SetVirtualSize(self.GetBestVirtualSize())
+ self.Layout()
+
+ def UpdatePlots(self, group, subgroup, currentCat, stats_data):
+@@ -139,7 +188,7 @@
+
+ def UpdateCategory(self, cat):
+ self.currentCat = cat
+-
++
+ def DrawCoincidencePlots(self):
+ """!Draw coincidence plots"""
+ for bandIdx in range(len(self.bandList)):
+Index: gui/wxpython/mapdisp/toolbars.py
+===================================================================
+--- gui/wxpython/mapdisp/toolbars.py (revision 57728)
++++ gui/wxpython/mapdisp/toolbars.py (working copy)
+@@ -49,6 +49,8 @@
+ label = _('Create histogram of raster map')),
+ 'vnet' : MetaIcon(img = 'vector-tools',
+ label = _('Vector network analysis tool')),
++ 'interact_scatter' : MetaIcon(img = 'layer-raster-profile',
++ label = _("Interactive scatter plot tool")),
+ }
+
+ NvizIcons = {
+@@ -239,7 +241,9 @@
+ (MapIcons["scatter"], self.parent.OnScatterplot),
+ (MapIcons["histogram"], self.parent.OnHistogramPyPlot),
+ (BaseIcons["histogramD"], self.parent.OnHistogram),
+- (MapIcons["vnet"], self.parent.OnVNet)))
++ (MapIcons["vnet"], self.parent.OnVNet),
++ (MapIcons["interact_scatter"],
++ self.parent.OnIScatt)))
+
+ def OnDecoration(self, event):
+ """!Decorations overlay menu
+Index: gui/wxpython/mapdisp/frame.py
+===================================================================
+--- gui/wxpython/mapdisp/frame.py (revision 57728)
++++ gui/wxpython/mapdisp/frame.py (working copy)
+@@ -225,6 +225,7 @@
+ #
+ self.dialogs = {}
+ self.dialogs['attributes'] = None
++ self.dialogs['scatt_plot'] = None
+ self.dialogs['category'] = None
+ self.dialogs['barscale'] = None
+ self.dialogs['legend'] = None
+@@ -1168,6 +1169,19 @@
+ """!Returns toolbar with zooming tools"""
+ return self.toolbars['map']
+
++ def OnIScatt(self, event):
++ """!Init interactive scatterplot tools
++ """
++ if self.dialogs['scatt_plot']:
++ self.dialogs['scatt_plot'].Raise()
++ return
+
-+ colored_cat = np.zeros(dtype='uint8', shape=(sh[0], sh[1], 4))
-+ ApplyColormap(masked_cat, masked_cat.mask, cmap, colored_cat)
++ from scatt_plot.frame import IScattDialog
++ self.dialogs['scatt_plot'] = IScattDialog(parent=self, giface=self._giface)
++
++ self.dialogs['scatt_plot'].CenterOnScreen()
++ self.dialogs['scatt_plot'].Show()
+
-+ #colored_cat = np.uint8(cmap(masked_cat) * 255)
-+ cmap_time += time.clock() - tmp
-+
-+ del masked_cat
-+ del cmap
-+
-+ #colored_cat[...,3] = np.choose(masked_cat.mask, (255, 0))
-+
-+ tmp = time.clock()
-+ if init:
-+ merged_img = np.memmap(merge_tmp, dtype='uint8', mode='w+', shape=colored_cat.shape)
-+ merged_img[:] = colored_cat[:]
-+ init = False
-+ else:
-+ MergeArrays(merged_img, colored_cat, styles[cat_id]['opacity'])
-+
-+ """
-+ #c_img_a = np.memmap(grass.tempfile(), dtype="uint16", mode='w+', shape = shape)
-+ c_img_a = colored_cat.astype('uint16')[:,:,3] * styles[cat_id]['opacity']
-+
-+ #TODO apply strides and there will be no need for loop
-+ #b = as_strided(a, strides=(0, a.strides[3], a.strides[3], a.strides[3]), shape=(3, a.shape[0], a.shape[1]))
-+
-+ for i in range(3):
-+ merged_img[:,:,i] = (merged_img[:,:,i] * (255 - c_img_a) + colored_cat[:,:,i] * c_img_a) / 255;
-+ merged_img[:,:,3] = (merged_img[:,:,3] * (255 - c_img_a) + 255 * c_img_a) / 255;
-+
-+ del c_img_a
-+ """
-+ merge_time += time.clock() - tmp
-+
-+ del colored_cat
-+
-+ end_time = time.clock() - start_time
-+ #print "all time:%f" % (end_time)
-+ #print "cmap_time:%f" % (cmap_time / end_time * 100.0 )
-+ #print "merge_time:%f" % (merge_time / end_time * 100.0 )
-+ #print "mask_time:%f" % (mask_time / end_time * 100.0 )
-+ #print "nax_time:%f" % (max_time / end_time * 100.0 )
-+ #print "norm_time:%f" % (norm_time / end_time * 100.0 )
-+
-+ #output_queue.put((merged_img, full_extend))
-+ return merged_img, full_extend
-\ No newline at end of file
+ def OnVNet(self, event):
+ """!Dialog for v.net* modules
+ """
Index: gui/wxpython/scatt_plot/controllers.py
===================================================================
--- gui/wxpython/scatt_plot/controllers.py (revision 0)
+++ gui/wxpython/scatt_plot/controllers.py (working copy)
-@@ -0,0 +1,883 @@
+@@ -0,0 +1,891 @@
+"""!
+ at package scatt_plot.controllers
+
@@ -2400,8 +2824,8 @@
+ if not sel_cat_id:
+ return
+
-+ for scatt in self.plots.itervalues():
-+ scatt.SetEmpty()
++ #for scatt in self.plots.itervalues():
++ # scatt.SetEmpty()
+
+ self.computingStarted.emit()
+
@@ -2685,12 +3109,14 @@
+ def __init__(self, scatt_mgr, cats_mgr, giface):
+ self.scatt_mgr = scatt_mgr
+ self.cats_mgr = cats_mgr
-+ self.set_group = None
++ self.set_g = {'group' : None, 'subg' : None}
+ self.giface = giface
+ self.added_cats_rasts = {}
+
+ def SetData(self):
-+ dlg = IClassGroupDialog(self.scatt_mgr.guiparent, group=self.set_group)
++ dlg = IClassGroupDialog(self.scatt_mgr.guiparent,
++ group=self.set_g['g'],
++ subgroup=self.set_g['subg'])
+
+ bands = []
+ while True:
@@ -2698,9 +3124,11 @@
+
+ bands = dlg.GetGroupBandsErr(parent=self.scatt_mgr.guiparent)
+ if bands:
-+ name = dlg.GetGroup()
++ name, s = dlg.GetData()
+ group = grass.find_file(name = name, element = 'group')
-+ self.set_group = group['name']
++ self.set_g['group'] = group['name']
++ self.set_g['subg'] = s
++
+ break
+ else:
+ break
@@ -2889,11 +3317,15 @@
+ self.stats_data.DeleteStatistics(cat_id)
+ self.stats_data.statisticsDeleted.connect(self.DeleteCategory)
+
-+ def GroupSet(self, group):
++ def GroupSet(self, group, subgroup):
++ kwargs = {}
++ if subgroup:
++ kwargs['subgroup'] = subgroup
++
+ res = RunCommand('i.group',
+ flags = 'g',
-+ group = group, subgroup = group,
-+ read = True).strip()
++ group = group,
++ read = True, **kwargs).strip()
+
+ if res.split('\n')[0]:
+ bands = res.split('\n')
@@ -3738,7 +4170,7 @@
===================================================================
--- gui/wxpython/scatt_plot/scatt_core.py (revision 0)
+++ gui/wxpython/scatt_plot/scatt_core.py (working copy)
-@@ -0,0 +1,795 @@
+@@ -0,0 +1,813 @@
+"""!
+ at package scatt_plot.scatt_plot
+
@@ -3764,14 +4196,14 @@
+
+from math import sqrt, ceil, floor
+from copy import deepcopy
-+from scipy.signal import convolve2d
++#from scipy.signal import convolve2d
+
+from core.gcmd import GException, GError, RunCommand
+
+import grass.script as grass
+
+from core_c import CreateCatRast, ComputeScatts, UpdateCatRast, \
-+ SC_SCATT_DATA, SC_SCATT_CONDITIONS
++ Rasterize, SC_SCATT_DATA, SC_SCATT_CONDITIONS
+
+class Core:
+ def __init__(self):
@@ -3887,13 +4319,27 @@
+ b1, b2 = idScattToidBands(scatt_id, len(self.an_data.GetBands()))
+ b = self.scatts_dt.GetBandsInfo(scatt_id)
+
-+ raster_pol = RasterizePolygon(coords, b['b1']['range'], b['b1']['min'],
-+ b['b2']['range'], b['b2']['min'])
++ region = {}
++ region['s'] = b['b2']['min'] - 0.5
++ region['n'] = b['b2']['max'] + 0.5
+
-+ raster_ind = np.where(raster_pol > 0)
++ region['w'] = b['b1']['min'] - 0.5
++ region['e'] = b['b1']['max'] + 0.5
++
+ arr = self.scatt_conds_dt.GetValuesArr(cat_id, scatt_id)
++ Rasterize(polygon=coords,
++ rast=arr,
++ region=region,
++ value=value)
+
-+ arr[raster_ind] = value
++ # previous way of rasterization / used scipy
++ #raster_pol = RasterizePolygon(coords, b['b1']['range'], b['b1']['min'],
++ # b['b2']['range'], b['b2']['min'])
++
++ #raster_ind = np.where(raster_pol > 0)
++ #arr = self.scatt_conds_dt.GetValuesArr(cat_id, scatt_id)
++
++ #arr[raster_ind] = value
+ #arr.flush()
+
+ self.ComputeCatsScatts([cat_id])
@@ -4483,6 +4929,9 @@
+
+ return cats_rasts
+
++
++# not used, using iclass_perimeter algorith instead of scipy convolve2d
++"""
+def RasterizePolygon(pol, height, min_h, width, min_w):
+
+ # Joe Kington
@@ -4509,6 +4958,7 @@
+ raster = convolve2d(grid, B, 'valid')
+
+ return raster
++"""
+
+#TODO move to utils?
+def idScattToidBands(scatt_id, n_bands):
@@ -4534,2419 +4984,1977 @@
+
+ return scatt_id
+
-Index: gui/wxpython/vnet/dialogs.py
+Index: gui/wxpython/scatt_plot/core_c.py
===================================================================
---- gui/wxpython/vnet/dialogs.py (revision 57652)
-+++ gui/wxpython/vnet/dialogs.py (working copy)
-@@ -1218,7 +1218,7 @@
- pos = (row, 1))
-
- row += 1
-- gridSizer.Add(item = self.settings["invert_colors"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
-+ gridSizer.Add(item=self.settings["invert_colors"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
-
- gridSizer.AddGrowableCol(1)
- styleBoxSizer.Add(item = gridSizer, flag = wx.EXPAND)
-Index: gui/wxpython/vnet/toolbars.py
-===================================================================
---- gui/wxpython/vnet/toolbars.py (revision 57652)
-+++ gui/wxpython/vnet/toolbars.py (working copy)
-@@ -19,9 +19,9 @@
-
- import wx
-
--from icon import MetaIcon
-+from icons.icon import MetaIcon
- from gui_core.toolbars import BaseToolbar, BaseIcons
--from core.gcmd import RunCommand
-+from core.gcmd import RunCommand
- from core.utils import _
-
- class PointListToolbar(BaseToolbar):
-Index: gui/wxpython/Makefile
-===================================================================
---- gui/wxpython/Makefile (revision 57652)
-+++ gui/wxpython/Makefile (working copy)
-@@ -13,7 +13,7 @@
- $(wildcard animation/* core/*.py dbmgr/* gcp/*.py gmodeler/* \
- gui_core/*.py iclass/* lmgr/*.py location_wizard/*.py mapwin/*.py mapdisp/*.py \
- mapswipe/* modules/*.py nviz/*.py psmap/* rlisetup/* timeline/* vdigit/* \
-- vnet/*.py web_services/*.py wxplot/*.py) \
-+ vnet/*.py web_services/*.py wxplot/*.py scatt_plot/*.py) \
- gis_set.py gis_set_error.py wxgui.py README
-
- DSTFILES := $(patsubst %,$(ETCDIR)/%,$(SRCFILES)) \
-@@ -21,8 +21,9 @@
-
- PYDSTDIRS := $(patsubst %,$(ETCDIR)/%,animation core dbmgr gcp gmodeler \
- gui_core iclass lmgr location_wizard mapwin mapdisp modules nviz psmap \
-- mapswipe vdigit wxplot web_services rlisetup vnet timeline)
-+ mapswipe vdigit wxplot web_services rlisetup vnet timeline scatt_plot)
-
+--- gui/wxpython/scatt_plot/core_c.py (revision 0)
++++ gui/wxpython/scatt_plot/core_c.py (working copy)
+@@ -0,0 +1,232 @@
++"""!
++ at package scatt_plot.scatt_plot
+
- DSTDIRS := $(patsubst %,$(ETCDIR)/%,icons scripts xml)
-
- default: $(DSTFILES)
-Index: gui/wxpython/vdigit/wxdigit.py
-===================================================================
---- gui/wxpython/vdigit/wxdigit.py (revision 57652)
-+++ gui/wxpython/vdigit/wxdigit.py (working copy)
-@@ -17,7 +17,7 @@
- (and NumPy would be an excellent candidate for acceleration via
- e.g. OpenCL or CUDA; I'm surprised it hasn't happened already).
-
--(C) 2007-2011 by the GRASS Development Team
-+(C) 2007-2011, 2013 by the GRASS Development Team
-
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
-@@ -27,6 +27,8 @@
-
- import grass.script.core as grass
-
-+from grass.pydispatch.signal import Signal
++ at brief Functions which work with scatter plot c code.
+
- from core.gcmd import GError
- from core.debug import Debug
- from core.settings import UserSettings
-@@ -176,7 +178,21 @@
-
- if self.poMapInfo:
- self.InitCats()
--
++Classes:
+
-+ self.emit_signals = False
++(C) 2013 by the GRASS Development Team
+
-+ # signals which describes features changes during digitization,
-+ # activate them using EmitSignals method
-+ #TODO signal for errors?
-+ self.featureAdded = Signal('IVDigit.featureAdded')
-+ self.areasDeleted = Signal('IVDigit.areasDeleted')
-+ self.vertexMoved = Signal('IVDigit.vertexMoved')
-+ self.vertexAdded = Signal('IVDigit.vertexAdded')
-+ self.vertexRemoved = Signal('IVDigit.vertexRemoved')
-+ self.featuresDeleted = Signal('IVDigit.featuresDeleted')
-+ self.featuresMoved = Signal('IVDigit.featuresMoved')
-+ self.lineEdited = Signal('IVDigit.lineEdited')
++This program is free software under the GNU General Public License
++(>=v2). Read the file COPYING that comes with GRASS for details.
+
- def __del__(self):
- Debug.msg(1, "IVDigit.__del__()")
- Vect_destroy_line_struct(self.poPoints)
-@@ -188,7 +204,12 @@
- Vect_close(self.poBgMapInfo)
- self.poBgMapInfo = self.popoBgMapInfo = None
- del self.bgMapInfo
--
-+
-+ def EmitSignals(self, emit):
-+ """!Activate/deactivate signals which describes features changes during digitization.
-+ """
-+ self.emit_signals = emit
++ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
++"""
+
- def CloseBackgroundMap(self):
- """!Close background vector map"""
- if not self.poBgMapInfo:
-@@ -394,7 +415,6 @@
-
- @return tuple (number of added features, feature ids)
- """
--
- layer = self._getNewFeaturesLayer()
- cat = self._getNewFeaturesCat()
-
-@@ -419,10 +439,14 @@
- return (-1, None)
-
- self.toolbar.EnableUndo()
--
-- return self._addFeature(vtype, points, layer, cat,
-- self._getSnapMode(), self._display.GetThreshold())
--
++#TODO just for testing
++import time
+
-+ ret = self._addFeature(vtype, points, layer, cat,
-+ self._getSnapMode(), self._display.GetThreshold())
-+ if ret[0] > -1 and self.emit_signals:
-+ self.featureAdded.emit(new_bboxs = [self._createBbox(points)], new_areas_cats = [[{layer : [cat]}, None]])
++import sys
++import numpy as np
++from multiprocessing import Process, Queue
+
-+ return ret
++from ctypes import *
++try:
++ from grass.lib.imagery import *
++ from grass.lib.gis import Cell_head, G_get_window
++except ImportError, e:
++ sys.stderr.write(_("Loading ctypes libs failed"))
+
- def DeleteSelectedLines(self):
- """!Delete selected features
-
-@@ -434,16 +458,27 @@
- # collect categories for deleting if requested
- deleteRec = UserSettings.Get(group = 'vdigit', key = 'delRecord', subkey = 'enabled')
- catDict = dict()
++from core.gcmd import GException
+
-+ old_bboxs = []
-+ old_areas_cats = []
- if deleteRec:
- for i in self._display.selected['ids']:
-+
- if Vect_read_line(self.poMapInfo, None, self.poCats, i) < 0:
- self._error.ReadLine(i)
-
-- cats = self.poCats.contents
-- for j in range(cats.n_cats):
-- if cats.field[j] not in catDict.keys():
-- catDict[cats.field[j]] = list()
-- catDict[cats.field[j]].append(cats.cat[j])
-+ if self.emit_signals:
-+ ret = self._getLineAreaBboxCats(i)
-+ if ret:
-+ old_bboxs += ret[0]
-+ old_areas_cats += ret[1]
-+
-+ # catDict was not used -> put into comment
-+ #cats = self.poCats.contents
-+ #for j in range(cats.n_cats):
-+ # if cats.field[j] not in catDict.keys():
-+ # catDict[cats.field[j]] = list()
-+ # catDict[cats.field[j]].append(cats.cat[j])
-
- poList = self._display.GetSelectedIList()
- nlines = Vedit_delete_lines(self.poMapInfo, poList)
-@@ -456,7 +491,10 @@
- self._deleteRecords(catDict)
- self._addChangeset()
- self.toolbar.EnableUndo()
--
++def Rasterize(polygon, rast, region, value):
++ pol_size = len(polygon) * 2
++ pol = np.array(polygon, dtype=float)
+
-+ if self.emit_signals:
-+ self.featuresDeleted.emit(old_bboxs = old_bboxs, old_areas_cats = old_areas_cats)
++ c_uint8_p = POINTER(c_uint8)
++ c_double_p = POINTER(c_double)
+
- return nlines
-
- def _deleteRecords(self, cats):
-@@ -512,22 +550,173 @@
-
- @return number of deleted
- """
-+ if len(self._display.selected['ids']) < 1:
-+ return 0
-+
- poList = self._display.GetSelectedIList()
- cList = poList.contents
-
- nareas = 0
-+ old_bboxs = []
-+ old_areas_cats = []
++ rows, cols = rast.shape
+
- for i in range(cList.n_values):
++ #TODO creating of region is on many places
++ region['rows'] = rows
++ region['cols'] = cols
+
- if Vect_get_line_type(self.poMapInfo, cList.value[i]) != GV_CENTROID:
- continue
--
++ region['nsres'] = 1.0
++ region['ewres'] = 1.0
+
-+ if self.emit_signals:
-+ area = Vect_get_centroid_area(self.poMapInfo, cList.value[i]);
-+ if area > 0:
-+ bbox, cats = self._getaAreaBboxCats(area)
-+ old_bboxs += bbox
-+ old_areas_cats += cats
++ pol_p = pol.ctypes.data_as(c_double_p)
++ rast_p = rast.ctypes.data_as(c_uint8_p)
+
- nareas += Vedit_delete_area_centroid(self.poMapInfo, cList.value[i])
-
- if nareas > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
-+ if self.emit_signals:
-+ self.areasDeleted.emit(old_bboxs = old_bboxs, old_areas_cats = old_areas_cats)
++ cell_h = _regionToCellHead(region)
++ I_rasterize(pol_p,
++ len(polygon),
++ rast_p, value,
++ pointer(cell_h))
+
-+ return nareas
++def ApplyColormap(vals, vals_mask, colmap, out_vals):
++
++ c_uint8_p = POINTER(c_uint8)
++
++ vals_p = vals.ctypes.data_as(c_uint8_p)
++
++
++ if hasattr(vals_mask, "ctypes"):
++ vals_mask_p = vals_mask.ctypes.data_as(c_uint8_p)
++ else: #vals mask is empty (all data are selected)
++ vals_mask_p = None
++ colmap_p = colmap.ctypes.data_as(c_uint8_p)
++ out_vals_p = out_vals.ctypes.data_as(c_uint8_p)
++
++ vals_size = vals.reshape((-1)).shape[0]
++ I_apply_colormap(vals_p, vals_mask_p, vals_size, colmap_p, out_vals_p)
++
++def MergeArrays(merged_arr, overlay_arr, alpha):
++ if merged_arr.shape != overlay_arr.shape:
++ GException("MergeArrays: merged_arr.shape != overlay_arr.shape")
++
++ c_uint8_p = POINTER(c_uint8)
++ merged_p = merged_arr.ctypes.data_as(c_uint8_p)
++ overlay_p = overlay_arr.ctypes.data_as(c_uint8_p)
++
++ I_merge_arrays(merged_p, overlay_p, merged_arr.shape[0], merged_arr.shape[1], alpha)
++
++def MergeArrays(merged_arr, overlay_arr, alpha):
++ if merged_arr.shape != overlay_arr.shape:
++ GException("MergeArrays: merged_arr.shape != overlay_arr.shape")
++
++ c_uint8_p = POINTER(c_uint8)
++ merged_p = merged_arr.ctypes.data_as(c_uint8_p)
++ overlay_p = overlay_arr.ctypes.data_as(c_uint8_p)
++
++ I_merge_arrays(merged_p, overlay_p, merged_arr.shape[0], merged_arr.shape[1], alpha)
++
++def ComputeScatts(region, scatt_conds, bands, n_bands, scatts, cats_rasts_conds, cats_rasts):
++
++ q = Queue()
++ p = Process(target=_computeScattsProcess, args=(region, scatt_conds, bands,
++ n_bands, scatts, cats_rasts_conds, cats_rasts, q))
++ p.start()
++ ret = q.get()
++ p.join()
++
++ return ret[0], ret[1]
++
++def UpdateCatRast(patch_rast, region, cat_rast):
++ q = Queue()
++ p = Process(target=_updateCatRastProcess, args=(patch_rast, region, cat_rast, q))
++ p.start()
++ ret = q.get()
++ p.join()
++
++ return ret
++
++def CreateCatRast(region, cat_rast):
++ cell_head = _regionToCellHead(region)
++ I_create_cat_rast(pointer(cell_head), cat_rast)
++
++def _computeScattsProcess(region, scatt_conds, bands, n_bands, scatts, cats_rasts_conds, cats_rasts, output_queue):
++
++ #TODO names for types not 0 and 1?
++ sccats_c, cats_rasts_c, refs = _getComputationStruct(scatts, cats_rasts, SC_SCATT_DATA, n_bands)
++ scatt_conds_c, cats_rasts_conds_c, refs2 = _getComputationStruct(scatt_conds, cats_rasts_conds, SC_SCATT_CONDITIONS, n_bands)
++
++ char_bands = _stringListToCharArr(bands)
+
-+ def _getLineAreaBboxCats(self, ln_id):
-+ ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
++ cell_head = _regionToCellHead(region)
+
-+ if ltype == GV_CENTROID:
-+ #TODO centroid opttimization, can be adited also its area -> it will appear two times in new_ lists
-+ return self._getCentroidAreaBboxCats(ln_id)
-+ else:
-+ return [self._getBbox(ln_id)], [self._getLineAreasCategories(ln_id)]
++ ret = I_compute_scatts(pointer(cell_head),
++ pointer(scatt_conds_c),
++ pointer(cats_rasts_conds_c),
++ pointer(char_bands),
++ n_bands,
++ pointer(sccats_c),
++ pointer(cats_rasts_c))
+
++ I_sc_free_cats(pointer(sccats_c))
++ I_sc_free_cats(pointer(scatt_conds_c))
+
-+ def _getCentroidAreaBboxCats(self, centroid):
-+ if not Vect_line_alive(self.poMapInfo, centroid):
-+ return None
++ output_queue.put((ret, scatts))
+
-+ area = Vect_get_centroid_area(self.poMapInfo, centroid)
-+ if area > 0:
-+ return self._getaAreaBboxCats(area)
++def _getBandcRange( band_info):
++ band_c_range = struct_Range()
++
++ band_c_range.max = band_info['max']
++ band_c_range.min = band_info['min']
++
++ return band_c_range
++
++def _regionToCellHead(region):
++ cell_head = struct_Cell_head()
++ G_get_window(pointer(cell_head))
++
++ convert_dict = {'n' : 'north', 'e' : 'east',
++ 'w' : 'west', 's' : 'south',
++ 'nsres' : 'ns_res',
++ 'ewres' : 'ew_res'}
++
++ for k, v in region.iteritems():
++ if k in ["rows", "cols", "cells"]:
++ v = int(v)
+ else:
-+ return None
++ v = float(v)
+
-+ def _getaAreaBboxCats(self, area):
++ if convert_dict.has_key(k):
++ k = convert_dict[k]
++
++ setattr(cell_head, k, v)
+
-+ po_b_list = Vect_new_list()
-+ Vect_get_area_boundaries(self.poMapInfo, area, po_b_list);
-+ b_list = po_b_list.contents
++ return cell_head
+
-+ geoms = []
-+ areas_cats = []
++def _stringListToCharArr(str_list):
+
-+ if b_list.n_values > 0:
-+ for i_line in range(b_list.n_values):
++ arr = c_char_p * len(str_list)
++ char_arr = arr()
++ for i, st in enumerate(str_list):
++ if st:
++ char_arr[i] = st
++ else:
++ char_arr[i] = None
+
-+ line = b_list.value[i_line];
++ return char_arr
+
-+ geoms.append(self._getBbox(abs(line)))
-+ areas_cats.append(self._getLineAreasCategories(abs(line)))
-
-- return nareas
-+ Vect_destroy_list(po_b_list);
++def _getComputationStruct(cats, cats_rasts, cats_type, n_bands):
+
-+ return geoms, areas_cats
++ sccats = struct_scCats()
++ I_sc_init_cats(pointer(sccats), c_int(n_bands), c_int(cats_type));
+
-+ def _getLineAreasCategories(self, ln_id):
-+ if not Vect_line_alive (self.poMapInfo, ln_id):
-+ return []
++ refs = []
++ cats_rasts_core = []
++
++ for cat_id, scatt_ids in cats.iteritems():
++ cat_c_id = I_sc_add_cat(pointer(sccats))
++ cats_rasts_core.append(cats_rasts[cat_id])
+
-+ ltype = Vect_read_line(self.poMapInfo, None, None, ln_id)
-+ if ltype != GV_BOUNDARY:
-+ return []
++ for scatt_id, dt in scatt_ids.iteritems():
++ # if key is missing condition is always True (full scatter plor is computed)
++ vals = dt['np_vals']
+
-+ cats = [None, None]
++ scatt_vals = scdScattData()
+
-+ left = c_int()
-+ right = c_int()
++ c_void_p = ctypes.POINTER(ctypes.c_void_p)
+
-+ if Vect_get_line_areas(self.poMapInfo, ln_id, pointer(left), pointer(right)) == 1:
-+ areas = [left.value, right.value]
++ if cats_type == SC_SCATT_DATA:
++ vals[:] = 0
++ elif cats_type == SC_SCATT_CONDITIONS:
++ pass
++ else:
++ return None
++ data_p = vals.ctypes.data_as(c_void_p)
++ I_scd_init_scatt_data(pointer(scatt_vals), cats_type, len(vals), data_p)
+
-+ for i, a in enumerate(areas):
-+ if a > 0:
-+ centroid = Vect_get_area_centroid(self.poMapInfo, a)
-+ if centroid <= 0:
-+ continue
-+ c = self._getCategories(centroid)
-+ if c:
-+ cats[i] = c
++ refs.append(scatt_vals)
+
-+ return cats
++ I_sc_insert_scatt_data(pointer(sccats),
++ pointer(scatt_vals),
++ cat_c_id, scatt_id)
+
-+ def _getCategories(self, ln_id):
-+ if not Vect_line_alive (self.poMapInfo, ln_id):
-+ return none
++ cats_rasts_c = _stringListToCharArr(cats_rasts_core)
+
-+ poCats = Vect_new_cats_struct()
-+ if Vect_read_line(self.poMapInfo, None, poCats, ln_id) < 0:
-+ Vect_destroy_cats_struct(poCats)
-+ return None
++ return sccats, cats_rasts_c, refs
+
-+ cCats = poCats.contents
++def _updateCatRastProcess(patch_rast, region, cat_rast, output_queue):
++ cell_head = _regionToCellHead(region)
++
++
++ ret = I_insert_patch_to_cat_rast(patch_rast,
++ pointer(cell_head),
++ cat_rast)
+
-+ cats = {}
-+ for j in range(cCats.n_cats):
-+ if cats.has_key(cCats.field[j]):
-+ cats[cCats.field[j]].append(cCats.cat[j])
-+ else:
-+ cats[cCats.field[j]] = [cCats.cat[j]]
-
-+ Vect_destroy_cats_struct(poCats)
-+ return cats
++ output_queue.put(ret)
+
-+ def _getBbox(self, ln_id):
-+ if not Vect_line_alive (self.poMapInfo, ln_id):
-+ return None
+
-+ poPoints = Vect_new_line_struct()
-+ if Vect_read_line(self.poMapInfo, poPoints, None, ln_id) < 0:
-+ Vect_destroy_line_struct(poPoints)
-+ return []
+Index: gui/wxpython/scatt_plot/frame.py
+===================================================================
+--- gui/wxpython/scatt_plot/frame.py (revision 0)
++++ gui/wxpython/scatt_plot/frame.py (working copy)
+@@ -0,0 +1,714 @@
++"""!
++ at package scatt_plot.dialogs
+
-+ geom = self._convertGeom(poPoints)
-+ bbox = self._createBbox(geom)
-+ Vect_destroy_line_struct(poPoints)
-+ return bbox
++ at brief GUI.
+
-+ def _createBbox(self, points):
++Classes:
+
-+ bbox = {}
-+ for pt in points:
-+ if not bbox.has_key('maxy'):
-+ bbox['maxy'] = pt[1]
-+ bbox['miny'] = pt[1]
-+ bbox['maxx'] = pt[0]
-+ bbox['minx'] = pt[0]
-+ continue
-+
-+ if bbox['maxy'] < pt[1]:
-+ bbox['maxy'] = pt[1]
-+ elif bbox['miny'] > pt[1]:
-+ bbox['miny'] = pt[1]
-+
-+ if bbox['maxx'] < pt[0]:
-+ bbox['maxx'] = pt[0]
-+ elif bbox['minx'] > pt[0]:
-+ bbox['minx'] = pt[0]
-+ return bbox
++(C) 2013 by the GRASS Development Team
+
-+ def _convertGeom(self, poPoints):
++This program is free software under the GNU General Public License
++(>=v2). Read the file COPYING that comes with GRASS for details.
+
-+ Points = poPoints.contents
++ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
++"""
++import os
++import sys
+
-+ pts_geom = []
-+ for j in range(Points.n_points):
-+ pts_geom.append((Points.x[j], Points.y[j]))
++import wx
++import wx.lib.scrolledpanel as scrolled
++import wx.lib.mixins.listctrl as listmix
+
-+ return pts_geom
++from core import globalvar
++from core.gcmd import GException, GError, RunCommand
+
- def MoveSelectedLines(self, move):
- """!Move selected features
-
-@@ -536,16 +725,45 @@
- if not self._checkMap():
- return -1
-
-+ nsel = len(self._display.selected['ids'])
-+ if nsel < 1:
-+ return -1
++from gui_core.gselect import Select
++from gui_core.dialogs import SetOpacityDialog
++
++from scatt_plot.controllers import ScattsManager
++from scatt_plot.toolbars import MainToolbar, EditingToolbar, CategoryToolbar
++from scatt_plot.scatt_core import idScattToidBands
++from scatt_plot.plots import ScatterPlotWidget
++from vnet.dialogs import VnetStatusbar
++
++try:
++ from agw import aui
++except ImportError:
++ import wx.lib.agw.aui as aui
++
++
++class IClassIScattPanel(wx.Panel):
++ def __init__(self, parent, giface, iclass_mapwin = None,
++ id = wx.ID_ANY, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
++
++ #wx.SplitterWindow.__init__(self, parent = parent, id = id,
++ # style = wx.SP_LIVE_UPDATE)
++ wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY, **kwargs)
++
++ self.scatt_mgr = self._createScattMgr(guiparent=parent, giface=giface,
++ iclass_mapwin=iclass_mapwin)
++
++ # toobars
++ self.toolbars = {}
++ self.toolbars['mainToolbar'] = self._createMainToolbar()
++ self.toolbars['editingToolbar'] = EditingToolbar(parent = self, scatt_mgr = self.scatt_mgr)
+
- thresh = self._display.GetThreshold()
- snap = self._getSnapMode()
-
- poList = self._display.GetSelectedIList()
++ self._createCategoryPanel(self)
+
-+ if self.emit_signals:
-+ old_bboxs = []
-+ old_areas_cats = []
-+ for sel_id in self._display.selected['ids']:
-+ ret = self._getLineAreaBboxCats(sel_id)
-+ if ret:
-+ old_bboxs += ret[0]
-+ old_areas_cats += ret[1]
++ self.plot_panel = ScatterPlotsPanel(self, self.scatt_mgr)
++
++ # statusbar
++ self.stPriors = {'high' : 5, 'info' : 1}
++ self.stBar = VnetStatusbar(parent = self, style = 0)
++ self.stBar.SetFieldsCount(number = 1)
++
++ self.mainsizer = wx.BoxSizer(wx.VERTICAL)
++ self.mainsizer.Add(item = self.toolbars['mainToolbar'], proportion = 0, flag = wx.EXPAND)
++ self.mainsizer.Add(item = self.toolbars['editingToolbar'], proportion = 0, flag = wx.EXPAND)
++ self.mainsizer.Add(item = self.catsPanel, proportion = 0, flag = wx.EXPAND | wx.LEFT | wx.RIGHT , border = 5)
++ self.mainsizer.Add(item = self.plot_panel, proportion = 1, flag = wx.EXPAND)
++
++ self.mainsizer.Add(item = self.stBar, proportion = 0)
++
++ self.catsPanel.Hide()
++ self.toolbars['editingToolbar'].Hide()
++
++ self.SetSizer(self.mainsizer)
+
-+ Vect_set_updated(self.poMapInfo, 1)
-+ n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
++ self.scatt_mgr.cursorPlotMove.connect(self.CursorPlotMove)
++ self.scatt_mgr.renderingStarted.connect(lambda : self.stBar.AddStatusItem(text=_("Rendering..."),
++ key='comp',
++ priority=self.stPriors['high']))
++ self.scatt_mgr.renderingFinished.connect(lambda : self.stBar.RemoveStatusItem(key='comp'))
++
++ self.scatt_mgr.computingStarted.connect(lambda : self.stBar.AddStatusItem(text=_("Computing data..."),
++ key='comp',
++ priority=self.stPriors['high']))
++
++ #self.SetSashGravity(0.5)
++ #self.SplitHorizontally(self.head_panel, self.plot_panel, -50)
++ self.Layout()
++
++ def _selCatInIScatt(self):
++ return False
++
++ def _createMainToolbar(self):
++ return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr)
++
++ def _createScattMgr(self, guiparent, giface, iclass_mapwin):
++ return ScattsManager(guiparent=self, giface=giface, iclass_mapwin=iclass_mapwin)
++
++ def CursorPlotMove(self, x, y):
++ x = int(round(x))
++ y = int(round(y))
++ self.stBar.AddStatusItem(text="%d; %d" % (x, y),
++ key='coords',
++ priority=self.stPriors['info'])
++
++ def NewScatterPlot(self, scatt_id, transpose):
++ return self.plot_panel.NewScatterPlot(scatt_id, transpose)
++
++ def ShowPlotEditingToolbar(self, show):
++ self.toolbars["editingToolbar"].Show(show)
++ self.Layout()
++
++ def ShowCategoryPanel(self, show):
++ self.catsPanel.Show(show)
+
- nlines = Vedit_move_lines(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
- poList,
- move[0], move[1], 0,
- snap, thresh)
++ #if show:
++ # self.SetSashSize(5)
++ #else:
++ # self.SetSashSize(0)
++ self.plot_panel.SetVirtualSize(self.plot_panel.GetBestVirtualSize())
++ self.Layout()
+
- Vect_destroy_list(poList)
--
++ def _createCategoryPanel(self, parent):
++ self.catsPanel = wx.Panel(parent=parent)
++ self.cats_list = CategoryListCtrl(parent=self.catsPanel,
++ cats_mgr=self.scatt_mgr.GetCategoriesManager(),
++ sel_cats_in_iscatt=self._selCatInIScatt())
+
-+ if nlines > 0 and self.emit_signals:
-+ new_bboxs = []
-+ new_areas_cats = []
-+ n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
-+ for i in range(n_up_lines_old, n_up_lines):
-+ new_id = Vect_get_updated_line(self.poMapInfo, i)
-+ ret = self._getLineAreaBboxCats(new_id)
-+ if ret:
-+ new_bboxs += ret[0]
-+ new_areas_cats += ret[1]
++ self.catsPanel.SetMaxSize((-1, 150))
+
- if nlines > 0 and self._settings['breakLines']:
- for i in range(1, nlines):
- self._breakLineAtIntersection(nlines + i, None, changeset)
-@@ -553,7 +771,13 @@
- if nlines > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
--
-+
-+ if self.emit_signals:
-+ self.featuresMoved.emit(new_bboxs = new_bboxs,
-+ old_bboxs = old_bboxs,
-+ old_areas_cats = old_areas_cats,
-+ new_areas_cats = new_areas_cats)
++ box_capt = wx.StaticBox(parent=self.catsPanel, id=wx.ID_ANY,
++ label=' %s ' % _("Classes manager for scatter plots"),)
++ catsSizer = wx.StaticBoxSizer(box_capt, wx.VERTICAL)
+
- return nlines
-
- def MoveSelectedVertex(self, point, move):
-@@ -571,12 +795,21 @@
-
- if len(self._display.selected['ids']) != 1:
- return -1
--
++ self.toolbars['categoryToolbar'] = self._createCategoryToolbar(self.catsPanel)
+
-+ # move only first found vertex in bbox
-+ poList = self._display.GetSelectedIList()
++ catsSizer.Add(item=self.cats_list, proportion=1, flag=wx.EXPAND)
++ if self.toolbars['categoryToolbar']:
++ catsSizer.Add(item=self.toolbars['categoryToolbar'], proportion=0)
+
-+ if self.emit_signals:
-+ cList = poList.contents
-+ old_bboxs = [self._getBbox(cList.value[0])]
-+ old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
++ self.catsPanel.SetSizer(catsSizer)
+
-+ Vect_set_updated(self.poMapInfo, 1)
-+ n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
++ def CleanUp(self):
++ pass
+
- Vect_reset_line(self.poPoints)
- Vect_append_point(self.poPoints, point[0], point[1], 0.0)
--
-- # move only first found vertex in bbox
-- poList = self._display.GetSelectedIList()
++ def _createCategoryToolbar(self, parent):
++ return None
+
- moved = Vedit_move_vertex(self.poMapInfo, self.popoBgMapInfo, int(self.poBgMapInfo is not None),
- poList, self.poPoints,
- self._display.GetThreshold(type = 'selectThresh'),
-@@ -584,7 +817,17 @@
- move[0], move[1], 0.0,
- 1, self._getSnapMode())
- Vect_destroy_list(poList)
--
++class IScattDialog(wx.Dialog):
++ def __init__(self, parent, giface, title=_("GRASS GIS Interactive Scatter Plot Tool"),
++ id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE, **kwargs):
++ wx.Dialog.__init__(self, parent, id, style=style, title = title, **kwargs)
++ self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+
-+ if moved > 0 and self.emit_signals:
-+ n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
++ self.iscatt_panel = MapDispIScattPanel(self, giface)
+
-+ new_bboxs = []
-+ new_areas_cats = []
-+ for i in range(n_up_lines_old, n_up_lines):
-+ new_id = Vect_get_updated_line(self.poMapInfo, i)
-+ new_bboxs.append(self._getBbox(new_id))
-+ new_areas_cats.append(self._getLineAreasCategories(new_id))
++ mainsizer = wx.BoxSizer(wx.VERTICAL)
++ mainsizer.Add(item=self.iscatt_panel, proportion=1, flag=wx.EXPAND)
+
- if moved > 0 and self._settings['breakLines']:
- self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
- None)
-@@ -592,7 +835,13 @@
- if moved > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
--
++ self.SetSizer(mainsizer)
+
-+ if self.emit_signals:
-+ self.vertexMoved.emit(new_bboxs = new_bboxs,
-+ new_areas_cats = new_areas_cats,
-+ old_areas_cats = old_areas_cats,
-+ old_bboxs = old_bboxs)
++ self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+
- return moved
-
- def AddVertex(self, coords):
-@@ -681,6 +930,10 @@
- self._error.ReadLine(line)
- return -1
-
-+ if self.emit_signals:
-+ old_bboxs = [self._getBbox(line)]
-+ old_areas_cats = [self._getLineAreasCategories(line)]
++ self.SetMinSize((300, 300))
+
- # build feature geometry
- Vect_reset_line(self.poPoints)
- for p in coords:
-@@ -696,6 +949,9 @@
-
- newline = Vect_rewrite_line(self.poMapInfo, line, ltype,
- self.poPoints, self.poCats)
-+ if newline > 0 and self.emit_signals:
-+ new_geom = [self._getBbox(newline)]
-+ new_areas_cats = [self._getLineAreasCategories(newline)]
-
- if newline > 0 and self._settings['breakLines']:
- self._breakLineAtIntersection(newline, None)
-@@ -703,7 +959,13 @@
- if newline > 0:
- self._addChangeset()
- self.toolbar.EnableUndo()
--
++ def OnCloseWindow(self, event):
++ event.Skip()
++ #self.
++
++class MapDispIScattPanel(IClassIScattPanel):
++ def __init__(self, parent, giface,
++ id=wx.ID_ANY, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
++ IClassIScattPanel.__init__(self, parent=parent, giface=giface,
++ id=id, style=style, **kwargs)
++
++ def _createCategoryToolbar(self, parent):
++ return CategoryToolbar(parent=parent,
++ scatt_mgr=self.scatt_mgr,
++ cats_list=self.cats_list)
++
++ def _createScattMgr(self, guiparent, giface, iclass_mapwin):
++ return ScattsManager(guiparent = self, giface = giface)
++
++ def _createMainToolbar(self):
++ return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr, opt_tools=['add_group'])
++
++ def _selCatInIScatt(self):
++ return True
++
++class ScatterPlotsPanel(scrolled.ScrolledPanel):
++ def __init__(self, parent, scatt_mgr, id = wx.ID_ANY):
+
-+ if self.emit_signals:
-+ self.lineEdited.emit(old_bboxs = old_bboxs,
-+ old_areas_cats = old_areas_cats,
-+ new_bboxs = new_bboxs,
-+ new_areas_cats = new_areas_cats)
++ scrolled.ScrolledPanel.__init__(self, parent)
++ self.SetupScrolling(scroll_x = False, scroll_y = True, scrollToTop = False)
+
- return newline
-
- def FlipLine(self):
-@@ -1514,6 +1776,16 @@
- return 0
-
- poList = self._display.GetSelectedIList()
++ self.scatt_mgr = scatt_mgr
+
-+ if self.emit_signals:
-+ cList = poList.contents
-+
-+ old_bboxs = [self._getBbox(cList.value[0])]
-+ old_areas_cats = [self._getLineAreasCategories(cList.value[0])]
++ self.mainPanel = wx.Panel(parent = self, id = wx.ID_ANY)
+
-+ Vect_set_updated(self.poMapInfo, 1)
-+ n_up_lines_old = Vect_get_num_updated_lines(self.poMapInfo)
++ #self._createCategoryPanel()
++ # Fancy gui
++ self._mgr = aui.AuiManager(self.mainPanel)
++ #self._mgr.SetManagedWindow(self)
+
- Vect_reset_line(self.poPoints)
- Vect_append_point(self.poPoints, coords[0], coords[1], 0.0)
-
-@@ -1525,15 +1797,35 @@
- else:
- ret = Vedit_remove_vertex(self.poMapInfo, poList,
- self.poPoints, thresh)
++ self._mgr.Update()
+
- Vect_destroy_list(poList)
++ self._doLayout()
++ self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
++ self.Bind(wx.EVT_SCROLL_CHANGED, self.OnScrollChanged)
+
-+ if ret > 0 and self.emit_signals:
-+ new_bboxs = []
-+ new_areas_cats = []
++ self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPlotPaneClosed)
+
-+ n_up_lines = Vect_get_num_updated_lines(self.poMapInfo)
-+ for i in range(n_up_lines_old, n_up_lines):
-+ new_id = Vect_get_updated_line(self.poMapInfo, i)
-+ new_areas_cats.append(self._getLineAreasCategories(new_id))
-+ new_bboxs.append(self._getBbox(new_id))
-
- if not add and ret > 0 and self._settings['breakLines']:
- self._breakLineAtIntersection(Vect_get_num_lines(self.poMapInfo),
- None)
--
++ dlgSize = (-1, 400)
++ #self.SetBestSize(dlgSize)
++ #self.SetInitialSize(dlgSize)
++ self.SetAutoLayout(1)
++ #fix goutput's pane size (required for Mac OSX)
++ #if self.gwindow:
++ # self.gwindow.SetSashPosition(int(self.GetSize()[1] * .75))
++ self.ignore_scroll = 0
++ self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+
- if ret > 0:
- self._addChangeset()
--
++ self.scatt_i = 1
++ self.scatt_id_scatt_i = {}
+
-+ if ret > 0 and self.emit_signals:
-+ if add:
-+ self.vertexAdded.emit(old_bboxs = old_bboxs, new_bboxs = new_bboxs)
-+ else:
-+ self.vertexRemoved.emit(old_bboxs = old_bboxs,
-+ new_bboxs = new_bboxs,
-+ old_areas_cats = old_areas_cats,
-+ new_areas_cats = new_areas_cats)
++ self.Bind(wx.EVT_CLOSE, self.OnClose)
+
- return 1
-
- def GetLineCats(self, line):
-Index: gui/wxpython/vdigit/toolbars.py
-===================================================================
---- gui/wxpython/vdigit/toolbars.py (revision 57652)
-+++ gui/wxpython/vdigit/toolbars.py (working copy)
-@@ -17,6 +17,7 @@
- import wx
-
- from grass.script import core as grass
-+from grass.pydispatch.signal import Signal
-
- from gui_core.toolbars import BaseToolbar, BaseIcons
- from gui_core.dialogs import CreateNewVector
-@@ -42,6 +43,8 @@
- self.digit = None
- self._giface = giface
-
-+ self.editingStarted = Signal("VDigitToolbar.editingStarted")
++ def ScatterPlotClosed(self, scatt_id):
+
- # currently selected map layer for editing (reference to MapLayer instance)
- self.mapLayer = None
- # list of vector layers from Layer Manager (only in the current mapset)
-@@ -860,6 +863,7 @@
- alpha = int(opacity * 255)
- self.digit.GetDisplay().UpdateSettings(alpha = alpha)
-
-+ self.editingStarted.emit(vectMap = mapLayer.GetName(), digit = self.digit)
- return True
-
- def StopEditing(self):
-Index: gui/wxpython/mapdisp/toolbars.py
-===================================================================
---- gui/wxpython/mapdisp/toolbars.py (revision 57652)
-+++ gui/wxpython/mapdisp/toolbars.py (working copy)
-@@ -49,6 +49,8 @@
- label = _('Create histogram of raster map')),
- 'vnet' : MetaIcon(img = 'vector-tools',
- label = _('Vector network analysis tool')),
-+ 'interact_scatter' : MetaIcon(img = 'layer-raster-profile',
-+ label = _("Interactive scatter plot tool")),
- }
-
- NvizIcons = {
-@@ -239,7 +241,9 @@
- (MapIcons["scatter"], self.parent.OnScatterplot),
- (MapIcons["histogram"], self.parent.OnHistogramPyPlot),
- (BaseIcons["histogramD"], self.parent.OnHistogram),
-- (MapIcons["vnet"], self.parent.OnVNet)))
-+ (MapIcons["vnet"], self.parent.OnVNet),
-+ (MapIcons["interact_scatter"],
-+ self.parent.OnIScatt)))
-
- def OnDecoration(self, event):
- """!Decorations overlay menu
-Index: gui/wxpython/mapdisp/frame.py
-===================================================================
---- gui/wxpython/mapdisp/frame.py (revision 57652)
-+++ gui/wxpython/mapdisp/frame.py (working copy)
-@@ -225,6 +225,7 @@
- #
- self.dialogs = {}
- self.dialogs['attributes'] = None
-+ self.dialogs['scatt_plot'] = None
- self.dialogs['category'] = None
- self.dialogs['barscale'] = None
- self.dialogs['legend'] = None
-@@ -1168,6 +1169,19 @@
- """!Returns toolbar with zooming tools"""
- return self.toolbars['map']
-
-+ def OnIScatt(self, event):
-+ """!Init interactive scatterplot tools
-+ """
-+ if self.dialogs['scatt_plot']:
-+ self.dialogs['scatt_plot'].Raise()
-+ return
++ scatt_i = self.scatt_id_scatt_i[scatt_id]
+
-+ from scatt_plot.frame import IScattDialog
-+ self.dialogs['scatt_plot'] = IScattDialog(parent=self, giface=self._giface)
-+
-+ self.dialogs['scatt_plot'].CenterOnScreen()
-+ self.dialogs['scatt_plot'].Show()
++ name = self._getScatterPlotName(scatt_i)
++ pane = self._mgr.GetPane(name)
+
- def OnVNet(self, event):
- """!Dialog for v.net* modules
- """
-Index: gui/wxpython/iclass/dialogs.py
-===================================================================
---- gui/wxpython/iclass/dialogs.py (revision 57652)
-+++ gui/wxpython/iclass/dialogs.py (working copy)
-@@ -25,14 +25,14 @@
- import wx.lib.mixins.listctrl as listmix
- import wx.lib.scrolledpanel as scrolled
-
--from core import globalvar
-+from core import globalvar
- from core.utils import _
--from core.settings import UserSettings
--from core.gcmd import GMessage
--from gui_core.dialogs import SimpleDialog, GroupDialog
--from gui_core import gselect
--from gui_core.widgets import SimpleValidator
--from iclass.statistics import Statistics, BandStatistics
-+from core.settings import UserSettings
-+from core.gcmd import GMessage, GError, RunCommand
-+from gui_core.dialogs import SimpleDialog, GroupDialog
-+from gui_core import gselect
-+from gui_core.widgets import SimpleValidator
-+from iclass.statistics import Statistics, BandStatistics
-
- import grass.script as grass
-
-@@ -88,7 +88,52 @@
- if gr in dlg.GetExistGroups():
- self.element.SetValue(gr)
- dlg.Destroy()
++ if pane.IsOk():
++ self._mgr.ClosePane(pane)
++ self._mgr.Update()
+
-+ def AddBands(self):
-+ """!Add imagery group"""
-+ dlg = IClassGroupDialog(self, group = self.group)
-
-+ while True:
-+ if dlg.ShowModal() == wx.ID_OK:
-+ if self.SetGroup(dlg.GetGroup()):
-+ break
-+ else:
-+ break
-+
-+ dlg.Destroy()
-+
-+ def GetGroupBandsErr(self, parent):
-+ """!Get list of raster bands which are in the soubgroup of group with both having same name.
-+ If the group does not exists or it does not contain any bands in subgoup with same name,
-+ error dialog is shown.
-+ """
-+ name = self.GetGroup()
-+ group = grass.find_file(name=name, element='group')
++ def OnMouseWheel(self, event):
++ #TODO very ugly find some better solution
++ self.ignore_scroll = 3
++ event.Skip()
+
-+ bands = []
-+ if group['name']:
-+ bands = self.GetGroupBands(group['name'])
-+ if not bands:
-+ GError(_("No data found in group <%s>.\n" \
-+ "Please create subgroup with same name as group and add the data there.") \
-+ % name, parent=parent)
-+ else:
-+ GError(_("Group <%s> not found") % name, parent=parent)
++ def ScrollChildIntoView(self, child):
++ #For aui manager it does not work and returns position always to the top -> deactivated.
++ pass
+
-+ return bands
++ def OnPlotPaneClosed(self, event):
++ if isinstance(event.pane.window, ScatterPlotWidget):
++ event.pane.window.CleanUp()
+
-+ def GetGroupBands(self, group):
-+ """!Get list of raster bands which are in the soubgroup of group with both having same name."""
-+ res = RunCommand('i.group',
-+ flags='g',
-+ group=group, subgroup=group,
-+ read = True).strip()
-+ bands = None
-+ if res.split('\n')[0]:
-+ bands = res.split('\n')
-+
-+ return bands
++ def OnScrollChanged(self, event):
++ wx.CallAfter(self.Layout)
+
- class IClassMapDialog(SimpleDialog):
- """!Dialog for adding raster/vector map"""
- def __init__(self, parent, title, element):
-@@ -333,13 +378,19 @@
- toolbar.SetCategories(catNames = catNames, catIdx = cats)
- if name in catNames:
- toolbar.choice.SetStringSelection(name)
-+ cat = toolbar.GetSelectedCategoryIdx()
- elif catNames:
- toolbar.choice.SetSelection(0)
--
-+ cat = toolbar.GetSelectedCategoryIdx()
++ def OnScroll(self, event):
++ if self.ignore_scroll > 0:
++ self.ignore_scroll -= 1
+ else:
-+ cat = None
++ event.Skip()
+
- if toolbar.choice.IsEmpty():
- toolbar.EnableControls(False)
- else:
- toolbar.EnableControls(True)
++ #wx.CallAfter(self._mgr.Update)
++ #wx.CallAfter(self.Layout)
+
-+ self.mapWindow.CategoryChanged(cat)
- # don't forget to update maps, histo, ...
-
- def GetSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
-Index: gui/wxpython/iclass/toolbars.py
-===================================================================
---- gui/wxpython/iclass/toolbars.py (revision 57652)
-+++ gui/wxpython/iclass/toolbars.py (working copy)
-@@ -46,9 +46,7 @@
- 'importAreas' : MetaIcon(img = 'layer-import',
- label = _('Import training areas from vector map')),
- 'addRgb' : MetaIcon(img = 'layer-rgb-add',
-- label = _('Add RGB map layer')),
-- 'scatt_plot' : MetaIcon(img = 'layer-raster-analyze',
-- label = _('Open Scatter Plot Tool (EXPERIMENTAL GSoC 2013)')),
-+ label = _('Add RGB map layer'))
- }
-
- class IClassMapToolbar(BaseToolbar):
-@@ -117,10 +115,7 @@
- ("zoomBack", icons["zoomBack"],
- self.parent.OnZoomBack),
- ("zoomToMap", icons["zoomExtent"],
-- self.parent.OnZoomToMap),
-- (None, ),
-- ("scatt_plot", iClassIcons["scatt_plot"],
-- self.parent.OnScatterplot)
-+ self.parent.OnZoomToMap)
- ))
- class IClassToolbar(BaseToolbar):
- """!IClass toolbar
-@@ -156,7 +151,7 @@
- """!Toolbar data"""
- icons = iClassIcons
- return self._getToolbarData((("selectGroup", icons['selectGroup'],
-- self.parent.OnAddBands),
-+ lambda event : self.parent.AddBands()),
- (None, ),
- ("classManager", icons['classManager'],
- self.parent.OnCategoryManager),
-Index: gui/wxpython/iclass/frame.py
-===================================================================
---- gui/wxpython/iclass/frame.py (revision 57652)
-+++ gui/wxpython/iclass/frame.py (working copy)
-@@ -64,6 +64,8 @@
- IClassExportAreasDialog, IClassMapDialog
- from iclass.plots import PlotPanel
-
-+from grass.pydispatch.signal import Signal
++ def _doLayout(self):
+
- class IClassMapFrame(DoubleMapFrame):
- """! wxIClass main frame
-
-@@ -114,6 +116,10 @@
- lambda:
- self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(None))
- self.SetSize(size)
++ mainsizer = wx.BoxSizer(wx.VERTICAL)
++ mainsizer.Add(item = self.mainPanel, proportion = 1, flag = wx.EXPAND)
++ self.SetSizer(mainsizer)
+
-+ self.groupSet = Signal("IClassMapFrame.groupSet")
-+ self.categoryChanged = Signal('IClassMapFrame.categoryChanged')
++ self.Layout()
++ self.SetupScrolling()
+
- #
- # Add toolbars
- #
-@@ -177,7 +183,7 @@
- self.dialogs['category'] = None
-
- # PyPlot init
-- self.plotPanel = PlotPanel(self, stats_data = self.stats_data)
-+ self.plotPanel = PlotPanel(self, giface = self._giface, stats_data = self.stats_data)
-
- self._addPanes()
- self._mgr.Update()
-@@ -237,7 +243,7 @@
- return False
-
- return vectorName
--
-+
- def RemoveTempVector(self):
- """!Removes temporary vector map with training areas"""
- ret = RunCommand(prog = 'g.remove',
-@@ -334,11 +340,12 @@
- self._addPaneMapWindow(name = 'preview')
- self._addPaneToolbar(name = 'iClassTrainingMapManager')
- self._addPaneMapWindow(name = 'training')
--
++ def OnClose(self, event):
++ """!Close dialog"""
++ #TODO
++ print "closed"
++ self.scatt_mgr.CleanUp()
++ self.Destroy()
+
-+ self._mgr.SetDockSizeConstraint(0.5, 0.5)
- self._mgr.AddPane(self.plotPanel, wx.aui.AuiPaneInfo().
- Name("plots").Caption(_("Plots")).
- Dockable(False).Floatable(False).CloseButton(False).
-- Left().Layer(1).BestSize((400, -1)))
-+ Left().Layer(1).BestSize((300, -1)))
-
- def _addPaneToolbar(self, name):
- if name == 'iClassPreviewMapManager':
-@@ -477,21 +484,24 @@
-
- self.Render(self.GetFirstWindow())
-
-- def OnAddBands(self, event):
-+ def AddBands(self):
- """!Add imagery group"""
-- dlg = IClassGroupDialog(self, group = self.group)
-- if dlg.ShowModal() == wx.ID_OK:
-- self.SetGroup(dlg.GetGroup())
-+ dlg = IClassGroupDialog(self, group=self.group)
-+
-+ while True:
-+ if dlg.ShowModal() == wx.ID_OK:
-+
-+ if dlg.GetGroupBandsErr(parent=self):
-+ name = dlg.GetGroup()
-+ group = grass.find_file(name = name, element = 'group')
-+ self.group = group['name']
-+ self.groupSet.emit(group=self.group)
-+ break
-+ else:
-+ break
-+
- dlg.Destroy()
--
-- def SetGroup(self, name):
-- """!Set imagery group"""
-- group = grass.find_file(name = name, element = 'group')
-- if group['name']:
-- self.group = group['name']
-- else:
-- GError(_("Group <%s> not found") % name, parent = self)
--
++ def OnSettings(self, event):
++ pass
+
- def OnImportAreas(self, event):
- """!Import training areas"""
- # check if we have any changes
-@@ -768,17 +778,20 @@
-
- Updates number of stddev, histograms, layer in preview display.
- """
-- stat = self.stats_data.GetStatistics(currentCat)
-- nstd = stat.nstd
-- self.toolbars['iClass'].UpdateStddev(nstd)
--
-- self.plotPanel.UpdateCategory(currentCat)
-- self.plotPanel.OnPlotTypeSelected(None)
-+ if currentCat:
-+ stat = self.stats_data.GetStatistics(currentCat)
-+ nstd = stat.nstd
-+ self.toolbars['iClass'].UpdateStddev(nstd)
-+
-+ self.plotPanel.UpdateCategory(currentCat)
-+ self.plotPanel.OnPlotTypeSelected(None)
-
-- name = stat.rasterName
-- name = self.previewMapManager.GetAlias(name)
-- if name:
-- self.previewMapManager.SelectLayer(name)
-+ name = stat.rasterName
-+ name = self.previewMapManager.GetAlias(name)
-+ if name:
-+ self.previewMapManager.SelectLayer(name)
++ def _newScatterPlotName(self, scatt_id):
++ name = self._getScatterPlotName(self.scatt_i)
++ self.scatt_id_scatt_i[scatt_id] = self.scatt_i
++ self.scatt_i += 1
++ return name
+
-+ self.categoryChanged.emit(cat = currentCat)
-
- def DeleteAreas(self, cats):
- """!Removes all training areas of given categories
-@@ -805,12 +818,12 @@
- def UpdateRasterName(self, newName, cat):
- """!Update alias of raster map when category name is changed"""
- origName = self.stats_data.GetStatistics(cat).rasterName
-- self.previewMapManager.SetAlias(origName, newName)
-+ self.previewMapManager.SetAlias(origName, self._addSuffix(newName))
-
- def StddevChanged(self, cat, nstd):
- """!Standard deviation multiplier changed, rerender map, histograms"""
- stat = self.stats_data.GetStatistics(cat)
-- stat.nstd = nstd
-+ stat.SetStatistics({"nstd" : nstd})
-
- if not stat.IsReady():
- return
-@@ -923,7 +936,7 @@
-
- self.ConvertToNull(name = stats.rasterName)
- self.previewMapManager.AddLayer(name = stats.rasterName,
-- alias = stats.name, resultsLayer = True)
-+ alias = self._addSuffix(stats.name), resultsLayer = True)
- # write statistics
- I_iclass_add_signature(self.signatures, statistics)
-
-@@ -936,7 +949,11 @@
-
- self.UpdateChangeState(changes = False)
- return True
--
++ def _getScatterPlotName(self, i):
++ name = "scatter plot %d" % i
++ return name
+
-+ def _addSuffix(self, name):
-+ suffix = _('results')
-+ return '_'.join((name, suffix))
++ def NewScatterPlot(self, scatt_id, transpose):
++ #TODO needs to be resolved (should be in this class)
+
- def OnSaveSigFile(self, event):
- """!Asks for signature file name and saves it."""
- if not self.group:
-@@ -1105,27 +1122,13 @@
- self.GetFirstWindow().SetModePointer()
- self.GetSecondWindow().SetModePointer()
-
-- def OnScatterplot(self, event):
-- """!Init interactive scatterplot tools
-- """
-- if self.dialogs['scatt_plot']:
-- self.dialogs['scatt_plot'].Raise()
-- return
-+ def GetMapManagers(self):
-+ """!Get map managers of wxIClass
-
-- try:
-- from scatt_plot.dialogs import ScattPlotMainDialog
-- except:
-- GError(parent = self, message = _("The Scatter Plot Tool is not installed."))
-- return
-+ @return trainingMapManager, previewMapManager
-+ """
-+ return self.trainingMapManager, self.previewMapManager
-
-- self.dialogs['scatt_plot'] = ScattPlotMainDialog(parent=self, giface=self._giface, iclass_mapwin = self.GetFirstWindow())
--
-- scatt_mgr = self.dialogs['scatt_plot'].GetScattMgr()
-- scatt_mgr.DigitDataChanged(self.toolbars['vdigit'].mapLayer.GetName(), self.GetFirstWindow().GetDigit())
--
-- self.dialogs['scatt_plot'].CenterOnScreen()
-- self.dialogs['scatt_plot'].Show()
--
- class MapManager:
- """! Class for managing map renderer.
-
-@@ -1168,7 +1171,6 @@
- self.frame.Render(self.mapWindow)
-
- if alias is not None:
-- alias = self._addSuffix(alias)
- self.layerName[alias] = name
- name = alias
- else:
-@@ -1225,7 +1227,11 @@
- self.toolbar.choice.SetSelection(0)
-
- self.frame.Render(self.mapWindow)
--
-+
-+ def Render(self):
-+ """@todo giface shoud be used instead of this method"""
-+ self.frame.Render(self.mapWindow)
++ scatt = ScatterPlotWidget(parent = self.mainPanel,
++ scatt_mgr = self.scatt_mgr,
++ scatt_id = scatt_id,
++ transpose = transpose)
++ scatt.plotClosed.connect(self.ScatterPlotClosed)
+
- def RemoveLayer(self, name, idx):
- """!Removes layer from Map and update toolbar"""
- name = self.layerName[name]
-@@ -1281,11 +1287,7 @@
- def _changeOpacity(self, layer, opacity):
- self.map.ChangeOpacity(layer=layer, opacity=opacity)
- self.frame.Render(self.mapWindow)
--
-- def _addSuffix(self, name):
-- suffix = _('results')
-- return '_'.join((name, suffix))
--
-+
- def GetAlias(self, name):
- """!Returns alias for layer"""
- name = [k for k, v in self.layerName.iteritems() if v == name]
-@@ -1296,11 +1298,11 @@
- def SetAlias(self, original, alias):
- name = self.GetAlias(original)
- if name:
-- self.layerName[self._addSuffix(alias)] = original
-+ self.layerName[alias] = original
- del self.layerName[name]
- idx = self.toolbar.choice.FindString(name)
- if idx != wx.NOT_FOUND:
-- self.toolbar.choice.SetString(idx, self._addSuffix(alias))
-+ self.toolbar.choice.SetString(idx, alias)
-
- def test():
- import core.render as render
-Index: gui/wxpython/iclass/plots.py
-===================================================================
---- gui/wxpython/iclass/plots.py (revision 57652)
-+++ gui/wxpython/iclass/plots.py (working copy)
-@@ -19,6 +19,7 @@
- import wx.lib.plot as plot
- import wx.lib.scrolledpanel as scrolled
- from core.utils import _
-+from core.gcmd import GError
-
- class PlotPanel(scrolled.ScrolledPanel):
- """!Panel for drawing multiple plots.
-@@ -28,7 +29,7 @@
- for each band and for one category. Coincidence plots show min max range
- of classes for each band.
- """
-- def __init__(self, parent, stats_data):
-+ def __init__(self, parent, giface, stats_data):
- scrolled.ScrolledPanel.__init__(self, parent)
-
- self.SetupScrolling(scroll_x = False, scroll_y = True)
-@@ -38,26 +39,71 @@
- self.stats_data = stats_data
- self.currentCat = None
-
-+ self._giface = giface
++ bands = self.scatt_mgr.GetBands()
+
- self.mainSizer = wx.BoxSizer(wx.VERTICAL)
--
++ #TODO too low level
++ b1_id, b2_id = idScattToidBands(scatt_id, len(bands))
+
- self._createControlPanel()
--
-+ self._createPlotPanel()
-+ self._createScatterPlotPanel()
++ x_b = bands[b1_id].split('@')[0]
++ y_b = bands[b2_id].split('@')[0]
+
- self.SetSizer(self.mainSizer)
- self.mainSizer.Fit(self)
- self.Layout()
--
++ if transpose:
++ tmp = x_b
++ x_b = y_b
++ y_b = tmp
+
-+ def _createPlotPanel(self):
++ caption = "%s x: %s y: %s" % (_("scatter plot"), x_b, y_b)
+
-+ self.canvasPanel = wx.Panel(parent=self)
-+ self.mainSizer.Add(item = self.canvasPanel, proportion = 1, flag = wx.EXPAND, border = 0)
-+ self.canvasSizer = wx.BoxSizer(wx.VERTICAL)
-+ self.canvasPanel.SetSizer(self.canvasSizer)
++ self._mgr.AddPane(scatt,
++ aui.AuiPaneInfo().Dockable(True).Floatable(True).
++ Name(self._newScatterPlotName(scatt_id)).MinSize((-1, 300)).
++ Caption(caption).
++ Center().Position(1).MaximizeButton(True).
++ MinimizeButton(True).CaptionVisible(True).
++ CloseButton(True).Layer(0))
+
- def _createControlPanel(self):
- self.plotSwitch = wx.Choice(self, id = wx.ID_ANY,
- choices = [_("Histograms"),
-- _("Coincident plots")])
-+ _("Coincident plots"),
-+ _("Scatter plots")])
- self.mainSizer.Add(self.plotSwitch, proportion = 0, flag = wx.EXPAND|wx.ALL, border = 5)
- self.plotSwitch.Bind(wx.EVT_CHOICE, self.OnPlotTypeSelected)
--
-+
-+ def _createScatterPlotPanel(self):
-+ """!Init interactive scatterplot tools
-+ """
-+ try:
-+ from scatt_plot.frame import IClassIScattPanel
-+ self.scatt_plot_panel = IClassIScattPanel(parent=self,
-+ giface=self._giface,
-+ iclass_mapwin = self.parent.GetFirstWindow())
-+ self.mainSizer.Add(self.scatt_plot_panel, proportion = 1, flag = wx.EXPAND, border = 0)
-+ self.scatt_plot_panel.Hide()
-+ except ImportError as e:#TODO
-+ self.scatt_error = _("Scatter plot functionality is disabled. Reason:\n" \
-+ "Unable to import packages needed for scatter plot.\n%s" % e)
-+ GError(self.scatt_error)
-+ self.scatt_plot_panel = None
++ self._mgr.Update()
++
++ self.SetVirtualSize(self.GetBestVirtualSize())
++ self.Layout()
+
- def OnPlotTypeSelected(self, event):
- """!Plot type selected"""
++ return scatt
+
-+ if self.plotSwitch.GetSelection() in [0, 1]:
-+ self.SetupScrolling(scroll_x = False, scroll_y = True)
-+ if self.scatt_plot_panel:
-+ self.scatt_plot_panel.Hide()
-+ self.canvasPanel.Show()
-+ self.Layout()
++ def GetScattMgr(self):
++ return self.scatt_mgr
+
-+ elif self.plotSwitch.GetSelection() == 2:
-+ self.SetupScrolling(scroll_x = False, scroll_y = False)
-+ if self.scatt_plot_panel:
-+ self.scatt_plot_panel.Show()
-+ else:
-+ GError(self.scatt_error)
-+ self.canvasPanel.Hide()
-+ self.Layout()
++class CategoryListCtrl(wx.ListCtrl,
++ listmix.ListCtrlAutoWidthMixin,
++ listmix.TextEditMixin):
+
- if self.currentCat is None:
- return
--
++ def __init__(self, parent, cats_mgr, sel_cats_in_iscatt, id = wx.ID_ANY):
+
- if self.plotSwitch.GetSelection() == 0:
- stat = self.stats_data.GetStatistics(self.currentCat)
- if not stat.IsReady():
-@@ -66,7 +112,10 @@
- self.DrawHistograms(stat)
- else:
- self.DrawCoincidencePlots()
--
++ wx.ListCtrl.__init__(self, parent, id,
++ style = wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|
++ wx.LC_VRULES|wx.LC_SINGLE_SEL)
++ self.columns = ((_('Class name'), 'name'),
++ (_('Color'), 'color'))
+
-+ self.Layout()
++ self.sel_cats_in_iscatt = sel_cats_in_iscatt
+
++ self.Populate(columns = self.columns)
++
++ self.cats_mgr = cats_mgr
++ self.SetItemCount(len(self.cats_mgr.GetCategories()))
+
- def StddevChanged(self):
- """!Standard deviation multiplier changed, redraw histograms"""
- if self.plotSwitch.GetSelection() == 0:
-@@ -89,7 +138,7 @@
- panel.Destroy()
-
- self.canvasList = []
--
++ self.rightClickedItemIdx = wx.NOT_FOUND
++
++ listmix.ListCtrlAutoWidthMixin.__init__(self)
+
- def ClearPlots(self):
- """!Clears plot canvases"""
- for bandIdx in range(len(self.bandList)):
-@@ -104,15 +153,15 @@
- def CreatePlotCanvases(self):
- """!Create plot canvases according to the number of bands"""
- for band in self.bandList:
-- canvas = plot.PlotCanvas(self)
-+ canvas = plot.PlotCanvas(self.canvasPanel)
- canvas.SetMinSize((-1, 140))
- canvas.SetFontSizeTitle(10)
- canvas.SetFontSizeAxis(8)
- self.canvasList.append(canvas)
-
-- self.mainSizer.Add(item = canvas, proportion = 1, flag = wx.EXPAND, border = 0)
--
-- self.SetVirtualSize(self.GetBestVirtualSize())
-+ self.canvasSizer.Add(item = canvas, proportion = 1, flag = wx.EXPAND, border = 0)
++ listmix.TextEditMixin.__init__(self)
++
++ self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnCategoryRightUp) #wxMSW
++ self.Bind(wx.EVT_RIGHT_UP, self.OnCategoryRightUp) #wxGTK
++
++ self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
++ self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSel)
++
++ self.cats_mgr.setCategoryAttrs.connect(self.Update)
++ self.cats_mgr.deletedCategory.connect(self.Update)
++ self.cats_mgr.addedCategory.connect(self.Update)
++
++ def Update(self, **kwargs):
++ self.SetItemCount(len(self.cats_mgr.GetCategories()))
++ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
++
++ def InitCoreCats(self):
++ self.SetItemCount(len(self.cats_mgr.GetCategories()))
++ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
++
++ def SetVirtualData(self, row, column, text):
++ attr = self.columns[column][1]
++ if attr == 'name':
++ try:
++ text.encode('ascii')
++ except UnicodeEncodeError:
++ GMessage(parent = self, message = _("Please use only ASCII characters."))
++ return
++
++ cat_id = self.cats_mgr.GetCategories()[row]
++
++ self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
++ self.cats_mgr.SetCategoryAttrs(cat_id, {attr : text})
++ self.cats_mgr.setCategoryAttrs.connect(self.Update)
+
-+ self.SetVirtualSize(self.GetBestVirtualSize())
- self.Layout()
-
- def UpdatePlots(self, group, currentCat, stats_data):
-@@ -138,7 +187,7 @@
-
- def UpdateCategory(self, cat):
- self.currentCat = cat
--
-+
- def DrawCoincidencePlots(self):
- """!Draw coincidence plots"""
- for bandIdx in range(len(self.bandList)):
-Index: lib/vector/Vlib/open.c
-===================================================================
---- lib/vector/Vlib/open.c (revision 57652)
-+++ lib/vector/Vlib/open.c (working copy)
-@@ -240,7 +240,9 @@
- }
- else {
- char file_path[GPATH_MAX];
--
-+ /* reduce to current mapset if search path was set */
-+ if(strcmp(Map->mapset, "") == 0)
-+ Map->mapset = G_store(G_mapset());
- /* temporary map: reduce to current mapset if search path
- * was set */
- if (strcmp(Map->mapset, "") == 0)
-Index: lib/imagery/scatt_sccats.c
-===================================================================
---- lib/imagery/scatt_sccats.c (revision 0)
-+++ lib/imagery/scatt_sccats.c (working copy)
-@@ -0,0 +1,405 @@
-+/*!
-+ \file lib/imagery/scatt_cat_rast.c
++ self.Select(row)
++
++ def Populate(self, columns):
++ for i, col in enumerate(columns):
++ self.InsertColumn(i, col[0])#wx.LIST_FORMAT_RIGHT
+
-+ \brief Imagery library - functions for manipulation with scatter plot structs.
++ self.SetColumnWidth(0, 100)
++ self.SetColumnWidth(1, 100)
++
++ def AddCategory(self):
+
-+ Copyright (C) 2013 by the GRASS Development Team
++ self.cats_mgr.addedCategory.disconnect(self.Update)
++ cat_id = self.cats_mgr.AddCategory()
++ self.cats_mgr.addedCategory.connect(self.Update)
+
-+ This program is free software under the GNU General Public License
-+ (>=v2). Read the file COPYING that comes with GRASS for details.
++ if cat_id < 0:
++ GError(_("Maximum limit of categories number was reached."))
++ return
++ self.SetItemCount(len(self.cats_mgr.GetCategories()))
++
++ def DeleteCategory(self):
++ indexList = sorted(self.GetSelectedIndices(), reverse = True)
++ cats = []
++ for i in indexList:
++ # remove temporary raster
++ cat_id = self.cats_mgr.GetCategories()[i]
++
++ cats.append(cat_id)
+
-+ \author Stepan Turek <stepan.turek at seznam.cz> (Mentor: Martin Landa)
-+ */
++ self.cats_mgr.deletedCategory.disconnect(self.Update)
++ self.cats_mgr.DeleteCategory(cat_id)
++ self.cats_mgr.deletedCategory.connect(self.Update)
++
++ self.SetItemCount(len(self.cats_mgr.GetCategories()))
++
++ def OnSel(self, event):
++ if self.sel_cats_in_iscatt:
++ indexList = self.GetSelectedIndices()
++ sel_cats = []
++ cats = self.cats_mgr.GetCategories()
++ for i in indexList:
++ sel_cats.append(cats[i])
+
-+#include <grass/raster.h>
-+#include <grass/imagery.h>
-+#include <grass/gis.h>
++ if sel_cats:
++ self.cats_mgr.SetSelectedCat(sel_cats[0])
++ event.Skip()
+
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <math.h>
-+#include <string.h>
++ def GetSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
++ indices = []
++ lastFound = -1
++ while True:
++ index = self.GetNextItem(lastFound, wx.LIST_NEXT_ALL, state)
++ if index == -1:
++ break
++ else:
++ lastFound = index
++ indices.append(index)
++ return indices
+
-+/*!
-+ \brief Compute band ids from scatter plot id.
++ def OnEdit(self, event):
++ currentItem = event.m_itemIndex
++ currentCol = event.m_col
+
-+ Scatter plot id describes which bands defines the scatter plot.
++ if currentCol == 1:
++ dlg = wx.ColourDialog(self)
++ dlg.GetColourData().SetChooseFull(True)
+
-+ Let say we have 3 bands, their ids are 0, 1 and 2.
-+ Scatter plot with id 0 consists of band 1 (b_1_id) 0 and band 2 (b_2_id) 1.
-+ All scatter plots:
-+ scatt_id b_1_id b_2_id
-+ 0 0 1
-+ 1 0 2
-+ 2 1 2
++ if dlg.ShowModal() == wx.ID_OK:
++ color = dlg.GetColourData().GetColour().Get()
++ color = ':'.join(map(str, color))
++ self.SetVirtualData(currentItem, currentCol, color)
++ dlg.Destroy()
++ wx.CallAfter(self.SetFocus)
++
++ event.Skip()
++
++
++ def DeselectAll(self):
++ """!Deselect all items"""
++ indexList = self.GetSelectedIndices()
++ for i in indexList:
++ self.Select(i, on = 0)
++
++ # no highlight
++ self.OnCategorySelected(None)
++
++ def OnGetItemText(self, item, col):
++ attr = self.columns[col][1]
++ cat_id = self.cats_mgr.GetCategories()[item]
+
-+ \param scatt_id scatter plot id
-+ \param n_bands number of bands
-+ \param [out] b_1_id id of band1
-+ \param[out] b_2_id id of band2
++ return self.cats_mgr.GetCategoryAttrs(cat_id)[attr]
+
-+ \return 0
-+ */
-+int I_id_scatt_to_bands(const int scatt_id, const int n_bands, int * b_1_id, int * b_2_id)
-+{
-+ int n_b1 = n_bands - 1;
++ def OnGetItemImage(self, item):
++ return -1
+
-+ * b_1_id = (int) ((2 * n_b1 + 1 - sqrt((double)((2 * n_b1 + 1) * (2 * n_b1 + 1) - 8 * scatt_id))) / 2);
++ def OnGetItemAttr(self, item):
++ return None
+
-+ * b_2_id = scatt_id - ((* b_1_id) * (2 * n_b1 + 1) - (* b_1_id) * (* b_1_id)) / 2 + (* b_1_id) + 1;
++ def OnCategoryRightUp(self, event):
++ """!Show context menu on right click"""
++ item, flags = self.HitTest((event.GetX(), event.GetY()))
++ if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
++ self.rightClickedItemIdx = item
+
-+ return 0;
-+}
++ # generate popup-menu
++ cat_idx = self.rightClickedItemIdx
+
++ cats = self.cats_mgr.GetCategories()
++ cat_id = cats[cat_idx]
++ showed = self.cats_mgr.GetCategoryAttrs(cat_id)['show']
++
++ menu = wx.Menu()
+
-+/*!
-+ \brief Compute scatter plot id from band ids.
++ if showed:
++ text = _("Hide")
++ else:
++ text = _("Show")
+
-+ See also I_id_scatt_to_bands().
++ item_id = wx.NewId()
++ menu.Append(item_id, text = text)
++ self.Bind(wx.EVT_MENU, lambda event : self._setCatAttrs(cat_id=cat_id,
++ attrs={'show' : not showed}),
++ id=item_id)
++ menu.AppendSeparator()
++
++ if cat_idx != 0:
++ item_id = wx.NewId()
++ menu.Append(item_id, text=_("Move category up"))
++ self.Bind(wx.EVT_MENU, self.OnMoveUp, id=item_id)
+
-+ \param n_bands number of bands
-+ \param b_1_id id of band1
-+ \param b_1_id id of band2
-+ \param [out] scatt_id scatter plot id
++ if cat_idx != len(cats) - 1:
++ item_id = wx.NewId()
++ menu.Append(item_id, text=_("Move category down"))
++ self.Bind(wx.EVT_MENU, self.OnMoveDown, id=item_id)
+
-+ \return 0
-+ */
-+int I_bands_to_id_scatt(const int b_1_id, const int b_2_id, const int n_bands, int * scatt_id)
-+{
-+ int n_b1 = n_bands - 1;
++ menu.AppendSeparator()
++
++ item_id = wx.NewId()
++ menu.Append(item_id, text=_("Change opacity level"))
++ self.Bind(wx.EVT_MENU, self.OnPopupOpacityLevel, id=item_id)
+
-+ * scatt_id = (b_1_id * (2 * n_b1 + 1) - b_1_id * b_1_id) / 2 + b_2_id - b_1_id - 1;
++ item_id = wx.NewId()
++ menu.Append(item_id, text=_("Export raster"))
++ self.Bind(wx.EVT_MENU, self.OnExportCatRast, id=item_id)
+
-+ return 0;
-+}
+
-+/*!
-+ \brief Initialize structure for storing scatter plots data.
++ self.PopupMenu(menu)
++ menu.Destroy()
+
-+ \param cats pointer to scCats struct
-+ \param n_bands number of bands
-+ \param type SC_SCATT_DATA - stores scatter plots
-+ \param type SC_SCATT_CONDITIONS - stores selected areas in scatter plots
-+ */
-+void I_sc_init_cats(struct scCats * cats, int n_bands, int type)
-+{
-+ int i_cat;
++ def OnExportCatRast(self, event):
++ """!Export training areas"""
++ #TODO
++ # GMessage(parent=self, message=_("No class raster to export."))
++ # return
+
-+ cats->type = type;
++ cat_idx = self.rightClickedItemIdx
++ cat_id = self.cats_mgr.GetCategories()[cat_idx]
+
-+ cats->n_cats = 100;
-+ cats->n_a_cats = 0;
++ self.cats_mgr.ExportCatRast(cat_id)
+
-+ cats->n_bands = n_bands;
-+ cats->n_scatts = (n_bands - 1) * n_bands / 2;
++ def OnMoveUp(self, event):
++ cat_idx = self.rightClickedItemIdx
++ cat_id = self.cats_mgr.GetCategories()[cat_idx]
++ self.cats_mgr.ChangePosition(cat_id, cat_idx - 1)
++ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
-+ cats->cats_arr = (struct scScatts **) G_malloc(cats->n_cats * sizeof(struct scScatts *));
-+ memset(cats->cats_arr, 0, cats-> n_cats * sizeof(struct scScatts *));
++ def OnMoveDown(self, event):
++ cat_idx = self.rightClickedItemIdx
++ cat_id = self.cats_mgr.GetCategories()[cat_idx]
++ self.cats_mgr.ChangePosition(cat_id, cat_idx + 1)
++ self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
-+ cats->cats_ids = (int *) G_malloc(cats->n_cats * sizeof(int));
-+ cats->cats_idxs =(int *) G_malloc(cats->n_cats * sizeof(int));
++ def OnPopupOpacityLevel(self, event):
++ """!Popup opacity level indicator"""
+
-+ for(i_cat = 0; i_cat < cats->n_cats; i_cat++)
-+ cats->cats_idxs[i_cat] = -1;
++ cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
++ cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
++ value = cat_attrs['opacity'] * 100
++ name = cat_attrs['name']
+
-+ return;
-+}
++ dlg = SetOpacityDialog(self, opacity = value,
++ title = _("Change opacity of class <%s>" % name))
+
-+/*!
-+ \brief Free data of struct scCats, the structure itself remains alocated.
++ dlg.applyOpacity.connect(lambda value:
++ self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value}))
++ dlg.CentreOnParent()
+
-+ \param cats pointer to existing scCats struct
-+ */
-+void I_sc_free_cats(struct scCats * cats)
-+{
-+ int i_cat;
++ if dlg.ShowModal() == wx.ID_OK:
++ self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value})
++
++ dlg.Destroy()
+
-+ for(i_cat = 0; i_cat < cats->n_a_cats; i_cat++)
-+ {
-+ if(cats->cats_arr[i_cat])
-+ {
-+ G_free(cats->cats_arr[i_cat]->scatt_idxs);
-+ G_free(cats->cats_arr[i_cat]->scatts_bands);
-+ G_free(cats->cats_arr[i_cat]->scatts_arr);
-+ G_free(cats->cats_arr[i_cat]);
-+ }
-+ }
++ def _setCatAttrs(self, cat_id, attrs):
++ self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
++ self.cats_mgr.SetCategoryAttrs(cat_id, attrs)
++ self.cats_mgr.setCategoryAttrs.connect(self.Update)
+
-+ G_free(cats->cats_ids);
-+ G_free(cats->cats_idxs);
-+ G_free(cats->cats_arr);
+
-+ cats->n_cats = 0;
-+ cats->n_a_cats = 0;
-+ cats->n_bands = 0;
-+ cats->n_scatts = 0;
-+ cats->type = -1;
++class AddScattPlotDialog(wx.Dialog):
+
-+ return;
-+}
++ def __init__(self, parent, bands, id = wx.ID_ANY):
++
++ wx.Dialog.__init__(self, parent, title = ("Add scatter plot"), id = id)
+
-+#if 0
-+void I_sc_get_active_categories(int * a_cats_ids, int * n_a_cats, struct scCats * cats)
-+{
-+ a_cats_ids = cats->cats_ids;
-+ * n_a_cats = cats->n_a_cats;
-+}
-+#endif
++ self.bands = bands
+
-+/*!
-+ \brief Add category.
-+
-+ Category represents group of scatter plots.
++ self.scatt_id = None
+
-+ \param cats pointer to scCats struct
++ self._createWidgets()
+
-+ \return assigned category id (starts with 0)
-+ \return -1 if maximum nuber of categories was reached
-+ */
-+int I_sc_add_cat(struct scCats * cats)
-+{
-+ int i_scatt, i_cat_id, cat_id;
-+ int n_a_cats = cats->n_a_cats;
++ def _createWidgets(self):
+
-+ if(cats->n_a_cats >= cats->n_cats)
-+ return -1;
++ self.labels = {}
++ self.params = {}
+
-+ for(i_cat_id = 0; i_cat_id < cats->n_cats; i_cat_id++)
-+ if(cats->cats_idxs[i_cat_id] < 0) {
-+ cat_id = i_cat_id;
-+ break;
-+ }
++ self.band_1_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("x axis:"))
+
-+ cats->cats_ids[n_a_cats] = cat_id;
-+ cats->cats_idxs[cat_id] = n_a_cats;
-+
-+ cats->cats_arr[n_a_cats] = (struct scScatts *) G_malloc(sizeof(struct scScatts));
++ self.band_1_ch = wx.ComboBox(parent = self, id = wx.ID_ANY,
++ choices = self.bands,
++ style = wx.CB_READONLY, size = (350, 30))
+
-+ cats->cats_arr[n_a_cats]->scatts_arr = (struct scdScattData **) G_malloc(cats->n_scatts * sizeof(struct scdScattData *));
-+ memset((cats->cats_arr[n_a_cats]->scatts_arr), 0, cats->n_scatts * sizeof(struct scdScattData *));
-+
-+ cats->cats_arr[n_a_cats]->n_a_scatts = 0;
++ self.band_2_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("y axis:"))
+
-+ cats->cats_arr[n_a_cats]->scatts_bands = (int *) G_malloc(cats->n_scatts * 2 * sizeof(int));
-+
-+ cats->cats_arr[n_a_cats]->scatt_idxs = (int *) G_malloc(cats->n_scatts * sizeof(int));
-+ for(i_scatt = 0; i_scatt < cats->n_scatts; i_scatt++)
-+ cats->cats_arr[n_a_cats]->scatt_idxs[i_scatt] = -1;
-+
-+ ++cats->n_a_cats;
++ self.band_2_ch = wx.ComboBox(parent = self, id = wx.ID_ANY,
++ choices = self.bands,
++ style = wx.CB_READONLY, size = (350, 30))
+
-+ return cat_id;
-+}
++ # buttons
++ self.btn_close = wx.Button(parent = self, id = wx.ID_CANCEL)
++
++ self.btn_ok = wx.Button(parent = self, id = wx.ID_OK, label = _("&OK"))
+
-+#if 0
-+int I_sc_delete_cat(struct scCats * cats, int cat_id)
-+{
-+ int cat_idx, i_cat;
++ self._layout()
+
-+ if(cat_id < 0 || cat_id >= cats->n_cats)
-+ return -1;
++ def _layout(self):
+
-+ cat_idx = cats->cats_idxs[cat_id];
-+ if(cat_idx < 0)
-+ return -1;
++ border = wx.BoxSizer(wx.VERTICAL)
++ dialogSizer = wx.BoxSizer(wx.VERTICAL)
+
-+ G_free(cats->cats_arr[cat_idx]->scatt_idxs);
-+ G_free(cats->cats_arr[cat_idx]->scatts_bands);
-+ G_free(cats->cats_arr[cat_idx]->scatts_arr);
-+ G_free(cats->cats_arr[cat_idx]);
++ regionSizer = wx.BoxSizer(wx.HORIZONTAL)
+
-+ for(i_cat = cat_idx; i_cat < cats->n_a_cats - 1; i_cat++)
-+ {
-+ cats->cats_arr[i_cat] = cats->cats_arr[i_cat + 1];
-+ cats->cats_ids[i_cat] = cats->cats_ids[i_cat + 1];
-+ }
-+ cats->cats_idxs[cat_id] = -1;
++ dialogSizer.Add(item = self._addSelectSizer(title = self.band_1_label,
++ sel = self.band_1_ch))
+
-+ --cats->n_a_cats;
-+
-+ return 0;
-+}
-+#endif
++ dialogSizer.Add(item = self._addSelectSizer(title = self.band_2_label,
++ sel = self.band_2_ch))
+
-+/*!
-+ \brief Insert scatter plot data .
-+ Inserted scatt_data struct must have same type as cats struct (SC_SCATT_DATA or SC_SCATT_CONDITIONS).
++ # buttons
++ self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+
-+ \param cats pointer to scCats struct
-+ \param cat_id id number of category.
-+ \param scatt_id id number of scatter plot.
++ self.btnsizer.Add(item = self.btn_close, proportion = 0,
++ flag = wx.ALL | wx.ALIGN_CENTER,
++ border = 10)
++
++ self.btnsizer.Add(item = self.btn_ok, proportion = 0,
++ flag = wx.ALL | wx.ALIGN_CENTER,
++ border = 10)
+
-+ \return 0 on success
-+ \return -1 on failure
-+ */
-+int I_sc_insert_scatt_data(struct scCats * cats, struct scdScattData * scatt_data, int cat_id, int scatt_id)
-+{
-+ int band_1, band_2, cat_idx, n_a_scatts;
-+ struct scScatts * scatts;
++ dialogSizer.Add(item = self.btnsizer, proportion = 0,
++ flag = wx.ALIGN_CENTER)
+
-+ if(cat_id < 0 || cat_id >= cats->n_cats)
-+ return -1;
++ border.Add(item = dialogSizer, proportion = 0,
++ flag = wx.ALL, border = 5)
+
-+ cat_idx = cats->cats_idxs[cat_id];
-+ if(cat_idx < 0)
-+ return -1;
++ self.SetSizer(border)
++ self.Layout()
++ self.Fit()
+
-+ if(scatt_id < 0 && scatt_id >= cats->n_scatts)
-+ return -1;
++ # bindings
++ self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
++ self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
+
-+ scatts = cats->cats_arr[cat_idx];
-+ if(scatts->scatt_idxs[scatt_id] >= 0)
-+ return -1;
++ def _addSelectSizer(self, title, sel):
++ """!Helper layout function.
++ """
++ selSizer = wx.BoxSizer(orient = wx.VERTICAL)
+
-+ if(!scatt_data->b_conds_arr && cats->type == SC_SCATT_CONDITIONS)
-+ return -1;
++ selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
++ selTitleSizer.Add(item = title, proportion = 1,
++ flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
+
-+ if(!scatt_data->scatt_vals_arr && cats->type == SC_SCATT_DATA)
-+ return -1;
++ selSizer.Add(item = selTitleSizer, proportion = 0,
++ flag = wx.EXPAND)
+
-+ n_a_scatts = scatts->n_a_scatts;
++ selSizer.Add(item = sel, proportion = 1,
++ flag = wx.EXPAND | wx.ALL| wx.ALIGN_CENTER_VERTICAL,
++ border = 5)
+
-+ scatts->scatt_idxs[scatt_id] = n_a_scatts;
++ return selSizer
+
-+ I_id_scatt_to_bands(scatt_id, cats->n_bands, &band_1, &band_2);
++ def OnClose(self, event):
++ """!Close dialog
++ """
++ if not self.IsModal():
++ self.Destroy()
++ event.Skip()
+
-+ scatts->scatts_bands[n_a_scatts * 2] = band_1;
-+ scatts->scatts_bands[n_a_scatts * 2 + 1] = band_2;
++ def OnOk(self, event):
++ """!
++ """
++ band_1 = self.band_1_ch.GetSelection()
++ band_2 = self.band_2_ch.GetSelection()
+
-+ scatts->scatts_arr[n_a_scatts] = scatt_data;
-+ ++scatts->n_a_scatts;
+
-+ return 0;
-+}
++ if band_1 == band_2:
++ GError(_("Selected bands must be different."))
++ return
++
++ #TODO axes selection
++ if band_1 > band_2:
++ tmp_band = band_2
++ band_2 = band_1
++ band_1 = band_2
+
-+#if 0
-+int I_sc_remove_scatt_data(struct scCats * cats, struct scdScattData * scatt_data, int cat_id, int scatt_id)
-+{
-+ int cat_idx, scatt_idx, n_init_scatts, i_scatt;
-+ struct scScatts * scatts;
++ self.scatt_id = idBandsToidScatt(band_1, band_2, len(self.bands))
+
-+ if(cat_id < 0 && cat_id >= cats->n_cats)
-+ return -1;
++ event.Skip()
+
-+ cat_idx = cats->cats_idxs[cat_id];
-+ if(cat_idx < 0)
-+ return -1;
++ def GetScattId(self):
++ return self.scatt_id
+Index: gui/wxpython/scatt_plot/__init__.py
+===================================================================
+--- gui/wxpython/scatt_plot/__init__.py (revision 0)
++++ gui/wxpython/scatt_plot/__init__.py (working copy)
+@@ -0,0 +1,11 @@
++all = [
++ 'dialogs',
++ 'controllers',
++ 'frame',
++ 'gthreading',
++ 'plots',
++ 'scatt_core',
++ 'toolbars',
++ 'scatt_core',
++ 'core_c',
++ ]
+Index: gui/wxpython/scatt_plot/plots.py
+===================================================================
+--- gui/wxpython/scatt_plot/plots.py (revision 0)
++++ gui/wxpython/scatt_plot/plots.py (working copy)
+@@ -0,0 +1,943 @@
++"""!
++ at package scatt_plot.dialogs
+
-+ if(scatt_id < 0 || scatt_id >= cats->n_scatts)
-+ return -1;
++ at brief Ploting widgets.
+
-+ scatts = cats->cats_arr[cat_idx];
-+ if(scatts->scatt_idxs[scatt_id] < 0)
-+ return -1;
++Classes:
+
-+ scatt_data = scatts->scatts_arr[scatt_idx];
++(C) 2013 by the GRASS Development Team
+
-+ for(i_scatt = scatt_idx; i_scatt < scatts->n_a_scatts - 1; i_scatt++)
-+ {
-+ scatts->scatts_arr[i_scatt] = scatts->scatts_arr[i_scatt + 1];
-+ scatts->scatts_bands[i_scatt * 2] = scatts->scatts_bands[(i_scatt + 1)* 2];
-+ scatts->scatts_bands[i_scatt * 2 + 1] = scatts->scatts_bands[(i_scatt + 1) * 2 + 1];
-+ }
-+ scatts->scatts_arr[scatts->n_a_scatts] = NULL;
++This program is free software under the GNU General Public License
++(>=v2). Read the file COPYING that comes with GRASS for details.
+
-+ scatts->scatt_idxs[scatt_id] = -1;
++ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
++"""
++import wx
++import numpy as np
++from math import ceil
++#TODO testing
++import time
++from multiprocessing import Process, Queue
+
-+ scatt_data = scatts->scatts_arr[scatt_id];
-+ scatts->n_a_scatts--;
++from copy import deepcopy
++from scatt_plot.core_c import MergeArrays, ApplyColormap
++from core.settings import UserSettings
+
-+ return 0;
-+}
++try:
++ import matplotlib
++ matplotlib.use('WXAgg')
++ from matplotlib.figure import Figure
++ from matplotlib.backends.backend_wxagg import \
++ FigureCanvasWxAgg as FigCanvas
++ from matplotlib.lines import Line2D
++ from matplotlib.artist import Artist
++ from matplotlib.mlab import dist_point_to_segment
++ from matplotlib.patches import Polygon, Ellipse, Rectangle
++ import matplotlib.image as mi
++ import matplotlib.colors as mcolors
++ import matplotlib.cbook as cbook
++except ImportError as e:
++ raise ImportError(_("Unable to import matplotlib (try to install it).\n%s") % e)
+
-+int I_sc_set_value(struct scCats * cats, int cat_id, int scatt_id, int value_idx, int value)
-+{
-+ int n_a_scatts = cats->cats_arr[cat_id]->n_a_scatts;
-+ int cat_idx, scatt_idx, ret;
++import grass.script as grass
++from grass.pydispatch.signal import Signal
+
-+ cat_idx = cats->cats_idxs[cat_id];
-+ if(cat_idx < 0)
-+ return -1;
-+
-+ if(cats->cats_arr[cat_idx]->scatt_idxs[scatt_id] < 0)
-+ return -1;
++class ScatterPlotWidget(wx.Panel):
++ def __init__(self, parent, scatt_id, scatt_mgr, transpose,
++ id = wx.ID_ANY):
+
-+ cat_idx = cats->cats_idxs[cat_id];
-+ scatt_idx = cats->cats_arr[cat_idx]->scatt_idxs[scatt_id];
++ wx.Panel.__init__(self, parent, id)
+
-+ I_scd_set_value(cats->cats_arr[cat_idx]->scatts_arr[scatt_idx], value_idx, value);
++ self.parent = parent
++ self.full_extend = None
++ self.mode = None
+
-+ return 0;
-+}
-+#endif
++ self._createWidgets()
++ self._doLayout()
++ self.scatt_id = scatt_id
++ self.scatt_mgr = scatt_mgr
+
-+/*!
-+ \brief Insert scatter plot data.
-+
-+ \param scatt_data pointer to existing struct scdScattData
-+ \param type SC_SCATT_DATA for scatter plots or SC_SCATT_CONDITIONS for selected areas in scatter plot
-+ \param n_vals number of data values
-+ \param data array of values (unsigned char for SC_SCATT_CONDITIONS, unsigned int for SC_SCATT_DATA)
-+ */
-+void I_scd_init_scatt_data(struct scdScattData * scatt_data, int type, int n_vals, void * data)
-+{
-+ scatt_data->n_vals = n_vals;
++ self.cidpress = None
++ self.cidrelease = None
+
-+ if(type == SC_SCATT_DATA)
-+ {
-+ if(data)
-+ scatt_data->scatt_vals_arr = (unsigned int *) data;
-+ else {
-+ scatt_data->scatt_vals_arr = (unsigned int *) G_malloc(n_vals * sizeof(unsigned int));
-+ memset(scatt_data->scatt_vals_arr, 0, n_vals * sizeof(unsigned int));
-+ }
-+ scatt_data->b_conds_arr = NULL;
-+ }
-+ else if(type == SC_SCATT_CONDITIONS)
-+ {
-+ if(data)
-+ scatt_data->b_conds_arr = (unsigned char *) data;
-+ else {
-+ scatt_data->b_conds_arr = (unsigned char *) G_malloc(n_vals * sizeof(unsigned char));
-+ memset(scatt_data->b_conds_arr, 0, n_vals * sizeof(unsigned char));
-+ }
-+ scatt_data->scatt_vals_arr = NULL;
-+ }
++ self.transpose = transpose
+
-+ return;
-+}
++ self.inverse = False
+
++ self.SetSize((200, 100))
++ self.Layout()
+
-+#if 0
-+void I_scd_get_range_min_max(struct scdScattData * scatt_data, CELL * band_1_min, CELL * band_1_max, CELL * band_2_min, CELL * band_2_max)
-+{
++ self.base_scale = 1.2
++ self.Bind(wx.EVT_CLOSE,lambda event : self.CleanUp())
+
-+ Rast_get_range_min_max(&(scatt_data->band_1_range), band_1_min, band_2_min);
-+ Rast_get_range_min_max(&(scatt_data->band_2_range), band_2_min, band_2_max);
++ self.plotClosed = Signal("ScatterPlotWidget.plotClosed")
++ self.cursorMove = Signal("ScatterPlotWidget.cursorMove")
+
-+ return;
-+}
-+s
-+void * I_scd_get_data_ptr(struct scdScattData * scatt_data)
-+{
-+ if(!scatt_data->b_conds_arr)
-+ return scatt_data->b_conds_arr;
-+ else if(!scatt_data->scatt_vals_arr)
-+ return scatt_data->scatt_vals_arr;
++ self.contex_menu = ScatterPlotContextMenu(plot = self)
+
-+ return NULL;
-+}
++ self.ciddscroll = None
+
-+int I_scd_set_value(struct scdScattData * scatt_data, unsigned int val_idx, unsigned int val)
-+{
-+ if(val_idx < 0 && val_idx > scatt_data->n_vals)
-+ return -1;
++ self.canvas.mpl_connect('motion_notify_event', self.Motion)
++ self.canvas.mpl_connect('button_press_event', self.OnPress)
++ self.canvas.mpl_connect('button_release_event', self.OnRelease)
++ self.canvas.mpl_connect('draw_event', self.draw_callback)
+
-+ if(scatt_data->b_conds_arr)
-+ scatt_data->b_conds_arr[val_idx] = val;
-+ else if(scatt_data->scatt_vals_arr)
-+ scatt_data->scatt_vals_arr[val_idx] = val;
-+ else
-+ return -1;
++ def draw_callback(self, event):
++ self.polygon_drawer.draw_callback(event)
++ self.axes.draw_artist(self.zoom_rect)
+
-+ return 0;
-+}
-+#endif
-Index: lib/imagery/scatt.c
-===================================================================
---- lib/imagery/scatt.c (revision 0)
-+++ lib/imagery/scatt.c (working copy)
-@@ -0,0 +1,799 @@
-+/*!
-+ \file lib/imagery/scatt.c
++ def _createWidgets(self):
+
-+ \brief Imagery library - functions for wx Scatter Plot Tool.
++ # Create the mpl Figure and FigCanvas objects.
++ # 5x4 inches, 100 dots-per-inch
++ #
++ self.dpi = 100
++ self.fig = Figure((1.0, 1.0), dpi=self.dpi)
++ self.fig.autolayout = True
+
-+ Low level functions used by wx Scatter Plot Tool.
++ self.canvas = FigCanvas(self, -1, self.fig)
++
++ self.axes = self.fig.add_axes([0.0,0.0,1,1])
+
-+ Copyright (C) 2013 by the GRASS Development Team
++ pol = Polygon(list(zip([0], [0])), animated=True)
++ self.axes.add_patch(pol)
++ self.polygon_drawer = PolygonDrawer(self.axes, pol = pol, empty_pol = True)
+
-+ This program is free software under the GNU General Public License
-+ (>=v2). Read the file COPYING that comes with GRASS for details.
++ self.zoom_wheel_coords = None
++ self.zoom_rect_coords = None
++ self.zoom_rect = Polygon(list(zip([0], [0])), facecolor = 'none')
++ self.zoom_rect.set_visible(False)
++ self.axes.add_patch(self.zoom_rect)
+
-+ \author Stepan Turek <stepan.turek at seznam.cz> (Mentor: Martin Landa)
-+ */
++ def ZoomToExtend(self):
++ if self.full_extend:
++ self.axes.axis(self.full_extend)
++ self.canvas.draw()
+
-+#include <grass/raster.h>
-+#include <grass/imagery.h>
-+#include <grass/gis.h>
-+#include <grass/glocale.h>
++ def SetMode(self, mode):
++ self._deactivateMode()
++ if mode == 'zoom':
++ self.ciddscroll = self.canvas.mpl_connect('scroll_event', self.ZoomWheel)
++ self.mode = 'zoom'
++ elif mode == 'zoom_extend':
++ self.mode = 'zoom_extend'
++ elif mode == 'pan':
++ self.mode = 'pan'
++ elif mode:
++ self.polygon_drawer.SetMode(mode)
+
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <math.h>
-+#include <string.h>
++ def SetSelectionPolygonMode(self, activate):
++ self.polygon_drawer.SetSelectionPolygonMode(activate)
+
++ def _deactivateMode(self):
++ self.mode = None
++ self.polygon_drawer.SetMode(None)
+
-+struct rast_row
-+{
-+ CELL * row;
-+ char * null_row;
-+ struct Range rast_range; /*Range of whole raster.*/
-+};
++ if self.ciddscroll:
++ self.canvas.mpl_disconnect(self.ciddscroll)
+
-+/*!
-+ \brief Create pgm header.
++ self.zoom_rect.set_visible(False)
++ self._stopCategoryEdit()
+
-+ Scatter plot internally generates pgm files. These pgms have header in format created by this function.
++ def GetCoords(self):
++
++ coords = self.polygon_drawer.GetCoords()
++ if coords is None:
++ return
++
++ if self.transpose:
++ for c in coords:
++ tmp = c[0]
++ c[0] = c[1]
++ c[1] = tmp
++
++ return coords
++
++ def SetEmpty(self):
++ return self.polygon_drawer.SetEmpty()
++
++ def OnRelease(self, event):
++ if not self.mode == "zoom": return
++ self.zoom_rect.set_visible(False)
++ self.ZoomRectangle(event)
++ self.canvas.draw()
+
-+ \param region region to be pgm header generated for
-+ \param [out] header header of pgm file
-+ */
-+static int get_cat_rast_header(struct Cell_head * region, char * header){
-+ return sprintf(header, "P5\n%d\n%d\n1\n", region->cols, region->rows);
-+}
++ def OnPress(self, event):
++ 'on button press we will see if the mouse is over us and store some data'
++ if not event.inaxes:
++ return
++ if self.mode == "zoom_extend":
++ self.ZoomToExtend()
+
-+/*!
-+ \brief Create category raster conditions file.
++ if event.xdata and event.ydata:
++ self.zoom_wheel_coords = { 'x' : event.xdata, 'y' : event.ydata}
++ self.zoom_rect_coords = { 'x' : event.xdata, 'y' : event.ydata}
++ else:
++ self.zoom_wheel_coords = None
++ self.zoom_rect_coords = None
++
++ def _stopCategoryEdit(self):
++ 'disconnect all the stored connection ids'
++
++ if self.cidpress:
++ self.canvas.mpl_disconnect(self.cidpress)
++ if self.cidrelease:
++ self.canvas.mpl_disconnect(self.cidrelease)
++ #self.canvas.mpl_disconnect(self.cidmotion)
++
++ def _doLayout(self):
++
++ self.main_sizer = wx.BoxSizer(wx.VERTICAL)
++ self.main_sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
++ self.SetSizer(self.main_sizer)
++ self.main_sizer.Fit(self)
+
-+ \param cat_rast_region region to be file generated for
-+ \param cat_rast path of generated category raster file
-+ */
-+int I_create_cat_rast(struct Cell_head * cat_rast_region, const char * cat_rast)
-+{
-+ FILE * f_cat_rast;
-+ char cat_rast_header[1024];//TODO magic number
-+ int i_row, i_col;
-+ int head_nchars;
++ def Plot(self, cats_order, scatts, ellipses, styles):
++ """ Redraws the figure
++ """
+
-+ unsigned char * row_data;
++ callafter_list = []
+
-+ f_cat_rast = fopen(cat_rast, "wb");
-+ if(!f_cat_rast) {
-+ G_warning("Unable to create category raster condition file <%s>.", cat_rast);
-+ return -1;
-+ }
++ if self.full_extend:
++ cx = self.axes.get_xlim()
++ cy = self.axes.get_ylim()
++ c = cx + cy
++ else:
++ c = None
+
-+ head_nchars = get_cat_rast_header(cat_rast_region, cat_rast_header);
++ #q = Queue()
++ #p = Process(target=MergeImg, args=(cats_order, scatts, styles, self.transpose, q))
++ #p.start()
++ #merged_img, self.full_extend = q.get()
++ #p.join()
++
++ merged_img, self.full_extend = MergeImg(cats_order, scatts, styles, None)
++ self.axes.clear()
++ self.axes.axis('equal')
+
-+ fwrite(cat_rast_header, sizeof(char), head_nchars/sizeof(char), f_cat_rast);
-+ if (ferror(f_cat_rast)){
-+ fclose(f_cat_rast);
-+ G_warning(_("Unable to write header into category raster condition file <%s>."), cat_rast);
-+ return -1;
-+ }
++ if self.transpose:
++ merged_img = np.transpose(merged_img, (1, 0, 2))
+
-+ row_data = (unsigned char *) G_malloc(cat_rast_region->cols * sizeof(unsigned char));
-+ for(i_col = 0; i_col < cat_rast_region->cols; i_col++)
-+ row_data[i_col] = 0 & 255;
++ img = imshow(self.axes, merged_img,
++ extent= [int(ceil(x)) for x in self.full_extend],
++ origin='lower',
++ interpolation='nearest',
++ aspect="equal")
+
-+ for(i_row = 0; i_row < cat_rast_region->rows; i_row++) {
-+ fwrite(row_data, sizeof(unsigned char), (cat_rast_region->cols)/sizeof(unsigned char), f_cat_rast);
-+ if (ferror(f_cat_rast))
-+ {
-+ fclose(f_cat_rast);
-+ G_warning(_("Unable to write into category raster condition file <%s>."), cat_rast);
-+ return -1;
-+ }
-+ }
++ callafter_list.append([self.axes.draw_artist, [img]])
++ callafter_list.append([grass.try_remove, [merged_img.filename]])
+
-+ fclose(f_cat_rast);
-+ return 0;
-+}
++ for cat_id in cats_order:
++ if cat_id == 0:
++ continue
++ if not ellipses.has_key(cat_id):
++ continue
++
++ e = ellipses[cat_id]
++ if not e:
++ continue
+
-+static int print_reg(struct Cell_head * intersec, const char * pref, int dbg_level)
-+{
-+ G_debug(dbg_level, "%s:\n n:%f\ns:%f\ne:%f\nw:%f\nns_res:%f\new_res:%f", pref, intersec->north, intersec->south,
-+ intersec->east, intersec->west, intersec->ns_res, intersec->ew_res);
-+}
++ colors = styles[cat_id]['color'].split(":")
++ if self.transpose:
++ e['theta'] = 360 - e['theta'] + 90
++ if e['theta'] >= 360:
++ e['theta'] = abs(360 - e['theta'])
++
++ e['pos'] = [e['pos'][1], e['pos'][0]]
+
-+/*!
-+ \brief Find intersection region of two regions.
++ ellip = Ellipse(xy=e['pos'],
++ width=e['width'],
++ height=e['height'],
++ angle=e['theta'],
++ edgecolor="w",
++ linewidth=1.5,
++ facecolor='None')
++ self.axes.add_artist(ellip)
++ callafter_list.append([self.axes.draw_artist, [ellip]])
+
-+ \param A pointer to intersected region
-+ \param B pointer to intersected region
-+ \param [out] intersec pointer to intersection region of regions A B (relevant params of the region are: south, north, east, west)
++ color = map(lambda v : int(v)/255.0, styles[cat_id]['color'].split(":"))
+
-+ \return 0 if interection exists
-+ \return -1 if regions does not intersect
-+ */
-+static int regions_intersecion(struct Cell_head * A, struct Cell_head * B, struct Cell_head * intersec)
-+{
++ ellip = Ellipse(xy=e['pos'],
++ width=e['width'],
++ height=e['height'],
++ angle=e['theta'],
++ edgecolor=color,
++ linewidth=1,
++ facecolor='None')
+
-+ if(B->north < A->south) return -1;
-+ else if(B->north > A->north) intersec->north = A->north;
-+ else intersec->north = B->north;
++ self.axes.add_artist(ellip)
++ callafter_list.append([self.axes.draw_artist, [ellip]])
++
++ center = Line2D([e['pos'][0]], [e['pos'][1]],
++ marker='x',
++ markeredgecolor='w',
++ #markerfacecolor=color,
++ markersize=2)
++ self.axes.add_artist(center)
++ callafter_list.append([self.axes.draw_artist, [center]])
+
-+ if(B->south > A->north) return -1;
-+ else if(B->south < A->south) intersec->south = A->south;
-+ else intersec->south = B->south;
++ callafter_list.append([self.fig.canvas.blit, []])
+
-+ if(B->east < A->west) return -1;
-+ else if(B->east > A->east) intersec->east = A->east;
-+ else intersec->east = B->east;
++ if c:
++ self.axes.axis(c)
++ wx.CallAfter(lambda : self.CallAfter(callafter_list))
++
++ def CallAfter(self, funcs_list):
++ while funcs_list:
++ fcn, args = funcs_list.pop(0)
++ fcn(*args)
+
-+ if(B->west > A->east) return -1;
-+ else if(B->west < A->west) intersec->west = A->west;
-+ else intersec->west = B->west;
++ self.canvas.draw()
+
-+ if(intersec->north == intersec->south) return -1;
++ def CleanUp(self):
++ self.plotClosed.emit(scatt_id = self.scatt_id)
++ self.Destroy()
+
-+ if(intersec->east == intersec->west) return -1;
++ def ZoomWheel(self, event):
++ # get the current x and y limits
++ if not event.inaxes:
++ return
++ # tcaswell
++ # http://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel
++ cur_xlim = self.axes.get_xlim()
++ cur_ylim = self.axes.get_ylim()
++
++ xdata = event.xdata
++ ydata = event.ydata
++ if event.button == 'up':
++ scale_factor = 1/self.base_scale
++ elif event.button == 'down':
++ scale_factor = self.base_scale
++ else:
++ scale_factor = 1
+
-+ return 0;
++ extend = (xdata - (xdata - cur_xlim[0]) * scale_factor,
++ xdata + (cur_xlim[1] - xdata) * scale_factor,
++ ydata - (ydata - cur_ylim[0]) * scale_factor,
++ ydata + (cur_ylim[1] - ydata) * scale_factor)
+
-+}
++ self.axes.axis(extend)
++
++ self.canvas.draw()
+
-+/*!
-+ \brief Get rows and cols numbers, which defines intersection of the regions.
-+
-+ \param A pointer to intersected region
-+ \param B pointer to intersected region (A and B must have same resolution)
-+ \param [out] A_bounds rows and cols numbers of A stored in south, north, east, west, which defines intersection of A and B
-+ \param [out] B_bounds rows and cols numbers of B stored in south, north, east, west, which defines intersection of A and B
++ def ZoomRectangle(self, event):
++ # get the current x and y limits
++ if not self.mode == "zoom": return
++ if event.inaxes is None: return
++ if event.button != 1: return
+
-+ \return 0 if interection exists
-+ \return -1 if regions do not intersect
-+ \return -2 resolution of regions is not same
-+*/
-+static int get_rows_and_cols_bounds(struct Cell_head * A, struct Cell_head * B, struct Cell_head * A_bounds, struct Cell_head * B_bounds)
-+{
-+ float ns_res, ew_res;
++ cur_xlim = self.axes.get_xlim()
++ cur_ylim = self.axes.get_ylim()
++
++ x1, y1 = event.xdata, event.ydata
++ x2 = deepcopy(self.zoom_rect_coords['x'])
++ y2 = deepcopy(self.zoom_rect_coords['y'])
+
-+ struct Cell_head intersec;
++ if x1 == x2 or y1 == y2:
++ return
+
-+ /* TODO is it right check? */
-+ if(abs(A->ns_res - B->ns_res) > GRASS_EPSILON) {
-+ G_debug(0, "'get_rows_and_cols_bounds' ns_res does not fit, A->ns_res: %f B->ns_res: %f", A->ns_res, B->ns_res);
-+ return -2;
-+ }
++ self.axes.axis((x1, x2, y1, y2))
++ #self.axes.set_xlim(x1, x2)#, auto = True)
++ #self.axes.set_ylim(y1, y2)#, auto = True)
++ self.canvas.draw()
+
-+ if(abs(A->ew_res - B->ew_res) > GRASS_EPSILON) {
-+ G_debug(0, "'get_rows_and_cols_bounds' ew_res does not fit, A->ew_res: %f B->ew_res: %f", A->ew_res, B->ew_res);
-+ return -2;
-+ }
++ def Motion(self, event):
++ self.PanMotion(event)
++ self.ZoomRectMotion(event)
++
++ if event.inaxes is None:
++ return
++
++ self.cursorMove.emit(x=event.xdata, y=event.ydata)
+
-+ ns_res = A->ns_res;
-+ ew_res = A->ew_res;
++ def PanMotion(self, event):
++ 'on mouse movement'
++ if not self.mode == "pan":
++ return
++ if event.inaxes is None:
++ return
++ if event.button != 1:
++ return
+
-+ if(regions_intersecion(A, B, &intersec) == -1)
-+ return -1;
++ cur_xlim = self.axes.get_xlim()
++ cur_ylim = self.axes.get_ylim()
+
-+ A_bounds->north = ceil((A->north - intersec.north - ns_res * 0.5) / ns_res);
-+ A_bounds->south = ceil((A->north - intersec.south - ns_res * 0.5) / ns_res);
++ x,y = event.xdata, event.ydata
++
++ mx = (x - self.zoom_wheel_coords['x']) * 0.6
++ my = (y - self.zoom_wheel_coords['y']) * 0.6
+
-+ A_bounds->east = ceil((intersec.east - A->west - ew_res * 0.5) / ew_res);
-+ A_bounds->west = ceil((intersec.west - A->west - ew_res * 0.5) / ew_res);
++ extend = (cur_xlim[0] - mx, cur_xlim[1] - mx, cur_ylim[0] - my, cur_ylim[1] - my)
+
-+ B_bounds->north = ceil((B->north - intersec.north - ns_res * 0.5) / ns_res);
-+ B_bounds->south = ceil((B->north - intersec.south - ns_res * 0.5) / ns_res);
++ self.zoom_wheel_coords['x'] = x
++ self.zoom_wheel_coords['y'] = y
+
-+ B_bounds->east = ceil((intersec.east - B->west - ew_res * 0.5) / ew_res);
-+ B_bounds->west = ceil((intersec.west - B->west - ew_res * 0.5) / ew_res);
++ self.axes.axis(extend)
+
-+ return 0;
-+}
++ #self.canvas.copy_from_bbox(self.axes.bbox)
++ #self.canvas.restore_region(self.background)
++ self.canvas.draw()
++
++ def ZoomRectMotion(self, event):
++ if not self.mode == "zoom": return
++ if event.inaxes is None: return
++ if event.button != 1: return
+
-+/*!
-+ \brief Insert raster map patch into pgm file.
-+
-+ \param patch_rast name of raster map
-+ \param cat_rast_region region of category raster file
-+ \param cat_rast path to category raster file
++ x1, y1 = event.xdata, event.ydata
++ self.zoom_rect.set_visible(True)
++ x2 = self.zoom_rect_coords['x']
++ y2 = self.zoom_rect_coords['y']
+
-+ \return 0 on success
-+ \return -1 on failure
-+*/
-+int I_insert_patch_to_cat_rast(const char * patch_rast, struct Cell_head * cat_rast_region, const char * cat_rast)
-+{
++ self.zoom_rect.xy = ((x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1))
+
-+ FILE * f_cat_rast;
-+ struct Cell_head patch_region, patch_bounds, cat_rast_bounds;
-+ char cat_rast_header[1024];//TODO magic number
-+ int i_row, i_col, ncols, nrows, cat_rast_col, patch_col, val;
-+ int head_nchars, ret;
-+ int fd_patch_rast, init_shift, step_shift;
-+ unsigned char * patch_data;
++ #self.axes.draw_artist(self.zoom_rect)
++ self.canvas.draw()
+
-+ char * null_chunk_row;
++class ScatterPlotContextMenu:
++ def __init__(self, plot):
+
-+ const char *mapset;
++ self.plot = plot
++ self.canvas = plot.canvas
++ self.cidpress = self.canvas.mpl_connect(
++ 'button_press_event', self.ContexMenu)
++
++ def ContexMenu(self, event):
++ if not event.inaxes:
++ return
+
-+ struct Cell_head patch_lines, cat_rast_lines;
++ if event.button == 3:
++ menu = wx.Menu()
++ menu_items = [["zoom_to_extend", _("Zoom to scatter plot extend"), lambda event : self.plot.ZoomToExtend()]]
+
-+ unsigned char * row_data;
++ for item in menu_items:
++ item_id = wx.ID_ANY
++ menu.Append(item_id, text = item[1])
++ menu.Bind(wx.EVT_MENU, item[2], id = item_id)
+
-+ f_cat_rast = fopen(cat_rast, "rb+");
-+ if(!f_cat_rast) {
-+ G_warning(_("Unable to open category raster condtions file <%s>."), cat_rast);
-+ return -1;
-+ }
++ wx.CallAfter(self.ShowMenu, menu)
++
++ def ShowMenu(self, menu):
++ self.plot.PopupMenu(menu)
++ menu.Destroy()
++ self.plot.ReleaseMouse()
+
-+ head_nchars = get_cat_rast_header(cat_rast_region, cat_rast_header);
-+ if ((mapset = G_find_raster(patch_rast,"")) == NULL) {
-+ fclose(f_cat_rast);
-+ G_warning(_("Unable to find patch raster <%s>."), patch_rast);
-+ return -1;
-+ }
++class PolygonDrawer:
++ """
++ An polygon editor.
++ """
++ def __init__(self, ax, pol, empty_pol):
++ if pol.figure is None:
++ raise RuntimeError('You must first add the polygon to a figure or canvas before defining the interactor')
++ self.ax = ax
++ self.canvas = pol.figure.canvas
+
-+ Rast_get_cellhd(patch_rast, mapset, &patch_region);
-+ Rast_set_window(&patch_region);
-+
-+ if ((fd_patch_rast = Rast_open_old(patch_rast, mapset)) < 0) {
-+ fclose(f_cat_rast);
-+ return -1;
-+ }
++ self.showverts = True
+
-+ ret = get_rows_and_cols_bounds(cat_rast_region, &patch_region, &cat_rast_bounds, &patch_bounds);
-+ if(ret == -2) {
-+ G_warning(_("Resolutions of patch <%s> and patched file <%s> are not same."), patch_rast, cat_rast);
++ self.pol = pol
++ self.empty_pol = empty_pol
+
-+ Rast_close(fd_patch_rast);
-+ fclose(f_cat_rast);
++ x, y = zip(*self.pol.xy)
+
-+ return -1;
-+ }
-+ else if (ret == -1){
++ style = self._getPolygonStyle()
+
-+ Rast_close(fd_patch_rast);
-+ fclose(f_cat_rast);
++ self.line = Line2D(x, y, marker='o', markerfacecolor='r', animated=True)
++ self.ax.add_line(self.line)
++ #self._update_line(pol)
+
-+ return 0;
-+ }
++ cid = self.pol.add_callback(self.poly_changed)
++ self.moving_ver_idx = None # the active vert
+
-+ ncols = cat_rast_bounds.east - cat_rast_bounds.west;
-+ nrows = cat_rast_bounds.south - cat_rast_bounds.north;
++ self.mode = None
+
-+ patch_data = (unsigned char *) G_malloc(ncols * sizeof(unsigned char));
++ if self.empty_pol:
++ self._show(False)
+
-+ init_shift = head_nchars + cat_rast_region->cols * cat_rast_bounds.north + cat_rast_bounds.west;
++ #self.canvas.mpl_connect('draw_event', self.draw_callback)
++ self.canvas.mpl_connect('button_press_event', self.OnButtonPressed)
++ self.canvas.mpl_connect('button_release_event', self.button_release_callback)
++ self.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)
++
++ self.it = 0
+
-+ if(fseek(f_cat_rast, init_shift, SEEK_SET) != 0) {
-+ G_warning(_("Corrupted category raster conditions file <%s> (fseek failed)"), cat_rast);
++ def _getPolygonStyle(self):
++ style = {}
++ style['sel_pol'] = UserSettings.Get(group='scatt',
++ key='selection',
++ subkey='sel_pol')
++ style['sel_pol_vertex'] = UserSettings.Get(group='scatt',
++ key='selection',
++ subkey='sel_pol_vertex')
+
-+ Rast_close(fd_patch_rast);
-+ G_free(null_chunk_row);
-+ fclose(f_cat_rast);
++ style['sel_pol'] = [i / 255.0 for i in style['sel_pol']]
++ style['sel_pol_vertex'] = [i / 255.0 for i in style['sel_pol_vertex']]
+
-+ return -1;
-+ }
++ return style
+
-+ step_shift = cat_rast_region->cols - ncols;
++ def _getSnapTresh(self):
++ return UserSettings.Get(group='scatt',
++ key='selection',
++ subkey='snap_tresh')
+
-+ null_chunk_row = Rast_allocate_null_buf();
-+
-+ for(i_row = 0; i_row < nrows; i_row++) {
-+ Rast_get_null_value_row (fd_patch_rast, null_chunk_row, i_row + patch_bounds.north);
++ def SetMode(self, mode):
++ self.mode = mode
+
-+ for(i_col = 0; i_col < ncols; i_col++) {
-+ patch_col = patch_bounds.west + i_col;
++ def SetSelectionPolygonMode(self, activate):
++
++ self.Show(activate)
++ if not activate and self.mode:
++ self.SetMode(None)
+
-+ if(null_chunk_row[patch_col] != 1)
-+ patch_data[i_col] = 1 & 255;
-+ else {
-+ patch_data[i_col] = 0 & 255;
-+ }
-+ }
++ def Show(self, show):
++ if show:
++ if not self.empty_pol:
++ self._show(True)
++ else:
++ self._show(False)
+
-+ fwrite(patch_data, sizeof(unsigned char), (ncols)/sizeof(unsigned char), f_cat_rast);
-+ if (ferror(f_cat_rast))
-+ {
-+ G_warning(_("Unable to write into category raster conditions file <%s>"), cat_rast);
-+
-+ Rast_close(fd_patch_rast);
-+ G_free(null_chunk_row);
-+ fclose(f_cat_rast);
++ def GetCoords(self):
++ if self.empty_pol:
++ return None
+
-+ return -1;
-+ }
-+ if(fseek(f_cat_rast, step_shift, SEEK_CUR) != 0) {
-+ G_warning(_("Corrupted category raster conditions file <%s> (fseek failed)"), cat_rast);
-+
-+ Rast_close(fd_patch_rast);
-+ G_free(null_chunk_row);
-+ fclose(f_cat_rast);
-+
-+ return -1;
-+ }
-+ }
++ coords = deepcopy(self.pol.xy)
++ return coords
+
-+ Rast_close(fd_patch_rast);
-+ G_free(null_chunk_row);
-+ fclose(f_cat_rast);
-+ return 0;
-+}
++ def SetEmpty(self):
++ self._setEmptyPol(True)
+
-+/*!
-+ \brief Updates scatter plots data in category by pixels which meets category conditions.
-+
-+ \param bands_rows data represents data describig one row from raster band
-+ \param belongs_pix array which defines which pixels belongs to category (1 value) and which not (0 value)
-+ \param [out] scatts pointer to scScatts struct of type SC_SCATT_DATA, which are modified according to values in belongs_pix
-+*/
-+static inline void update_cat_scatt_plts(struct rast_row * bands_rows, unsigned short * belongs_pix, struct scScatts * scatts)
-+{
-+ int band_axis_1, band_axis_2, i_scatt, array_idx, cat_idx, i_chunk_rows_pix, max_arr_idx;
++ def _setEmptyPol(self, empty_pol):
++ self.empty_pol = empty_pol
++ self._show(not empty_pol)
+
-+ CELL * b_1_row;
-+ CELL * b_2_row;
-+ char * b_1_null_row,* b_2_null_row;
-+ struct rast_row b_1_rast_row, b_2_rast_row;
++ def _show(self, show):
+
-+ struct Range b_1_range, b_2_range;
-+ int b_1_range_size;
++ self.show = show
+
-+ int row_size = Rast_window_cols();
++ self.line.set_visible(self.show)
++ self.pol.set_visible(self.show)
+
-+ int * scatts_bands = scatts->scatts_bands;
++ self.Redraw()
+
-+ for(i_scatt = 0; i_scatt < scatts->n_a_scatts; i_scatt++)
-+ {
-+ b_1_rast_row = bands_rows[scatts_bands[i_scatt * 2]];
-+ b_2_rast_row = bands_rows[scatts_bands[i_scatt * 2 + 1]];
++ def Redraw(self):
++ if self.show:
++ self.ax.draw_artist(self.pol)
++ self.ax.draw_artist(self.line)
++ self.canvas.blit(self.ax.bbox)
++ self.canvas.draw()
+
-+ b_1_row = b_1_rast_row.row;
-+ b_2_row = b_2_rast_row.row;
++ def draw_callback(self, event):
+
-+ b_1_null_row = b_1_rast_row.null_row;
-+ b_2_null_row = b_2_rast_row.null_row;
++ style=self._getPolygonStyle()
++ self.pol.set_facecolor(style['sel_pol'])
++ self.line.set_markerfacecolor(style['sel_pol_vertex'])
+
-+ b_1_range = b_1_rast_row.rast_range;
-+ b_2_range = b_2_rast_row.rast_range;
-+
-+ b_1_range_size = b_1_range.max - b_1_range.min + 1;
-+ max_arr_idx = (b_1_range.max - b_1_range.min + 1) * (b_2_range.max - b_2_range.min + 1);
++ self.background = self.canvas.copy_from_bbox(self.ax.bbox)
++ self.ax.draw_artist(self.pol)
++ self.ax.draw_artist(self.line)
++
++ def poly_changed(self, pol):
++ 'this method is called whenever the polygon object is called'
++ # only copy the artist props to the line (except visibility)
++ vis = self.line.get_visible()
++ Artist.update_from(self.line, pol)
++ self.line.set_visible(vis) # don't use the pol visibility state
+
-+ for(i_chunk_rows_pix = 0; i_chunk_rows_pix < row_size; i_chunk_rows_pix++)
-+ {
-+ /* pixel does not belongs to scatter plot or has null value in one of the bands */
-+ if(!belongs_pix[i_chunk_rows_pix] ||
-+ b_1_null_row[i_chunk_rows_pix] == 1 ||
-+ b_2_null_row[i_chunk_rows_pix] == 1)
-+ continue;
++ def get_ind_under_point(self, event):
++ 'get the index of the vertex under point if within treshold'
+
-+ /* index in scatter plot array */
-+ array_idx = b_1_row[i_chunk_rows_pix] - b_1_range.min + (b_2_row[i_chunk_rows_pix] - b_2_range.min) * b_1_range_size;
++ # display coords
++ xy = np.asarray(self.pol.xy)
++ xyt = self.pol.get_transform().transform(xy)
++ xt, yt = xyt[:, 0], xyt[:, 1]
++ d = np.sqrt((xt-event.x)**2 + (yt-event.y)**2)
++ indseq = np.nonzero(np.equal(d, np.amin(d)))[0]
++ ind = indseq[0]
+
-+ if(array_idx < 0 || array_idx >= max_arr_idx) {
-+ G_warning ("Data inconsistent. Value computed for scatter plot is out of initialized range.");
-+ continue;
-+ }
++ if d[ind]>=self._getSnapTresh():
++ ind = None
+
-+ /* increment scatter plot value */
-+ ++scatts->scatts_arr[i_scatt]->scatt_vals_arr[array_idx];
-+ }
-+ }
-+}
++ return ind
+
-+/*!
-+ \brief Computes scatter plots data from chunk_rows.
++ def OnButtonPressed(self, event):
++ if not event.inaxes:
++ return
+
-+ \param scatts pointer to scScatts struct of type SC_SCATT_DATA, where are computed scatter plots stored
-+ \param scatt_conds pointer to scScatts struct of type SC_SCATT_CONDITIONS, where are selected areas (condtitions)stored
++ if event.button in [2, 3]:
++ return
+
-+ \param chunk_rows data arrays of chunk_rows from analyzed raster bands (all data in chunk_rows, null_chunk_rows and belongs_pix arrays represents same region)
-+ \param null_chunk_rows null data arrays of chunk_rows from analyzed raster bands
-+ \param row_size size of data in chunk_rows, null_chunk_rows and belongs_pix arrays
-+ \param f_cats_rasts_conds file which stores selected areas (conditions) from mapwindow see I_create_cat_rast() and I_pa
-+ \param fd_cats_rasts array of openedraster maps which represents all selected pixels for category
-+ \param region analysis region
++ if self.mode == "delete_vertex":
++ self._deleteVertex(event)
++ elif self.mode == "add_boundary_vertex":
++ self._addVertexOnBoundary(event)
++ elif self.mode == "add_vertex":
++ self._addVertex(event)
++ elif self.mode == "remove_polygon":
++ self.SetEmpty()
++ self.moving_ver_idx = self.get_ind_under_point(event)
+
-+ \return 0 on success
-+ \return -1 on failure
-+*/
-+static inline int compute_scatts_from_chunk_row(struct Cell_head *region, struct scCats * scatt_conds,
-+ FILE ** f_cats_rasts_conds, struct rast_row * bands_rows,
-+ struct scCats * scatts, int * fd_cats_rasts)
-+{
++ def button_release_callback(self, event):
++ 'whenever a mouse button is released'
++ if not self.showverts: return
++ if event.button != 1: return
++ self.moving_ver_idx = None
+
-+ int i_rows_pix, i_cat, i_scatt, n_a_scatts, n_pixs;
-+ int cat_id, scatt_plts_cat_idx, array_idx, max_arr_idx;
-+ char * b_1_null_row,* b_2_null_row;
-+ struct rast_row b_1_rast_row, b_2_rast_row;
-+ CELL * cat_rast_row;
++ def ShowVertices(self, show):
++ self.showverts = show
++ self.line.set_visible(self.showverts)
++ if not self.showverts: self.moving_ver_idx = None
+
-+ struct scScatts * scatts_conds;
-+ struct scScatts * scatts_scatt_plts;
-+ struct scdScattData * conds;
++ def _deleteVertex(self, event):
++ ind = self.get_ind_under_point(event)
+
-+ struct Range b_1_range, b_2_range;
-+ int b_1_range_size;
++ if ind is None or self.empty_pol:
++ return
+
-+ int * scatts_bands;
-+ struct scdScattData ** scatts_arr;
++ if len(self.pol.xy) <= 2:
++ self.empty_pol = True
++ self._show(False)
++ return
+
-+ CELL * b_1_row;
-+ CELL * b_2_row;
-+ unsigned char * i_scatt_conds;
++ coords = []
++ for i,tup in enumerate(self.pol.xy):
++ if i == ind:
++ continue
++ elif i == 0 and ind == len(self.pol.xy) - 1:
++ continue
++ elif i == len(self.pol.xy) - 1 and ind == 0:
++ continue
+
-+ int row_size = Rast_window_cols();
++ coords.append(tup)
+
-+ unsigned short * belongs_pix = (unsigned short *) G_malloc(row_size * sizeof(unsigned short));
-+ unsigned char * rast_pixs = (unsigned char *) G_malloc(row_size * sizeof(unsigned char));
-+ cat_rast_row = Rast_allocate_c_buf();
++ self.pol.xy = coords
++ self.line.set_data(zip(*self.pol.xy))
+
-+
-+ for(i_cat = 0; i_cat < scatt_conds->n_a_cats; i_cat++)
-+ {
-+ scatts_conds = scatt_conds->cats_arr[i_cat];
++ self.Redraw()
+
-+ cat_id = scatt_conds->cats_ids[i_cat];
++ def _addVertexOnBoundary(self, event):
++ if self.empty_pol:
++ return
+
-+ scatt_plts_cat_idx = scatts->cats_idxs[cat_id];
-+ if(scatt_plts_cat_idx < 0)
-+ continue;
-+ scatts_scatt_plts = scatts->cats_arr[scatt_plts_cat_idx];
++ xys = self.pol.get_transform().transform(self.pol.xy)
++ p = event.x, event.y # display coords
++ for i in range(len(xys)-1):
++ s0 = xys[i]
++ s1 = xys[i+1]
++ d = dist_point_to_segment(p, s0, s1)
+
-+ G_zero(belongs_pix, row_size * sizeof(unsigned short));
++ if d<=self._getSnapTresh():
++ self.pol.xy = np.array(
++ list(self.pol.xy[:i + 1]) +
++ [(event.xdata, event.ydata)] +
++ list(self.pol.xy[i + 1:]))
++ self.line.set_data(zip(*self.pol.xy))
++ break
+
-+ /* if category has no conditions defined, scatter plots without any constraint are computed (default scatter plots) */
-+ if(!scatts_conds->n_a_scatts && !f_cats_rasts_conds[i_cat]) {
-+ for(i_scatt = 0; i_scatt < scatts_scatt_plts->n_a_scatts; i_scatt++)
-+ {
-+ /* all pixels belongs */
-+ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
-+ belongs_pix[i_rows_pix] = 1;
-+ }
-+ }
-+ /* compute belonging pixels for defined conditions */
-+ else
-+ {
-+ scatts_bands = scatts_conds->scatts_bands;
++ self.Redraw()
+
-+ /* check conditions from category raster condtitions file */
-+ if(f_cats_rasts_conds[i_cat]) {
-+ n_pixs = fread(rast_pixs, sizeof(unsigned char), (row_size)/sizeof(unsigned char), f_cats_rasts_conds[i_cat]);
++ def _addVertex(self, event):
+
-+ if (ferror(f_cats_rasts_conds[i_cat]))
-+ {
-+ G_free(rast_pixs);
-+ G_free(belongs_pix);
-+ G_warning(_("Unable to read from category raster condtition file."));
-+ return -1;
-+ }
-+ if (n_pixs != n_pixs) {
-+ G_free(rast_pixs);
-+ G_free(belongs_pix);
-+ G_warning(_("Invalid size of category raster conditions file."));
-+ return -1;
++ if self.empty_pol:
++ pt = (event.xdata, event.ydata)
++ self.pol.xy = np.array([pt, pt])
++ self._show(True)
++ self.empty_pol = False
++ else:
++ self.pol.xy = np.array(
++ [(event.xdata, event.ydata)] +
++ list(self.pol.xy[1:]) +
++ [(event.xdata, event.ydata)])
+
-+ }
++ self.line.set_data(zip(*self.pol.xy))
++
++ self.Redraw()
+
-+ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
-+ {
-+ if(rast_pixs[i_rows_pix] != 0 & 255)
-+ belongs_pix[i_rows_pix] = 1;
-+ }
-+ }
++ def motion_notify_callback(self, event):
++ 'on mouse movement'
++ if not self.mode == "move_vertex": return
++ if not self.showverts: return
++ if self.empty_pol: return
++ if self.moving_ver_idx is None: return
++ if event.inaxes is None: return
++ if event.button != 1: return
+
-+ /* check condtions defined in scatter plots*/
-+ for(i_scatt = 0; i_scatt < scatts_conds->n_a_scatts; i_scatt++)
-+ {
-+ b_1_rast_row = bands_rows[scatts_bands[i_scatt * 2]];
-+ b_2_rast_row = bands_rows[scatts_bands[i_scatt * 2 + 1]];
++ self.it += 1
+
-+ b_1_row = b_1_rast_row.row;
-+ b_2_row = b_2_rast_row.row;
++ x,y = event.xdata, event.ydata
+
-+ b_1_null_row = b_1_rast_row.null_row;
-+ b_2_null_row = b_2_rast_row.null_row;
++ self.pol.xy[self.moving_ver_idx] = x,y
++ if self.moving_ver_idx == 0:
++ self.pol.xy[len(self.pol.xy) - 1] = x,y
++ elif self.moving_ver_idx == len(self.pol.xy) - 1:
++ self.pol.xy[0] = x,y
+
-+ b_1_range = b_1_rast_row.rast_range;
-+ b_2_range = b_2_rast_row.rast_range;
++ self.line.set_data(zip(*self.pol.xy))
+
-+ b_1_range_size = b_1_range.max - b_1_range.min + 1;
-+ max_arr_idx = (b_1_range.max - b_1_range.min + 1) * (b_2_range.max - b_2_range.min + 1);
++ self.canvas.restore_region(self.background)
+
-+ i_scatt_conds = scatts_conds->scatts_arr[i_scatt]->b_conds_arr;
++ self.Redraw()
+
-+ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
-+ {
-+ /* pixels already belongs to category from category raster conditions file or contains null value in one of the bands */
-+ if(belongs_pix[i_rows_pix] ||
-+ b_1_null_row[i_rows_pix] == 1 ||
-+ b_2_null_row[i_rows_pix] == 1)
-+ continue;
++class ModestImage(mi.AxesImage):
++ """
++ Computationally modest image class.
+
-+ array_idx = b_1_row[i_rows_pix] - b_1_range.min + (b_2_row[i_rows_pix] - b_2_range.min) * b_1_range_size;
-+ if(array_idx < 0 || array_idx >= max_arr_idx) {
-+ G_warning ("Data inconsistent. Value computed for scatter plot is out of initialized range.");
-+ continue;
-+ }
-+ /* pixels meets condtion defined in scatter plot */
-+ if(i_scatt_conds[array_idx])
-+ belongs_pix[i_rows_pix] = 1;
-+ }
-+ }
-+ }
++ ModestImage is an extension of the Matplotlib AxesImage class
++ better suited for the interactive display of larger images. Before
++ drawing, ModestImage resamples the data array based on the screen
++ resolution and view window. This has very little affect on the
++ appearance of the image, but can substantially cut down on
++ computation since calculations of unresolved or clipped pixels
++ are skipped.
+
-+ /* update category raster with belonging pixels */
-+ if(fd_cats_rasts[i_cat] >= 0) {
-+ Rast_set_null_value(cat_rast_row, Rast_window_cols(), CELL_TYPE);
++ The interface of ModestImage is the same as AxesImage. However, it
++ does not currently support setting the 'extent' property. There
++ may also be weird coordinate warping operations for images that
++ I'm not aware of. Don't expect those to work either.
+
-+ for(i_rows_pix = 0; i_rows_pix < row_size; i_rows_pix++)
-+ if(belongs_pix[i_rows_pix])
-+ cat_rast_row[i_rows_pix] = belongs_pix[i_rows_pix];
++ Author: Chris Beaumont <beaumont at hawaii.edu>
++ """
++ def __init__(self, minx=0.0, miny=0.0, *args, **kwargs):
++ if 'extent' in kwargs and kwargs['extent'] is not None:
++ raise NotImplementedError("ModestImage does not support extents")
+
-+ Rast_put_c_row (fd_cats_rasts[i_cat], cat_rast_row);
-+ }
++ self._full_res = None
++ self._sx, self._sy = None, None
++ self._bounds = (None, None, None, None)
++ self.minx = minx
++ self.miny = miny
+
-+ /* update scatter plots with belonging pixels */
-+ update_cat_scatt_plts(bands_rows, belongs_pix, scatts_scatt_plts);
-+ }
++ super(ModestImage, self).__init__(*args, **kwargs)
+
-+ G_free(cat_rast_row);
-+ G_free(rast_pixs);
-+ G_free(belongs_pix);
++ def set_data(self, A):
++ """
++ Set the image array
+
-+ return 0;
-+}
++ ACCEPTS: numpy/PIL Image A
++ """
++ self._full_res = A
++ self._A = A
+
-+/*!
-+ \brief Get list if bands needed to be opened for analysis from scCats struct.
-+*/
-+static void get_needed_bands(struct scCats * cats, int * b_needed_bands)
-+{
-+ // results in b_needed_bands - array of bools - if item has value 1, band (defined by item index) is needed to be opened
-+ int i_cat, i_scatt, cat_id;
++ if self._A.dtype != np.uint8 and not np.can_cast(self._A.dtype,
++ np.float):
++ raise TypeError("Image data can not convert to float")
+
-+ for(i_cat = 0; i_cat < cats->n_a_cats; i_cat++)
-+ {
-+ for(i_scatt = 0; i_scatt < cats->cats_arr[i_cat]->n_a_scatts; i_scatt++)
-+ {
-+ G_debug(3, "Active scatt %d in catt %d", i_scatt, i_cat);
++ if (self._A.ndim not in (2, 3) or
++ (self._A.ndim == 3 and self._A.shape[-1] not in (3, 4))):
++ raise TypeError("Invalid dimensions for image data")
+
-+ b_needed_bands[cats->cats_arr[i_cat]->scatts_bands[i_scatt * 2]] = 1;
-+ b_needed_bands[cats->cats_arr[i_cat]->scatts_bands[i_scatt * 2 + 1]] = 1;
-+ }
-+ }
-+ return;
-+}
++ self._imcache =None
++ self._rgbacache = None
++ self._oldxslice = None
++ self._oldyslice = None
++ self._sx, self._sy = None, None
+
-+/*!
-+ \brief Helper function for clean up.
-+*/
-+static void free_compute_scatts_data(int * fd_bands, struct rast_row * bands_rows, int n_a_bands, int * bands_ids,
-+ int * fd_cats_rasts, FILE ** f_cats_rasts_conds, int n_a_cats)
-+{
-+ int i, band_id;
++ def get_array(self):
++ """Override to return the full-resolution array"""
++ return self._full_res
+
-+ for(i = 0; i < n_a_bands; i++)
-+ {
-+ band_id = bands_ids[i];
-+ if(band_id >= 0) {
-+ Rast_close(fd_bands[i]);
-+ G_free(bands_rows[band_id].row);
-+ G_free(bands_rows[band_id].null_row);
-+ }
-+ }
-+
-+ if(f_cats_rasts_conds)
-+ for(i = 0; i < n_a_cats; i++)
-+ if(f_cats_rasts_conds[i])
-+ fclose(f_cats_rasts_conds[i]);
++ def _scale_to_res(self):
++ """ Change self._A and _extent to render an image whose
++ resolution is matched to the eventual rendering."""
+
-+ if(fd_cats_rasts)
-+ for(i = 0; i < n_a_cats; i++)
-+ if(fd_cats_rasts[i] >= 0)
-+ Rast_close(fd_cats_rasts[i]);
++ ax = self.axes
++ ext = ax.transAxes.transform([1, 1]) - ax.transAxes.transform([0, 0])
++ xlim, ylim = ax.get_xlim(), ax.get_ylim()
++ dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
+
-+}
++ y0 = max(self.miny, ylim[0] - 5)
++ y1 = min(self._full_res.shape[0] + self.miny, ylim[1] + 5)
++ x0 = max(self.minx, xlim[0] - 5)
++ x1 = min(self._full_res.shape[1] + self.minx, xlim[1] + 5)
++ y0, y1, x0, x1 = map(int, [y0, y1, x0, x1])
+
-+/*!
-+ \brief Compute scatter plots data.
++ sy = int(max(1, min((y1 - y0) / 5., np.ceil(dy / ext[1]))))
++ sx = int(max(1, min((x1 - x0) / 5., np.ceil(dx / ext[0]))))
+
-+ If category has not defined no category raster condition file and no scatter plot with consdtion,
-+ default scatter plot is computed.
++ # have we already calculated what we need?
++ if sx == self._sx and sy == self._sy and \
++ x0 == self._bounds[0] and x1 == self._bounds[1] and \
++ y0 == self._bounds[2] and y1 == self._bounds[3]:
++ return
+
-+ \param region analysis region, beaware that all input data must be prepared for this region (bands (their ranges), cats_rasts_conds rasters...)
-+ \param region function calls Rast_set_window for this region
-+ \param scatt_conds pointer to scScatts struct of type SC_SCATT_CONDITIONS, where are stored selected areas (conditions) in scatter plots
-+ \param cats_rasts_conds paths to category raster conditions files representing selected areas (conditions) in rasters for every category
-+ \param cats_rasts_conds index in array represents corresponding category id
-+ \param cats_rasts_conds for manupulation with category raster conditions file see also I_id_scatt_to_bands() and I_insert_patch_to_cat_rast()
-+ \param bands names of analyzed bands, order of bands is defined by their id
-+ \param n_bands number of bands
-+ \param [out] scatts pointer to scScatts struct of type SC_SCATT_DATA, where are computed scatter plots stored
-+ \param [out] cats_rasts array of raster maps names where will be stored all selected pixels for every category
++ self._A = self._full_res[y0 - self.miny:y1 - self.miny:sy,
++ x0 - self.minx:x1 - self.minx:sx]
+
-+ \return 0 on success
-+ \return -1 on failure
-+*/
-+int I_compute_scatts(struct Cell_head *region, struct scCats * scatt_conds, const char ** cats_rasts_conds,
-+ const char ** bands, int n_bands, struct scCats * scatts, const char ** cats_rasts)
-+{
-+ const char *mapset;
-+ char header[1024];
++ x1 = x0 + self._A.shape[1] * sx
++ y1 = y0 + self._A.shape[0] * sy
+
-+ int fd_cats_rasts[scatt_conds->n_a_cats];
-+ FILE * f_cats_rasts_conds[scatt_conds->n_a_cats];
++ self.set_extent([x0 - .5, x1 - .5, y0 - .5, y1 - .5])
++ self._sx = sx
++ self._sy = sy
++ self._bounds = (x0, x1, y0, y1)
++ self.changed()
+
-+ struct rast_row bands_rows[n_bands];
++ def draw(self, renderer, *args, **kwargs):
++ self._scale_to_res()
++ super(ModestImage, self).draw(renderer, *args, **kwargs)
+
-+ RASTER_MAP_TYPE data_type;
++def imshow(axes, X, cmap=None, norm=None, aspect=None,
++ interpolation=None, alpha=None, vmin=None, vmax=None,
++ origin=None, extent=None, shape=None, filternorm=1,
++ filterrad=4.0, imlim=None, resample=None, url=None, **kwargs):
++ """Similar to matplotlib's imshow command, but produces a ModestImage
+
-+ int nrows, i_band, n_a_bands, band_id,
-+ i, i_row, head_nchars, i_cat, id_cat;
-+
-+ int fd_bands[n_bands];
-+ int bands_ids[n_bands];
-+ int b_needed_bands[n_bands];
++ Unlike matplotlib version, must explicitly specify axes
++ Author: Chris Beaumont <beaumont at hawaii.edu>
++ """
+
-+ Rast_set_window(region);
++ if not axes._hold:
++ axes.cla()
++ if norm is not None:
++ assert(isinstance(norm, mcolors.Normalize))
++ if aspect is None:
++ aspect = rcParams['image.aspect']
++ axes.set_aspect(aspect)
+
-+ for(i_band = 0; i_band < n_bands; i_band++)
-+ fd_bands[i_band] = -1;
-+
-+ for(i_band = 0; i_band < n_bands; i_band++)
-+ bands_ids[i_band] = -1;
++ if extent:
++ minx=extent[0]
++ miny=extent[2]
++ else:
++ minx=0.0
++ miny=0.0
+
-+ if (n_bands != scatts->n_bands ||
-+ n_bands != scatt_conds->n_bands)
-+ return -1;
++ im = ModestImage(minx, miny, axes, cmap, norm, interpolation, origin, extent,
++ filternorm=filternorm,
++ filterrad=filterrad, resample=resample, **kwargs)
+
-+ memset(b_needed_bands, 0, (size_t)n_bands * sizeof(int));
++ im.set_data(X)
++ im.set_alpha(alpha)
++ axes._set_artist_props(im)
+
-+ get_needed_bands(scatt_conds, &b_needed_bands[0]);
-+ get_needed_bands(scatts, &b_needed_bands[0]);
++ if im.get_clip_path() is None:
++ # image does not already have clipping set, clip to axes patch
++ im.set_clip_path(axes.patch)
+
-+ n_a_bands = 0;
++ #if norm is None and shape is None:
++ # im.set_clim(vmin, vmax)
++ if vmin is not None or vmax is not None:
++ im.set_clim(vmin, vmax)
++ else:
++ im.autoscale_None()
++ im.set_url(url)
+
-+ /* open band rasters, which are needed for computation */
-+ for(band_id = 0; band_id < n_bands; band_id++)
-+ {
-+ if(b_needed_bands[band_id])
-+ {
-+ G_debug(3, "Opening raster no. %d with name: %s", band_id, bands[band_id]);
++ # update ax.dataLim, and, if autoscaling, set viewLim
++ # to tightly fit the image, regardless of dataLim.
++ im.set_extent(im.get_extent())
+
-+ if ((mapset = G_find_raster2(bands[band_id],"")) == NULL) {
-+ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands,
-+ bands_ids, NULL, NULL, scatt_conds->n_a_cats);
-+ G_warning(_("Unbale to read find raster <%s>"), bands[band_id]);
-+ return -1;
-+ }
-+
-+ if ((fd_bands[n_a_bands] = Rast_open_old(bands[band_id], mapset)) < 0) {
-+ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands,
-+ bands_ids, NULL, NULL, scatt_conds->n_a_cats);
-+ G_warning(_("Unbale to open raster <%s>"), bands[band_id]);
-+ return -1;
-+ }
++ axes.images.append(im)
++ im._remove_method = lambda h: axes.images.remove(h)
+
-+ data_type = Rast_get_map_type(fd_bands[n_a_bands]);
-+ if(data_type != CELL_TYPE) {
-+ G_warning(_("Raster <%s> type is not <%s>"), bands[band_id], "CELL");
-+ return -1;
-+ }
++ return im
+
-+ bands_rows[band_id].row = Rast_allocate_c_buf();
-+ bands_rows[band_id].null_row = Rast_allocate_null_buf();
++def MergeImg(cats_order, scatts, styles, output_queue):
+
-+ if(Rast_read_range(bands[band_id], mapset, &bands_rows[band_id].rast_range) != 1){
-+ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands,
-+ bands_ids, NULL, NULL, scatt_conds->n_a_cats);
-+ G_warning(_("Unable to read range of raster <%s>"), bands[band_id]);
-+ return -1;
-+ }
++ start_time = time.clock()
++ cmap_time = 0
++ merge_time = 0
++ mask_time = 0
++ norm_time = 0
++ max_time = 0
+
-+ bands_ids[n_a_bands] = band_id;
-+ ++n_a_bands;
-+ }
-+ }
++ init = True
++ merge_tmp = grass.tempfile()
++ for cat_id in cats_order:
++ if not scatts.has_key(cat_id):
++ continue
++ scatt = scatts[cat_id]
++ #print "color map %d" % cat_id
++ #TODO make more general
++ if cat_id != 0 and (styles[cat_id]['opacity'] == 0.0 or \
++ not styles[cat_id]['show']):
++ continue
++ if init:
+
-+ /* open category rasters condition files and category rasters */
-+ for(i_cat = 0; i_cat < scatts->n_a_cats; i_cat++)
-+ {
-+ id_cat = scatts->cats_ids[i_cat];
-+ if(cats_rasts[id_cat]) {
-+ fd_cats_rasts[i_cat] = Rast_open_new(cats_rasts[id_cat], CELL_TYPE);
-+ }
-+ else
-+ fd_cats_rasts[i_cat] = -1;
++ b2_i = scatt['bands_info']['b1']
++ b1_i = scatt['bands_info']['b2']
+
-+ if(cats_rasts_conds[id_cat]) {
-+ f_cats_rasts_conds[i_cat] = fopen(cats_rasts_conds[id_cat], "r");
-+ if(!f_cats_rasts_conds[i_cat]) {
-+ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands, bands_ids,
-+ f_cats_rasts_conds, f_cats_rasts_conds, scatt_conds->n_a_cats);
-+ G_warning(_("Unable to open category raster condtition file <%s>"), bands[band_id]);
-+ return -1;
-+ }
-+ }
-+ else
-+ f_cats_rasts_conds[i_cat] = NULL;
-+ }
++ full_extend = (b1_i['min'] - 0.5, b1_i['max'] + 0.5, b2_i['min'] - 0.5, b2_i['max'] + 0.5)
++
++ if cat_id == 0:
++ cmap = matplotlib.cm.jet
++ cmap.set_bad('w',1.)
++ cmap._init()
++ cmap._lut[len(cmap._lut) - 1, -1] = 0
++ else:
++ colors = styles[cat_id]['color'].split(":")
+
-+ head_nchars = get_cat_rast_header(region, header);
-+ for(i_cat = 0; i_cat < scatt_conds->n_a_cats; i_cat++)
-+ if(f_cats_rasts_conds[i_cat])
-+ if( fseek(f_cats_rasts_conds[i_cat] , head_nchars, SEEK_SET) != 0) {
-+ G_warning(_("Corrupted category raster conditions file (fseek failed)"));
-+ return -1;
-+ }
++ cmap = matplotlib.cm.jet
++ cmap.set_bad('w',1.)
++ cmap._init()
++ cmap._lut[len(cmap._lut) - 1, -1] = 0
++ cmap._lut[:, 0] = int(colors[0])/255.0
++ cmap._lut[:, 1] = int(colors[1])/255.0
++ cmap._lut[:, 2] = int(colors[2])/255.0
+
-+ nrows = Rast_window_rows();
++ #if init:
++ tmp = time.clock()
+
-+ /* analyze bands by rows */
-+ for (i_row = 0; i_row < nrows; i_row++)
-+ {
-+ for(i_band = 0; i_band < n_a_bands; i_band++)
-+ {
-+ band_id = bands_ids[i_band];
-+ Rast_get_c_row(fd_bands[i_band], bands_rows[band_id].row, i_row);
-+ Rast_get_null_value_row (fd_bands[i_band], bands_rows[band_id].null_row, i_row);
-+ }
-+ if(compute_scatts_from_chunk_row(region, scatt_conds, f_cats_rasts_conds, bands_rows, scatts, fd_cats_rasts) == -1)
-+ {
-+ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands, bands_ids, fd_cats_rasts,
-+ f_cats_rasts_conds, scatt_conds->n_a_cats);
-+ return -1;
-+ }
-+
-+ }
-+ free_compute_scatts_data(fd_bands, bands_rows, n_a_bands, bands_ids,
-+ fd_cats_rasts, f_cats_rasts_conds, scatt_conds->n_a_cats);
-+ return 0;
-+}
++ masked_cat = np.ma.masked_less_equal(scatt['np_vals'], 0)
++ mask_time += time.clock() - tmp
+
++ tmp = time.clock()
++ vmax = np.amax(masked_cat)
++ max_time += time.clock() - tmp
+
-+int I_merge_arrays(unsigned char * merged_arr, unsigned char * overlay_arr, unsigned rows, unsigned cols, double alpha)
-+{
-+ unsigned int i_row, i_col, i_b;
-+ unsigned int row_idx, col_idx, idx;
-+ unsigned int c_a_i, c_a;
++ tmp = time.clock()
++ masked_cat = np.uint8(masked_cat * (255.0 / float(vmax)))
++ norm_time += time.clock() - tmp
+
-+ for(i_row = 0; i_row < rows; i_row++)
-+ {
-+ row_idx = i_row * cols;
-+ for(i_col = 0; i_col < cols; i_col++)
-+ {
-+ col_idx = 4 * (row_idx + i_col);
-+ idx = col_idx + 3;
++ #print masked_cat
++ #print masked_cat.shape
++ #print masked_cat.mask.dtype
++ #
++ #print "colored_cat.shape"
++ #print colored_cat.shape
++ #merged_img = np.memmap(merge_tmp, dtype='uint8',, shape=colored_cat.shape)
++ tmp = time.clock()
++ cmap = np.uint8(cmap._lut * 255)
++ sh =masked_cat.shape
+
-+ c_a = overlay_arr[idx] * alpha;
-+ c_a_i = 255 - c_a;
++ colored_cat = np.zeros(dtype='uint8', shape=(sh[0], sh[1], 4))
++ ApplyColormap(masked_cat, masked_cat.mask, cmap, colored_cat)
+
-+ merged_arr[idx] = (c_a_i * (int)merged_arr[idx] + c_a * 255) / 255;
-+
-+ for(i_b = 0; i_b < 3; i_b++)
-+ {
-+ idx = col_idx + i_b;
-+ merged_arr[idx] = (c_a_i * (int)merged_arr[idx] + c_a * (int)overlay_arr[idx]) / 255;
-+ }
-+ }
-+ }
-+ return 0;
-+}
++ #colored_cat = np.uint8(cmap(masked_cat) * 255)
++ cmap_time += time.clock() - tmp
+
++ del masked_cat
++ del cmap
++
++ #colored_cat[...,3] = np.choose(masked_cat.mask, (255, 0))
+
-+int I_apply_colormap(unsigned char * vals, unsigned char * vals_mask, unsigned vals_size, unsigned char * colmap, unsigned char * col_vals){
-+ unsigned int i_val;
-+ int v, i, i_cm;
++ tmp = time.clock()
++ if init:
++ merged_img = np.memmap(merge_tmp, dtype='uint8', mode='w+', shape=colored_cat.shape)
++ merged_img[:] = colored_cat[:]
++ init = False
++ else:
++ MergeArrays(merged_img, colored_cat, styles[cat_id]['opacity'])
+
-+ for(i_val = 0; i_val < vals_size; i_val++){
-+ i_cm = 4 * i_val;
++ """
++ #c_img_a = np.memmap(grass.tempfile(), dtype="uint16", mode='w+', shape = shape)
++ c_img_a = colored_cat.astype('uint16')[:,:,3] * styles[cat_id]['opacity']
+
-+ v = vals[i_val];
++ #TODO apply strides and there will be no need for loop
++ #b = as_strided(a, strides=(0, a.strides[3], a.strides[3], a.strides[3]), shape=(3, a.shape[0], a.shape[1]))
++
++ for i in range(3):
++ merged_img[:,:,i] = (merged_img[:,:,i] * (255 - c_img_a) + colored_cat[:,:,i] * c_img_a) / 255;
++ merged_img[:,:,3] = (merged_img[:,:,3] * (255 - c_img_a) + 255 * c_img_a) / 255;
++
++ del c_img_a
++ """
++ merge_time += time.clock() - tmp
+
-+ if(vals_mask[i_val])
-+ for(i = 0; i < 4; i++) col_vals[i_cm + i] = colmap[258 * 4 + i];
-+ else if(v > 255)
-+ for(i = 0; i < 4; i++) col_vals[i_cm + i] = colmap[257 * 4 + i];
-+ else if(v < 0)
-+ for(i = 0; i < 4; i++) col_vals[i_cm + i] = colmap[256 * 4 + i];
-+ else
-+ for(i = 0; i < 4; i++){ col_vals[i_cm + i] = colmap[v * 4 + i];
-+ }
-+ }
-+ return 0;
-+}
++ del colored_cat
++
++ end_time = time.clock() - start_time
++ #print "all time:%f" % (end_time)
++ #print "cmap_time:%f" % (cmap_time / end_time * 100.0 )
++ #print "merge_time:%f" % (merge_time / end_time * 100.0 )
++ #print "mask_time:%f" % (mask_time / end_time * 100.0 )
++ #print "nax_time:%f" % (max_time / end_time * 100.0 )
++ #print "norm_time:%f" % (norm_time / end_time * 100.0 )
++
++ #output_queue.put((merged_img, full_extend))
++ return merged_img, full_extend
+\ No newline at end of file
+Index: gui/wxpython/vnet/dialogs.py
+===================================================================
+--- gui/wxpython/vnet/dialogs.py (revision 57728)
++++ gui/wxpython/vnet/dialogs.py (working copy)
+@@ -1218,7 +1218,7 @@
+ pos = (row, 1))
+
+ row += 1
+- gridSizer.Add(item = self.settings["invert_colors"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
++ gridSizer.Add(item=self.settings["invert_colors"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+
+ gridSizer.AddGrowableCol(1)
+ styleBoxSizer.Add(item = gridSizer, flag = wx.EXPAND)
+Index: gui/wxpython/vnet/toolbars.py
+===================================================================
+--- gui/wxpython/vnet/toolbars.py (revision 57728)
++++ gui/wxpython/vnet/toolbars.py (working copy)
+@@ -19,9 +19,9 @@
+
+ import wx
+
+-from icon import MetaIcon
++from icons.icon import MetaIcon
+ from gui_core.toolbars import BaseToolbar, BaseIcons
+-from core.gcmd import RunCommand
++from core.gcmd import RunCommand
+ from core.utils import _
+
+ class PointListToolbar(BaseToolbar):
+Index: gui/wxpython/Makefile
+===================================================================
+--- gui/wxpython/Makefile (revision 57728)
++++ gui/wxpython/Makefile (working copy)
+@@ -13,7 +13,7 @@
+ $(wildcard animation/* core/*.py dbmgr/* gcp/*.py gmodeler/* \
+ gui_core/*.py iclass/* lmgr/*.py location_wizard/*.py mapwin/*.py mapdisp/*.py \
+ mapswipe/* modules/*.py nviz/*.py psmap/* rlisetup/* timeline/* vdigit/* \
+- vnet/*.py web_services/*.py wxplot/*.py) \
++ vnet/*.py web_services/*.py wxplot/*.py scatt_plot/*.py) \
+ gis_set.py gis_set_error.py wxgui.py README
+
+ DSTFILES := $(patsubst %,$(ETCDIR)/%,$(SRCFILES)) \
+@@ -21,8 +21,9 @@
+
+ PYDSTDIRS := $(patsubst %,$(ETCDIR)/%,animation core dbmgr gcp gmodeler \
+ gui_core iclass lmgr location_wizard mapwin mapdisp modules nviz psmap \
+- mapswipe vdigit wxplot web_services rlisetup vnet timeline)
++ mapswipe vdigit wxplot web_services rlisetup vnet timeline scatt_plot)
+
++
+ DSTDIRS := $(patsubst %,$(ETCDIR)/%,icons scripts xml)
+
+ default: $(DSTFILES)
More information about the grass-commit
mailing list