vigra - vigra/multi_shape.hxx Source File

[ VIGRA Homepage | Function Index | Class Index | Namespaces | File List | Main Page ]

multi_shape.hxx |

1 /************************************************************************/
2 /* */
3 /* Copyright 2011-2012 by Stefan Schmidt and Ullrich Koethe */
4 /* */
5 /* This file is part of the VIGRA computer vision library. */
6 /* The VIGRA Website is */
7 /* http://hci.iwr.uni-heidelberg.de/vigra/ */
8 /* Please direct questions, bug reports, and contributions to */
9 /* ullrich.koethe@iwr.uni-heidelberg.de or */
10 /* vigra@informatik.uni-hamburg.de */
11 /* */
12 /* Permission is hereby granted, free of charge, to any person */
13 /* obtaining a copy of this software and associated documentation */
14 /* files (the "Software"), to deal in the Software without */
15 /* restriction, including without limitation the rights to use, */
16 /* copy, modify, merge, publish, distribute, sublicense, and/or */
17 /* sell copies of the Software, and to permit persons to whom the */
18 /* Software is furnished to do so, subject to the following */
19 /* conditions: */
20 /* */
21 /* The above copyright notice and this permission notice shall be */
22 /* included in all copies or substantial portions of the */
23 /* Software. */
24 /* */
25 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
26 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
27 /* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
28 /* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
29 /* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
30 /* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
31 /* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
32 /* OTHER DEALINGS IN THE SOFTWARE. */
33 /* */
34 /************************************************************************/
35
36 #ifndef VIGRA_MULTI_SHAPE_HXX
37 #define VIGRA_MULTI_SHAPE_HXX
38
39 #include "multi_fwd.hxx"
40 #include <sys/types.h>
41 #include "tinyvector.hxx"
42 #include "array_vector.hxx"
43 #include "numerictraits.hxx"
44
45 namespace vigra {
46
47 /** \addtogroup MultiIteratorGroup
48 */
49 //@{
50
51 /********************************************************/
52 /* */
53 /* Singleband and Multiband */
54 /* */
55 /********************************************************/
56
57 template <class T>
58 struct Singleband // the resulting MultiArray has no explicit channel axis
59 // (i.e. the number of channels is implicitly one)
60 {
61 typedef T value_type;
62 };
63
64 template <class T>
65 struct Multiband // the last axis is explicitly designated as channel axis
66 {
67 typedef T value_type;
68 };
69
70 // check if a type is a multiband type
71 template<class T>
72 struct IsMultiband : VigraFalseType{
73 };
74
75 template<class T>
76 struct IsMultiband<Multiband<T> > : VigraTrueType{
77 };
78
79 template <class T>
80 struct ChunkedMemory // the array is organised in chunks
81 {
82 typedef T value_type;
83 };
84
85 template<class T>
86 struct NumericTraits<Singleband<T> >
87 : public NumericTraits<T>
88 {};
89
90 template<class T>
91 struct NumericTraits<Multiband<T> >
92 {
93 typedef Multiband<T> Type;
94 /*
95 typedef int Promote;
96 typedef unsigned int UnsignedPromote;
97 typedef double RealPromote;
98 typedef std::complex<RealPromote> ComplexPromote;
99 */
100 typedef Type ValueType;
101
102 typedef typename NumericTraits<T>::isIntegral isIntegral;
103 typedef VigraFalseType isScalar;
104 typedef typename NumericTraits<T>::isSigned isSigned;
105 typedef typename NumericTraits<T>::isSigned isOrdered;
106 typedef typename NumericTraits<T>::isSigned isComplex;
107 /*
108 static signed char zero() { return 0; }
109 static signed char one() { return 1; }
110 static signed char nonZero() { return 1; }
111 static signed char min() { return SCHAR_MIN; }
112 static signed char max() { return SCHAR_MAX; }
113
114 #ifdef NO_INLINE_STATIC_CONST_DEFINITION
115 enum { minConst = SCHAR_MIN, maxConst = SCHAR_MIN };
116 #else
117 static const signed char minConst = SCHAR_MIN;
118 static const signed char maxConst = SCHAR_MIN;
119 #endif
120
121 static Promote toPromote(signed char v) { return v; }
122 static RealPromote toRealPromote(signed char v) { return v; }
123 static signed char fromPromote(Promote v) {
124 return ((v < SCHAR_MIN) ? SCHAR_MIN : (v > SCHAR_MAX) ? SCHAR_MAX : v);
125 }
126 static signed char fromRealPromote(RealPromote v) {
127 return ((v < 0.0)
128 ? ((v < (RealPromote)SCHAR_MIN)
129 ? SCHAR_MIN
130 : static_cast<signed char>(v - 0.5))
131 : (v > (RealPromote)SCHAR_MAX)
132 ? SCHAR_MAX
133 : static_cast<signed char>(v + 0.5));
134 }
135 */
136 };
137
138 namespace detail {
139
140 /********************************************************/
141 /* */
142 /* defaultStride */
143 /* */
144 /********************************************************/
145
146 /* generates the stride for a gapless shape.
147 */
148 template <int N>
149 inline TinyVector <MultiArrayIndex, N>
150 defaultStride(const TinyVector <MultiArrayIndex, N> &shape)
151 {
152 TinyVector <MultiArrayIndex, N> ret;
153 ret [0] = 1;
154 for (int i = 1; i < (int)N; ++i)
155 ret [i] = ret [i-1] * shape [i-1];
156 return ret;
157 }
158
159 /* generates the stride for a gapless shape.
160 */
161 template <int N>
162 inline TinyVector <MultiArrayIndex, N>
163 defaultMultibandStride(const TinyVector <MultiArrayIndex, N> &shape)
164 {
165 TinyVector <MultiArrayIndex, N> ret;
166 ret [N-1] = 1;
167 for (int i = 0; i < (int)N-1; ++i)
168 {
169 int j = (i + int(N - 1)) % N;
170 ret [i] = ret [j] * shape [j];
171 }
172 return ret;
173 }
174
175 /********************************************************/
176 /* */
177 /* resolve Multiband and ChunkedMemory tags */
178 /* */
179 /********************************************************/
180
181 template <class T>
182 struct ResolveMultiband
183 {
184 typedef T type;
185 typedef StridedArrayTag Stride;
186 static const bool value = false;
187
188 template <int N>
189 static TinyVector <MultiArrayIndex, N>
190 defaultStride(const TinyVector <MultiArrayIndex, N> &shape)
191 {
192 return vigra::detail::defaultStride(shape);
193 }
194 };
195
196 template <class T>
197 struct ResolveMultiband<Singleband<T> >
198 {
199 typedef T type;
200 typedef StridedArrayTag Stride;
201 static const bool value = false;
202
203 template <int N>
204 static TinyVector <MultiArrayIndex, N>
205 defaultStride(const TinyVector <MultiArrayIndex, N> &shape)
206 {
207 return vigra::detail::defaultStride(shape);
208 }
209 };
210
211 template <class T>
212 struct ResolveMultiband<Multiband<T> >
213 {
214 typedef T type;
215 typedef StridedArrayTag Stride;
216 static const bool value = true;
217
218 template <int N>
219 static TinyVector <MultiArrayIndex, N>
220 defaultStride(const TinyVector <MultiArrayIndex, N> &shape)
221 {
222 return vigra::detail::defaultMultibandStride(shape);
223 }
224 };
225
226 template <class T>
227 struct ResolveChunkedMemory
228 {
229 typedef T type;
230 };
231
232 template <class T>
233 struct ResolveChunkedMemory<ChunkedMemory<T> >
234 {
235 typedef T type;
236 };
237
238 } // namespace detail
239
240 /** Traits class for the difference type of all MultiIterator, MultiArrayView, and
241 MultiArray variants.
242 */
243 template <unsigned int N>
244 class MultiArrayShape
245 {
246 public:
247 /** The difference type of all MultiIterator, MultiArrayView, and
248 MultiArray variants.
249 */
250 typedef TinyVector<MultiArrayIndex, N> type;
251 };
252
253 typedef MultiArrayShape<1>::type Shape1; ///< shape type for MultiArray<1, T>
254 typedef MultiArrayShape<2>::type Shape2; ///< shape type for MultiArray<2, T>
255 typedef MultiArrayShape<3>::type Shape3; ///< shape type for MultiArray<3, T>
256 typedef MultiArrayShape<4>::type Shape4; ///< shape type for MultiArray<4, T>
257 typedef MultiArrayShape<5>::type Shape5; ///< shape type for MultiArray<5, T>
258
259 namespace detail
260 {
261
262 /********************************************************/
263 /* */
264 /* default chunk shapes */
265 /* */
266 /********************************************************/
267
268 template <unsigned int N, class T = int>
269 struct ChunkShape
270 {
271 typedef typename MultiArrayShape<N>::type Shape;
272 static Shape defaultShape()
273 {
274 Shape res(1);
275 res.template subarray<0,5>() = ChunkShape<5,T>::defaultShape();
276 return res;
277 }
278 };
279
280 template <class T>
281 struct ChunkShape<0, T>
282 {
283 static Shape1 defaultShape()
284 {
285 return Shape1(1);
286 }
287 };
288
289 template <class T>
290 struct ChunkShape<1, T>
291 {
292 static Shape1 defaultShape()
293 {
294 return Shape1(1 << 18);
295 }
296 };
297
298 template <class T>
299 struct ChunkShape<2, T>
300 {
301 static Shape2 defaultShape()
302 {
303 return Shape2(1 << 9, 1 << 9);
304 }
305 };
306
307 template <class T>
308 struct ChunkShape<3, T>
309 {
310 static Shape3 defaultShape()
311 {
312 return Shape3(1 << 6, 1 << 6, 1 << 6);
313 }
314 };
315
316 template <class T>
317 struct ChunkShape<4, T>
318 {
319 static Shape4 defaultShape()
320 {
321 return Shape4(1 << 6, 1 << 6, 1 << 4, 1 << 2);
322 }
323 };
324
325 template <class T>
326 struct ChunkShape<5, T>
327 {
328 static Shape5 defaultShape()
329 {
330 return Shape5(1 << 6, 1 << 6, 1 << 4, 1 << 2, 1 << 2);
331 }
332 };
333
334 } // namespace detail
335
336 // Helper functions
337
338 namespace detail {
339
340 /********************************************************/
341 /* */
342 /* CoordinateToScanOrder */
343 /* */
344 /********************************************************/
345
346 /* Convert multi-dimensional index (i.e. a grid coordinate) to scan-order index.
347 */
348 template <int K>
349 struct CoordinateToScanOrder
350 {
351 template <int N, class D1, class D2, class D3, class D4>
352 static MultiArrayIndex
353 exec(const TinyVectorBase <MultiArrayIndex, N, D1, D2> &shape,
354 const TinyVectorBase <MultiArrayIndex, N, D3, D4> & coordinate)
355 {
356 return coordinate[N-K] + shape[N-K] * CoordinateToScanOrder<K-1>::exec(shape, coordinate);
357 }
358 };
359
360 template <>
361 struct CoordinateToScanOrder<1>
362 {
363 template <int N, class D1, class D2, class D3, class D4>
364 static MultiArrayIndex
365 exec(const TinyVectorBase <MultiArrayIndex, N, D1, D2> & /*shape*/,
366 const TinyVectorBase <MultiArrayIndex, N, D3, D4> & coordinate)
367 {
368 return coordinate[N-1];
369 }
370 };
371
372 /********************************************************/
373 /* */
374 /* ScanOrderToCoordinate */
375 /* */
376 /********************************************************/
377
378 /* Convert scan-order index to multi-dimensional index (i.e. a grid coordinate).
379 */
380 template <int K>
381 struct ScanOrderToCoordinate
382 {
383 template <int N>
384 static void
385 exec(MultiArrayIndex d, const TinyVector <MultiArrayIndex, N> &shape,
386 TinyVector <MultiArrayIndex, N> & result)
387 {
388 result[N-K] = (d % shape[N-K]);
389 ScanOrderToCoordinate<K-1>::exec(d / shape[N-K], shape, result);
390 }
391 };
392
393 template <>
394 struct ScanOrderToCoordinate<1>
395 {
396 template <int N>
397 static void
398 exec(MultiArrayIndex d, const TinyVector <MultiArrayIndex, N> & /*shape*/,
399 TinyVector <MultiArrayIndex, N> & result)
400 {
401 result[N-1] = d;
402 }
403 };
404
405 /********************************************************/
406 /* */
407 /* ScanOrderToOffset */
408 /* */
409 /********************************************************/
410
411 /* transforms an index in scan order sense to a pointer offset in a possibly
412 strided, multi-dimensional array.
413 */
414 template <int K>
415 struct ScanOrderToOffset
416 {
417 template <int N>
418 static MultiArrayIndex
419 exec(MultiArrayIndex d, const TinyVector <MultiArrayIndex, N> &shape,
420 const TinyVector <MultiArrayIndex, N> & stride)
421 {
422 return stride[N-K] * (d % shape[N-K]) +
423 ScanOrderToOffset<K-1>::exec(d / shape[N-K], shape, stride);
424 }
425 };
426
427 template <>
428 struct ScanOrderToOffset<1>
429 {
430 template <int N>
431 static MultiArrayIndex
432 exec(MultiArrayIndex d, const TinyVector <MultiArrayIndex, N> & /*shape*/,
433 const TinyVector <MultiArrayIndex, N> & stride)
434 {
435 return stride[N-1] * d;
436 }
437 };
438
439 /********************************************************/
440 /* */
441 /* ScanOrderToOffset */
442 /* */
443 /********************************************************/
444
445 /* transforms a multi-dimensional index (grid coordinate) to a pointer offset in a possibly
446 strided, multi-dimensional array.
447 */
448 template <class C>
449 struct CoordinatesToOffest
450 {
451 template <int N>
452 static MultiArrayIndex
453 exec(const TinyVector <MultiArrayIndex, N> & stride, MultiArrayIndex x)
454 {
455 return stride[0] * x;
456 }
457 template <int N>
458 static MultiArrayIndex
459 exec(const TinyVector <MultiArrayIndex, N> & stride, MultiArrayIndex x, MultiArrayIndex y)
460 {
461 return stride[0] * x + stride[1] * y;
462 }
463 };
464
465 template <>
466 struct CoordinatesToOffest<UnstridedArrayTag>
467 {
468 template <int N>
469 static MultiArrayIndex
470 exec(const TinyVector <MultiArrayIndex, N> & /*stride*/, MultiArrayIndex x)
471 {
472 return x;
473 }
474 template <int N>
475 static MultiArrayIndex
476 exec(const TinyVector <MultiArrayIndex, N> & stride, MultiArrayIndex x, MultiArrayIndex y)
477 {
478 return x + stride[1] * y;
479 }
480 };
481
482 /********************************************************/
483 /* */
484 /* RelativeToAbsoluteCoordinate */
485 /* */
486 /********************************************************/
487
488 /* transforms a coordinate object with negative indices into the corresponding
489 'shape - abs(index)'.
490 */
491 template <int M>
492 struct RelativeToAbsoluteCoordinate
493 {
494 template <int N>
495 static void
496 exec(const TinyVector<MultiArrayIndex, N> & shape, TinyVector<MultiArrayIndex, N> & coord)
497 {
498 RelativeToAbsoluteCoordinate<M-1>::exec(shape, coord);
499 if(coord[M] < 0)
500 coord[M] += shape[M];
501 }
502 };
503
504 template <>
505 struct RelativeToAbsoluteCoordinate<0>
506 {
507 template <int N>
508 static void
509 exec(const TinyVector<MultiArrayIndex, N> & shape, TinyVector<MultiArrayIndex, N> & coord)
510 {
511 if(coord[0] < 0)
512 coord[0] += shape[0];
513 }
514 };
515
516 /********************************************************/
517 /* */
518 /* BorderTypeImpl */
519 /* */
520 /********************************************************/
521
522 // a border type is a compact bit-wise encoding of the fact that a
523 // given coordinate is at the border of the ROI. Each border corresponds
524 // to one bit in the encoding, e.g. the left, right, top, bottom borders
525 // of a 2D image are represented by bits 0 to 3 respectively.
526 // If a bit is set, the point in question is at the corresponding border.
527 // A code of all zeros therefore means that the point is in the interior
528 // of the ROI
529 template <unsigned int N, unsigned int DIMENSION=N-1>
530 struct BorderTypeImpl
531 {
532 typedef TinyVectorView<MultiArrayIndex, N> shape_type;
533
534 static unsigned int exec(shape_type const & point, shape_type const & shape)
535 {
536 unsigned int res = BorderTypeImpl<N, DIMENSION-1>::exec(point, shape);
537 if(point[DIMENSION] == 0)
538 res |= (1 << 2*DIMENSION);
539 if(point[DIMENSION] == shape[DIMENSION]-1)
540 res |= (2 << 2*DIMENSION);
541 return res;
542 }
543 };
544
545 template <unsigned int N>
546 struct BorderTypeImpl<N, 0>
547 {
548 typedef TinyVectorView<MultiArrayIndex, N> shape_type;
549 static const unsigned int DIMENSION = 0;
550
551 static unsigned int exec(shape_type const & point, shape_type const & shape)
552 {
553 unsigned int res = 0;
554 if(point[DIMENSION] == 0)
555 res |= (1 << 2*DIMENSION);
556 if(point[DIMENSION] == shape[DIMENSION]-1)
557 res |= (2 << 2*DIMENSION);
558 return res;
559 }
560 };
561
562 /********************************************************/
563 /* */
564 /* makeArrayNeighborhood */
565 /* */
566 /********************************************************/
567
568 // Create the offsets to all direct neighbors, starting from the given Level (=dimension)
569 // and append them to the given array. The algorithm is designed so that the offsets are
570 // sorted by ascending strides. This has two important consequences:
571 // * The first half of the array contains the causal neighbors (negative strides),
572 // the second half the anti-causal ones (positive strides), where 'causal' refers
573 // to all scan-order predecessors of the center pixel, and 'anticausal' to its successors.
574 // * For any neighbor k, its opposite (=point-reflected) neighbor is located at index
575 // 'N-1-k', where N is the total number of neighbors.
576 // The function 'exists' returns an array of flags that contains 'true' when the corresponding
577 // neighbor is inside the ROI for the given borderType, 'false' otherwise.
578 template <unsigned int Level>
579 struct MakeDirectArrayNeighborhood
580 {
581 template <class Array>
582 static void offsets(Array & a)
583 {
584 typedef typename Array::value_type Shape;
585
586 Shape point;
587 point[Level] = -1;
588 a.push_back(point);
589 MakeDirectArrayNeighborhood<Level-1>::offsets(a);
590 point[Level] = 1;
591 a.push_back(point);
592 }
593
594 template <class Array>
595 static void exists(Array & a, unsigned int borderType)
596 {
597 a.push_back((borderType & (1 << 2*Level)) == 0);
598 MakeDirectArrayNeighborhood<Level-1>::exists(a, borderType);
599 a.push_back((borderType & (2 << 2*Level)) == 0);
600 }
601 };
602
603 template <>
604 struct MakeDirectArrayNeighborhood<0>
605 {
606 template <class Array>
607 static void offsets(Array & a)
608 {
609 typedef typename Array::value_type Shape;
610
611 Shape point;
612 point[0] = -1;
613 a.push_back(point);
614 point[0] = 1;
615 a.push_back(point);
616 }
617
618 template <class Array>
619 static void exists(Array & a, unsigned int borderType)
620 {
621 a.push_back((borderType & 1) == 0);
622 a.push_back((borderType & 2) == 0);
623 }
624 };
625
626 // Likewise, create the offsets to all indirect neighbors according to the same rules.
627 template <unsigned int Level>
628 struct MakeIndirectArrayNeighborhood
629 {
630 template <class Array, class Shape>
631 static void offsets(Array & a, Shape point, bool isCenter = true)
632 {
633 point[Level] = -1;
634 MakeIndirectArrayNeighborhood<Level-1>::offsets(a, point, false);
635 point[Level] = 0;
636 MakeIndirectArrayNeighborhood<Level-1>::offsets(a, point, isCenter);
637 point[Level] = 1;
638 MakeIndirectArrayNeighborhood<Level-1>::offsets(a, point, false);
639 }
640
641 template <class Array>
642 static void exists(Array & a, unsigned int borderType, bool isCenter = true)
643 {
644 if((borderType & (1 << 2*Level)) == 0)
645 MakeIndirectArrayNeighborhood<Level-1>::exists(a, borderType, false);
646 else
647 MakeIndirectArrayNeighborhood<Level-1>::markOutside(a);
648
649 MakeIndirectArrayNeighborhood<Level-1>::exists(a, borderType, isCenter);
650
651 if((borderType & (2 << 2*Level)) == 0)
652 MakeIndirectArrayNeighborhood<Level-1>::exists(a, borderType, false);
653 else
654 MakeIndirectArrayNeighborhood<Level-1>::markOutside(a);
655 }
656
657 template <class Array>
658 static void markOutside(Array & a)
659 {
660 // Call markOutside() three times, for each possible offset at (Level-1)
661 MakeIndirectArrayNeighborhood<Level-1>::markOutside(a);
662 MakeIndirectArrayNeighborhood<Level-1>::markOutside(a);
663 MakeIndirectArrayNeighborhood<Level-1>::markOutside(a);
664 }
665
666 };
667
668 template <>
669 struct MakeIndirectArrayNeighborhood<0>
670 {
671 template <class Array, class Shape>
672 static void offsets(Array & a, Shape point, bool isCenter = true)
673 {
674 point[0] = -1;
675 a.push_back(point);
676 if(!isCenter) // the center point is not a neighbor, it's just convenient to do the enumeration this way...
677 {
678 point[0] = 0;
679 a.push_back(point);
680 }
681 point[0] = 1;
682 a.push_back(point);
683 }
684
685 template <class Array>
686 static void exists(Array & a, unsigned int borderType, bool isCenter = true)
687 {
688 a.push_back((borderType & 1) == 0);
689 if(!isCenter)
690 {
691 a.push_back(true);
692 }
693 a.push_back((borderType & 2) == 0);
694 }
695
696 template <class Array>
697 static void markOutside(Array & a)
698 {
699 // Push 'false' three times, for each possible offset at level 0, whenever the point was
700 // outside the ROI in one of the higher levels.
701 a.push_back(false);
702 a.push_back(false);
703 a.push_back(false);
704 }
705 };
706
707 // Create the list of neighbor offsets for the given neighborhood type
708 // and dimension (the dimension is implicitly defined by the Shape type)
709 // an return it in 'neighborOffsets'. Moreover, create a list of flags
710 // for each BorderType that is 'true' when the corresponding neighbor exists
711 // in this border situation and return the result in 'neighborExists'.
712 template <class Shape>
713 void
714 makeArrayNeighborhood(ArrayVector<Shape> & neighborOffsets,
715 ArrayVector<ArrayVector<bool> > & neighborExists,
716 NeighborhoodType neighborhoodType = DirectNeighborhood)
717 {
718 enum { N = Shape::static_size };
719
720 neighborOffsets.clear();
721 if(neighborhoodType == DirectNeighborhood)
722 {
723 MakeDirectArrayNeighborhood<N-1>::offsets(neighborOffsets);
724 }
725 else
726 {
727 Shape point; // represents the center
728 MakeIndirectArrayNeighborhood<N-1>::offsets(neighborOffsets, point);
729 }
730
731 unsigned int borderTypeCount = 1 << 2*N;
732 neighborExists.resize(borderTypeCount);
733
734 for(unsigned int k=0; k<borderTypeCount; ++k)
735 {
736 neighborExists[k].clear();
737 if(neighborhoodType == DirectNeighborhood)
738 {
739 MakeDirectArrayNeighborhood<N-1>::exists(neighborExists[k], k);
740 }
741 else
742 {
743 MakeIndirectArrayNeighborhood<N-1>::exists(neighborExists[k], k);
744 }
745 }
746 }
747
748 } // namespace detail
749
750 //@}
751
752 } // namespace vigra
753
754 #endif // VIGRA_MULTI_SHAPE_HXX

© Ullrich Köthe (ullrich.koethe@iwr.uni-heidelberg.de) Heidelberg Collaboratory for Image Processing, University of Heidelberg, Germany | html generated using doxygen and Python vigra 1.10.0 (Thu Jan 8 2015)