Point Cloud Library (PCL)  1.11.1
world_model.hpp
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  * Copyright (c) 2010-2011, Willow Garage, Inc.
6  *
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * * Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  * * Redistributions in binary form must reproduce the above
16  * copyright notice, this list of conditions and the following
17  * disclaimer in the documentation and/or other materials provided
18  * with the distribution.
19  * * Neither the name of Willow Garage, Inc. nor the names of its
20  * contributors may be used to endorse or promote products derived
21  * from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  *
36  * Author: Raphael Favier, Technical University Eindhoven, (r.mysurname <aT> tue.nl)
37  */
38 
39 #ifndef PCL_WORLD_MODEL_IMPL_HPP_
40 #define PCL_WORLD_MODEL_IMPL_HPP_
41 
42 #include <pcl/gpu/kinfu_large_scale/world_model.h>
43 
44 template <typename PointT>
45 void
47 {
48  PCL_DEBUG("Adding new cloud. Current world contains %zu points.\n",
49  static_cast<std::size_t>(world_->size()));
50 
51  PCL_DEBUG("New slice contains %zu points.\n",
52  static_cast<std::size_t>(new_cloud->size()));
53 
54  *world_ += *new_cloud;
55 
56  PCL_DEBUG("World now contains %zu points.\n",
57  static_cast<std::size_t>(world_->size()));
58 }
59 
60 template <typename PointT>
61 void
62 pcl::kinfuLS::WorldModel<PointT>::getExistingData(const double previous_origin_x, const double previous_origin_y, const double previous_origin_z, const double offset_x, const double offset_y, const double offset_z, const double volume_x, const double volume_y, const double volume_z, pcl::PointCloud<PointT> &existing_slice)
63 {
64  double newOriginX = previous_origin_x + offset_x;
65  double newOriginY = previous_origin_y + offset_y;
66  double newOriginZ = previous_origin_z + offset_z;
67  double newLimitX = newOriginX + volume_x;
68  double newLimitY = newOriginY + volume_y;
69  double newLimitZ = newOriginZ + volume_z;
70 
71  // filter points in the space of the new cube
73  // condition
74  ConditionAndPtr range_condAND (new pcl::ConditionAnd<PointT> ());
75  range_condAND->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::GE, newOriginX)));
76  range_condAND->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::LT, newLimitX)));
77  range_condAND->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::GE, newOriginY)));
78  range_condAND->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::LT, newLimitY)));
79  range_condAND->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::GE, newOriginZ)));
80  range_condAND->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::LT, newLimitZ)));
81 
82  // build the filter
83  pcl::ConditionalRemoval<PointT> condremAND (true);
84  condremAND.setCondition (range_condAND);
85  condremAND.setInputCloud (world_);
86  condremAND.setKeepOrganized (false);
87 
88  // apply filter
89  condremAND.filter (*newCube);
90 
91  // filter points that belong to the new slice
92  ConditionOrPtr range_condOR (new pcl::ConditionOr<PointT> ());
93 
94  if(offset_x >= 0)
95  range_condOR->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::GE, previous_origin_x + volume_x - 1.0 )));
96  else
97  range_condOR->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::LT, previous_origin_x )));
98 
99  if(offset_y >= 0)
100  range_condOR->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::GE, previous_origin_y + volume_y - 1.0 )));
101  else
102  range_condOR->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::LT, previous_origin_y )));
103 
104  if(offset_z >= 0)
105  range_condOR->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::GE, previous_origin_z + volume_z - 1.0 )));
106  else
107  range_condOR->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::LT, previous_origin_z )));
108 
109  // build the filter
110  pcl::ConditionalRemoval<PointT> condrem (true);
111  condrem.setCondition (range_condOR);
112  condrem.setInputCloud (newCube);
113  condrem.setKeepOrganized (false);
114  // apply filter
115  condrem.filter (existing_slice);
116 
117  if(!existing_slice.points.empty ())
118  {
119  //transform the slice in new cube coordinates
120  Eigen::Affine3f transformation;
121  transformation.translation ()[0] = newOriginX;
122  transformation.translation ()[1] = newOriginY;
123  transformation.translation ()[2] = newOriginZ;
124 
125  transformation.linear ().setIdentity ();
126 
127  transformPointCloud (existing_slice, existing_slice, transformation.inverse ());
128 
129  }
130 }
131 
132 
133 template <typename PointT>
134 void
135 pcl::kinfuLS::WorldModel<PointT>::getWorldAsCubes (const double size, std::vector<typename WorldModel<PointT>::PointCloudPtr> &cubes, std::vector<Eigen::Vector3f, Eigen::aligned_allocator<Eigen::Vector3f> > &transforms, double overlap)
136 {
137 
138  if(world_->points.empty ())
139  {
140  PCL_INFO("The world is empty, returning nothing\n");
141  return;
142  }
143 
144  PCL_INFO("Getting world as cubes. World contains %zu points.\n",
145  static_cast<std::size_t>(world_->size()));
146 
147  // remove nans from world cloud
148  world_->is_dense = false;
149  std::vector<int> indices;
150  pcl::removeNaNFromPointCloud ( *world_, *world_, indices);
151 
152  PCL_INFO("World contains %zu points after nan removal.\n",
153  static_cast<std::size_t>(world_->size()));
154 
155  // check cube size value
156  double cubeSide = size;
157  if (cubeSide <= 0.0f)
158  {
159  PCL_ERROR ("Size of the cube must be positive and non null (%f given). Setting it to 3.0 meters.\n", cubeSide);
160  cubeSide = 512.0f;
161  }
162 
163  std::cout << "cube size is set to " << cubeSide << std::endl;
164 
165  // check overlap value
166  double step_increment = 1.0f - overlap;
167  if (overlap < 0.0)
168  {
169  PCL_ERROR ("Overlap ratio must be positive or null (%f given). Setting it to 0.0 procent.\n", overlap);
170  step_increment = 1.0f;
171  }
172  if (overlap > 1.0)
173  {
174  PCL_ERROR ("Overlap ratio must be less or equal to 1.0 (%f given). Setting it to 10 procent.\n", overlap);
175  step_increment = 0.1f;
176  }
177 
178 
179  // get world's bounding values on XYZ
180  PointT min, max;
181  pcl::getMinMax3D(*world_, min, max);
182 
183  PCL_INFO ("Bounding box for the world: \n\t [%f - %f] \n\t [%f - %f] \n\t [%f - %f] \n", min.x, max.x, min.y, max.y, min.z, max.z);
184 
185  PointT origin = min;
186 
187  // clear returned vectors
188  cubes.clear();
189  transforms.clear();
190 
191  // iterate with box filter
192  while (origin.x < max.x)
193  {
194  origin.y = min.y;
195  while (origin.y < max.y)
196  {
197  origin.z = min.z;
198  while (origin.z < max.z)
199  {
200  // extract cube here
201  PCL_INFO ("Extracting cube at: [%f, %f, %f].\n", origin.x, origin.y, origin.z);
202 
203  // pointcloud for current cube.
204  PointCloudPtr box (new pcl::PointCloud<PointT>);
205 
206 
207  // set conditional filter
208  ConditionAndPtr range_cond (new pcl::ConditionAnd<PointT> ());
209  range_cond->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::GE, origin.x)));
210  range_cond->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::LT, origin.x + cubeSide)));
211  range_cond->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::GE, origin.y)));
212  range_cond->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::LT, origin.y + cubeSide)));
213  range_cond->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::GE, origin.z)));
214  range_cond->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::LT, origin.z + cubeSide)));
215 
216  // build the filter
218  condrem.setCondition (range_cond);
219  condrem.setInputCloud (world_);
220  condrem.setKeepOrganized(false);
221  // apply filter
222  condrem.filter (*box);
223 
224  // also push transform along with points.
225  if(!box->points.empty ())
226  {
227  Eigen::Vector3f transform;
228  transform[0] = origin.x, transform[1] = origin.y, transform[2] = origin.z;
229  transforms.push_back(transform);
230  cubes.push_back(box);
231  }
232  else
233  {
234  PCL_INFO ("Extracted cube was empty, skipping this one.\n");
235  }
236  origin.z += cubeSide * step_increment;
237  }
238  origin.y += cubeSide * step_increment;
239  }
240  origin.x += cubeSide * step_increment;
241  }
242 
243 
244  /* for(int c = 0 ; c < cubes.size() ; ++c)
245  {
246  std::stringstream name;
247  name << "cloud" << c+1 << ".pcd";
248  pcl::io::savePCDFileASCII(name.str(), *(cubes[c]));
249 
250  }*/
251 
252  std::cout << "returning " << cubes.size() << " cubes" << std::endl;
253 }
254 
255 template <typename PointT>
256 inline void
257 pcl::kinfuLS::WorldModel<PointT>::setIndicesAsNans (PointCloudPtr cloud, IndicesConstPtr indices)
258 {
259  std::vector<pcl::PCLPointField> fields;
260  pcl::for_each_type<FieldList> (pcl::detail::FieldAdder<PointT> (fields));
261  float my_nan = std::numeric_limits<float>::quiet_NaN ();
262 
263  for (int rii = 0; rii < static_cast<int> (indices->size ()); ++rii) // rii = removed indices iterator
264  {
265  std::uint8_t* pt_data = reinterpret_cast<std::uint8_t*> (&(*cloud)[(*indices)[rii]]);
266  for (const auto &field : fields)
267  memcpy (pt_data + field.offset, &my_nan, sizeof (float));
268  }
269 }
270 
271 
272 template <typename PointT>
273 void
274 pcl::kinfuLS::WorldModel<PointT>::setSliceAsNans (const double origin_x, const double origin_y, const double origin_z, const double offset_x, const double offset_y, const double offset_z, const int size_x, const int size_y, const int size_z)
275 {
276  // PCL_DEBUG ("IN SETSLICE AS NANS\n");
277 
279 
280  // prepare filter limits on all dimensions
281  double previous_origin_x = origin_x;
282  double previous_limit_x = origin_x + size_x - 1;
283  double new_origin_x = origin_x + offset_x;
284  double new_limit_x = previous_limit_x + offset_x;
285 
286  double previous_origin_y = origin_y;
287  double previous_limit_y = origin_y + size_y - 1;
288  double new_origin_y = origin_y + offset_y;
289  double new_limit_y = previous_limit_y + offset_y;
290 
291  double previous_origin_z = origin_z;
292  double previous_limit_z = origin_z + size_z - 1;
293  double new_origin_z = origin_z + offset_z;
294  double new_limit_z = previous_limit_z + offset_z;
295 
296  // get points of slice on X (we actually set a negative filter and set the ouliers (so, our slice points) to nan)
297  double lower_limit_x, upper_limit_x;
298  if(offset_x >=0)
299  {
300  lower_limit_x = previous_origin_x;
301  upper_limit_x = new_origin_x;
302  }
303  else
304  {
305  lower_limit_x = new_limit_x;
306  upper_limit_x = previous_limit_x;
307  }
308 
309  // PCL_DEBUG ("Limit X: [%f - %f]\n", lower_limit_x, upper_limit_x);
310 
311  ConditionOrPtr range_cond_OR_x (new pcl::ConditionOr<PointT> ());
312  range_cond_OR_x->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::GE, upper_limit_x ))); // filtered dimension
313  range_cond_OR_x->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::LT, lower_limit_x ))); // filtered dimension
314 
315  range_cond_OR_x->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::GE, previous_limit_y)));
316  range_cond_OR_x->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::LT, previous_origin_y )));
317 
318  range_cond_OR_x->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::GE, previous_limit_z)));
319  range_cond_OR_x->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::LT, previous_origin_z )));
320 
321  pcl::ConditionalRemoval<PointT> condrem_x (true);
322  condrem_x.setCondition (range_cond_OR_x);
323  condrem_x.setInputCloud (world_);
324  condrem_x.setKeepOrganized (false);
325  // apply filter
326  condrem_x.filter (*slice);
327  IndicesConstPtr indices_x = condrem_x.getRemovedIndices ();
328 
329  //set outliers (so our slice points) to nan
330  setIndicesAsNans(world_, indices_x);
331 
332  // PCL_DEBUG("%d points set to nan on X\n", indices_x->size ());
333 
334  // get points of slice on Y (we actually set a negative filter and set the ouliers (so, our slice points) to nan)
335  double lower_limit_y, upper_limit_y;
336  if(offset_y >=0)
337  {
338  lower_limit_y = previous_origin_y;
339  upper_limit_y = new_origin_y;
340  }
341  else
342  {
343  lower_limit_y = new_limit_y;
344  upper_limit_y = previous_limit_y;
345  }
346 
347  // PCL_DEBUG ("Limit Y: [%f - %f]\n", lower_limit_y, upper_limit_y);
348 
349  ConditionOrPtr range_cond_OR_y (new pcl::ConditionOr<PointT> ());
350  range_cond_OR_y->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::GE, previous_limit_x )));
351  range_cond_OR_y->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::LT, previous_origin_x )));
352 
353  range_cond_OR_y->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::GE, upper_limit_y))); // filtered dimension
354  range_cond_OR_y->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::LT, lower_limit_y ))); // filtered dimension
355 
356  range_cond_OR_y->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::GE, previous_limit_z)));
357  range_cond_OR_y->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::LT, previous_origin_z )));
358 
359  pcl::ConditionalRemoval<PointT> condrem_y (true);
360  condrem_y.setCondition (range_cond_OR_y);
361  condrem_y.setInputCloud (world_);
362  condrem_y.setKeepOrganized (false);
363  // apply filter
364  condrem_y.filter (*slice);
365  IndicesConstPtr indices_y = condrem_y.getRemovedIndices ();
366 
367  //set outliers (so our slice points) to nan
368  setIndicesAsNans(world_, indices_y);
369  // PCL_DEBUG ("%d points set to nan on Y\n", indices_y->size ());
370 
371  // get points of slice on Z (we actually set a negative filter and set the ouliers (so, our slice points) to nan)
372  double lower_limit_z, upper_limit_z;
373  if(offset_z >=0)
374  {
375  lower_limit_z = previous_origin_z;
376  upper_limit_z = new_origin_z;
377  }
378  else
379  {
380  lower_limit_z = new_limit_z;
381  upper_limit_z = previous_limit_z;
382  }
383 
384  // PCL_DEBUG ("Limit Z: [%f - %f]\n", lower_limit_z, upper_limit_z);
385 
386  ConditionOrPtr range_cond_OR_z (new pcl::ConditionOr<PointT> ());
387  range_cond_OR_z->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::GE, previous_limit_x )));
388  range_cond_OR_z->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("x", pcl::ComparisonOps::LT, previous_origin_x )));
389 
390  range_cond_OR_z->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::GE, previous_limit_y)));
391  range_cond_OR_z->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("y", pcl::ComparisonOps::LT, previous_origin_y )));
392 
393  range_cond_OR_z->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::GE, upper_limit_z))); // filtered dimension
394  range_cond_OR_z->addComparison (FieldComparisonConstPtr (new pcl::FieldComparison<PointT> ("z", pcl::ComparisonOps::LT, lower_limit_z ))); // filtered dimension
395 
396  pcl::ConditionalRemoval<PointT> condrem_z (true);
397  condrem_z.setCondition (range_cond_OR_z);
398  condrem_z.setInputCloud (world_);
399  condrem_z.setKeepOrganized (false);
400  // apply filter
401  condrem_z.filter (*slice);
402  IndicesConstPtr indices_z = condrem_z.getRemovedIndices ();
403 
404  //set outliers (so our slice points) to nan
405  setIndicesAsNans(world_, indices_z);
406  // PCL_DEBUG("%d points set to nan on Z\n", indices_z->size ());
407 
408 
409 }
410 
411 #define PCL_INSTANTIATE_WorldModel(T) template class PCL_EXPORTS pcl::kinfuLS::WorldModel<T>;
412 
413 #endif // PCL_WORLD_MODEL_IMPL_HPP_
ConditionalRemoval filters data that satisfies certain conditions.
void setCondition(ConditionBasePtr condition)
Set the condition that the filter will use.
void setKeepOrganized(bool val)
Set whether the filtered points should be kept and set to the value given through setUserFilterValue ...
The field-based specialization of the comparison object.
void filter(PointCloud &output)
Calls the filtering method and returns the filtered dataset in output.
Definition: filter.h:124
IndicesConstPtr const getRemovedIndices() const
Get the point indices being removed.
Definition: filter.h:106
virtual void setInputCloud(const PointCloudConstPtr &cloud)
Provide a pointer to the input dataset.
Definition: pcl_base.hpp:65
PointCloud represents the base class in PCL for storing collections of 3D points.
Definition: point_cloud.h:181
std::vector< PointT, Eigen::aligned_allocator< PointT > > points
The point data.
Definition: point_cloud.h:411
WorldModel maintains a 3D point cloud that can be queried and updated via helper functions.
Definition: world_model.h:64
typename PointCloud::Ptr PointCloudPtr
Definition: world_model.h:71
void setSliceAsNans(const double origin_x, const double origin_y, const double origin_z, const double offset_x, const double offset_y, const double offset_z, const int size_x, const int size_y, const int size_z)
Give nan values to the slice of the world.
void getWorldAsCubes(double size, std::vector< PointCloudPtr > &cubes, std::vector< Eigen::Vector3f, Eigen::aligned_allocator< Eigen::Vector3f > > &transforms, double overlap=0.0)
Returns the world as two vectors of cubes of size "size" (pointclouds) and transforms.
typename pcl::FieldComparison< PointT >::ConstPtr FieldComparisonConstPtr
Definition: world_model.h:76
void getExistingData(const double previous_origin_x, const double previous_origin_y, const double previous_origin_z, const double offset_x, const double offset_y, const double offset_z, const double volume_x, const double volume_y, const double volume_z, pcl::PointCloud< PointT > &existing_slice)
Retrieve existing data from the world model, after a shift.
Definition: world_model.hpp:62
typename pcl::ConditionAnd< PointT >::Ptr ConditionAndPtr
Definition: world_model.h:74
void addSlice(const PointCloudPtr new_cloud)
Append a new point cloud (slice) to the world.
Definition: world_model.hpp:46
typename pcl::ConditionOr< PointT >::Ptr ConditionOrPtr
Definition: world_model.h:75
void getMinMax3D(const pcl::PointCloud< PointT > &cloud, PointT &min_pt, PointT &max_pt)
Get the minimum and maximum values on each of the 3 (x-y-z) dimensions in a given pointcloud.
Definition: common.hpp:243
void transformPointCloud(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, const Eigen::Transform< Scalar, 3, Eigen::Affine > &transform, bool copy_all_fields)
Apply an affine transform defined by an Eigen Transform.
Definition: transforms.hpp:221
void removeNaNFromPointCloud(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, std::vector< int > &index)
Removes points with x, y, or z equal to NaN.
Definition: filter.hpp:46
shared_ptr< const Indices > IndicesConstPtr
Definition: pcl_base.h:62
std::uint8_t uint8_t
Definition: types.h:54
A point structure representing Euclidean xyz coordinates, and the RGB color.