Touchscreen Filters

From Openmoko

(Difference between revisions)
Jump to: navigation, search
(Touchscreen Filters.)
 
m (Discussions: spelling)
 
(36 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Work in progress.
+
__TOC__
  
'''TODO:''' Complete the document.
+
{{InProgress|Work in progress.}}
'''TODO:''' Improve videos (with no music, please).
+
 
 +
 
 +
== Intro ==
  
 
In this document we describe the algorithms we use to make
 
In this document we describe the algorithms we use to make
the touchscreen behave well. By "well" we mean that we can:
+
the touchscreen behave well ( Check http://www.youtube.com/watch?v=4oPKaVno3jQ for an example ). By "well" we mean that we can:
  
 
* Get reliable clicks with the stylus and with the finger
 
* Get reliable clicks with the stylus and with the finger
* Avoid further filtering in user space
+
* Avoid further filtering in user space. It might be useful in some scenarios, for instance in a [http://lists.openmoko.org/pipermail/devel/2008-December/003912.html|Touch based dual boot menu].
* Avoid calibration in user-space. We believe X (and other programs) should be able to gather reliable data from /dev/input/eventX.
+
* Do linear transformation in kernel-space. X (and other programs) should be able to gather data from /dev/input/eventX. This step is optional and you can use [http://tslib.berlios.de/ tslib] for this task if you prefer to do so.
  
 
Openmoko developers and contributors have tried different approaches
 
Openmoko developers and contributors have tried different approaches
 
that have helped improve the touchscreen performance. As a result now
 
that have helped improve the touchscreen performance. As a result now
 
we have a touchscreen filtering framework that we are using in Linux
 
we have a touchscreen filtering framework that we are using in Linux
2.6. We describe the current working solution as of December 2008 as
+
2.6. We describe the current working solution as of January 2009 as
 
it is in the [http://git.openmoko.org/?p=kernel.git;a=shortlog;h=andy-tracking andy-tracking] branch of the Openmoko GIT repository.
 
it is in the [http://git.openmoko.org/?p=kernel.git;a=shortlog;h=andy-tracking andy-tracking] branch of the Openmoko GIT repository.
  
Line 20: Line 22:
  
 
We include videos showing how things improve as we add filters. In the
 
We include videos showing how things improve as we add filters. In the
videos we use a program that plots the points reported by the driver[3].
+
videos we use a program that [http://lists.openmoko.org/pipermail/devel/2008-October/002712.html plots the points reported by the driver]. The videos are not polished (you might want to turn down volume) but they illustrate our points.
 +
 
 +
== Our hardware ==
 +
 
 +
The FreeRunner (GTA02) uses the S3C2442 touchscreen controller, while the Neo1973 (GTA01) uses the S3C2410 one. For both devices we use [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/s3c2410_ts.c;hb=andy-tracking the same driver].
 +
 
 +
The driver can be used for other devices:
 +
 
 +
* HP iPAQ H1940 (H1930, H1935, H1940 and H1945) ( http://www.handhelds.org/projects/h1940.html ).
 +
* Acer N30
 +
 
 +
== We don't use pressure information ==
 +
 
 +
The only information we are using from the hardware is the reported X and
 +
Y coordinates. We are not using [http://lists.openmoko.org/pipermail/devel/2008-October/002728.html pressure information]. We could get
 +
the area of the contact to the touchscreen and we could tell between stylus,
 +
fingernail or thumb. The area information is reliable but we didn't manage
 +
to normalize the results across the screen as they varied widely depending
 +
on which corner of the screen is pressed, in a non-linear way.
 +
 
 +
== Why are we doing filtering in kernel space? ==
 +
 
 +
This is not new. An [http://svn.opentom.org/opentom/trunk/linux-2.4/drivers/char/s3c2410_ts.c early driver for this device] does some filtering (corrections, averaging, etc).
 +
 
 +
In the latest driver we decided to add a generic filtering framework
 +
that could be used by more touchscreen drivers. It also helps keeping the driver small,
 +
with no averaging in the same file.
 +
 
 +
We are aware of [http://tslib.berlios.de/ tslib] and we use it with
 +
the current stable kernel (2.6.24) but we also think that doing
 +
filtering in the kernel is a good idea, and in the latest kernel
 +
(2.6.28 - next stable) we are doing it and as a side effect we no
 +
longer need to depend on tslib (but we can use it if we need it).
 +
 
 +
Let's say we would like to deliver a TS event to user space each
 +
10 milliseconds. In the GTA02 with the current configuration the
 +
Analog/Digital conversion time of a sample is 0.4697 milliseconds,
 +
thus can get 18 samples for each event that we send to user-space.
 +
Sending the event (with 18 samples) takes us about 8.45
 +
milliseconds. Sometimes we even decide that the event should not be
 +
sent to user-space (because the hardware didn't provide reliable data),
 +
and our tests show that it's the right thing to do.
 +
In previous versions of the driver light taps would confuse the driver
 +
(that would report bad clicks) and this is no longer an issue.
 +
 
 +
Briefly stated: We think it's better if the driver sends only
 +
good data to the applications. If there are multiple distributions
 +
for a device (there are quite a few for the GTA02) they can share
 +
a tuned in-kernel configuration. We also save a lot of kernel mode/user
 +
mode transitions and data transmission
 +
allowing us to consider more samples for each reported event.
 +
 
 +
== Raw output ==
 +
 
 +
This is the raw output of the s3c2410 driver we are using ([http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/s3c2410_ts.c;hb=andy-tracking source]).
 +
 
 +
In this video:
 +
 
 +
* We draw a few numbers
 +
* We draw points while trying to keep the stylus still and we get stain because of the variance of the raw output data. We also try to use some pressure, we are not testing light pressure here.
 +
* We draw two lines, two curves
 +
* We draw points with the finger
 +
* We use the stylus again to draw face
 +
 
 +
Note that we paint the points after we have lifted the stylus/finger[3] but keep
 +
in mind that the kernel is sending events while we press the screen.
 +
 
 +
Video: http://www.youtube.com/watch?v=-ouBKOETiG8
 +
 
 +
 
 +
== Generic filters ==
 +
 
 +
Sources:
 +
 
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/ts_filter.c;hb=andy-tracking drivers/input/touchscreen/ts_filter.c]
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=include/linux/ts_filter.h;hb=andy-tracking include/linux/ts_filter.h]
 +
 
 +
Generic filters are not useful by themselves, they define an API that that actual
 +
filters have to implement. These are the operations that each filter
 +
can perform.
 +
<pre>
 +
 
 +
struct ts_filter_api {
 +
struct ts_filter * (*create)(void *config, int count_coords);
 +
void (*destroy)(struct ts_filter *filter);
 +
void (*clear)(struct ts_filter *filter);
 +
int (*process)(struct ts_filter *filter, int *coords);
 +
void (*scale)(struct ts_filter *filter, int *coords);
 +
};
 +
</pre>
 +
 
 +
clear: We call this operation when we need the filter to be in a clean state.
 +
For instance, when we initialize the filter or when we have a pen-up event.
 +
 
 +
process: This function is called when we need to send a
 +
sample to the filter, in our driver this happens when an A/D
 +
conversion ends. After that one of three things
 +
can happen:
 +
 
 +
# If the filter needs more samples to be gathered it returns a 0.
 +
# If it is ready to return a sample and there are no filters after itself, it returns 1.
 +
# If it is ready to return a sample and there are filters after itself it will pass the resulting sample to the next filter and it's up to that filter to decide what it returns. If a filter reaches an error it returns -1. In the GTA01/GTA02 this happens when many inputs are discarded because they are not good samples and we decide to give up trying to get good samples.
 +
 
 +
scale: This function is called for each filter after the filter chain returns 1.
 +
It is used to save expensive operations (such as divisions) in those cases when
 +
you can get away with doing them with aggregated data.
 +
 
 +
== Group filter ==
 +
 
 +
Source:
 +
 
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/ts_filter_group.c;hb=andy-tracking drivers/input/touchscreen/ts_filter_group.c]
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=include/linux/ts_filter_group.h;hb=stable-tracking include/linux/ts_filter_group.h]
 +
 
 +
This filter is useful to reject samples that are not reliable. We consider
 +
that a sample is not reliable if it deviates form the Majority.
 +
 
 +
(1) First we collect a set of S samples. (2) We make a copy of the values
 +
of each coordinate and process each dimension separately. For each
 +
dimension (X,Y):
 +
 
 +
* Sort the points
 +
* Points that are "close enough" are considered to be in the same set.
 +
* Choose the set with more elements. If more than "threshold" points are in this set we use the first and the last point of the set to define the valid range for this dimension [min, max], otherwise we discard all the points and go to step 1.
 +
*
 +
* We consider the unsorted S samples and '''try''' to feed them to the next filter in the chain in the same order they arrived. If one of the points of each sample is not in the allowed range for its dimension, we discard the sample.
 +
 
 +
http://www.youtube.com/watch?v=fEDjQLDkd-U
 +
 
 +
With this filter get rid of most of the noise and we make up for the absence of pressure information. Light pressure means bad data, and the bad data is discarded.
 +
 
 +
== Median averaging filter ==
 +
 
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/ts_filter_median.c;hb=stable-tracking drivers/input/touchscreen/ts_filter_median.c]
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=include/linux/ts_filter_median.h;hb=stable-tracking include/linux/ts_filter_median.h]
 +
 
 +
In this video we use both the group filter and the median filter.
 +
http://www.youtube.com/watch?v=17gempMRhBs
 +
 
 +
We sort incoming raw samples into an array of MEDIAN_SIZE length, discarding the
 +
oldest sample each time once we are full.  We then return the sum of the middle three
 +
samples for X and Y.
 +
 
 +
When this filter is cleared (via the ''clear'' function) it consumes MEDIAN_SIZE points before the first output takes place.
 +
After that it consumes a few samples for each output and this number of samples depends on how fast the pointer is moving.
 +
The idea is to discard more samples from the array if the pointer is moving fast.
 +
 
 +
This strongly rejects brief excursions away from a central point that is
 +
sticky in time compared to the excursion duration.
 +
 
 +
== Mean filter ==
 +
 
 +
 
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/ts_filter_mean.c;hb=stable-tracking drivers/input/touchscreen/ts_filter_mean.c]
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=include/linux/ts_filter_mean.h;hb=stable-tracking include/linux/ts_filter_mean.h]
 +
 
 +
In this video we include all the filters that are in this page.
 +
http://www.youtube.com/watch?v=4oPKaVno3jQ
 +
 
 +
Mean works well if the input data is already good quality, reducing + / - 1
 +
sample jitter when the stylus is still, or moving very slowly, without
 +
introducing abrupt transitions or reducing ability to follow larger
 +
movements.  If you set the threshold higher than the dynamic range of the
 +
coordinates, you can just use it as a simple mean average.
 +
 
 +
This filter has no effect if the samples are changing by more that the
 +
threshold set by averaging_threshold in the configuration.
 +
However while samples come in that don't go outside this threshold from
 +
the last reported sample, Mean replaces the samples with a simple mean
 +
of a configurable number of samples (set by bits_filter_length in config,
 +
which is 2^n, so 5 there makes 32 sample averaging).
 +
 
 +
In the current configuration we are using a mean with 4 points. It means that
 +
when the filter is cleared we need 4 samples for each output and then we
 +
only use 1 sample for each output until the filter is cleared.
 +
 
 +
== Linear calibration filter ==
 +
 
 +
Source :
 +
 
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=drivers/input/touchscreen/ts_filter_linear.c;hb=stable-tracking drivers/input/touchscreen/ts_filter_linear.c]
 +
* [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=include/linux/ts_filter_linear.h;hb=stable-tracking include/linux/ts_filter_linear.h]
 +
 
 +
This filter allow us to scale touchscreen values using calibration data. It also exposes the calibration constants using sysfs.
 +
By default it does not scale the coordinates.
 +
 
 +
== How things fit together ==
 +
 
 +
The following configuration that we are using for the GTA02 lives in the file [http://git.openmoko.org/?p=kernel.git;a=blob_plain;f=arch/arm/mach-s3c2440/mach-gta02.c;hb=stable-tracking arch/arm/mach-s3c2440/mach-gta02.c]. This configuration is not in the touchscreen driver, and the same driver can be used with different settings in other devices. The filters can also be used in other drivers.
 +
 
 +
<pre>
 +
/* touchscreen configuration */
 +
 
 +
static struct ts_filter_linear_configuration gta02_ts_linear_config = {
 +
.constants = {1, 0, 0, 0, 1, 0, 1}, /* don't modify coords */
 +
.coord0 = 0,
 +
.coord1 = 1,
 +
};
 +
 
 +
static struct ts_filter_group_configuration gta02_ts_group_config = {
 +
        .extent = 12,
 +
        .close_enough = 10,
 +
        .threshold = 6,        /* at least half of the points in a group */
 +
        .attempts = 10,
 +
};
 +
 
 +
static struct ts_filter_median_configuration gta02_ts_median_config = {
 +
        .extent = 20,
 +
        .decimation_below = 3,
 +
        .decimation_threshold = 8 * 3,
 +
        .decimation_above = 4,
 +
};
 +
 
 +
static struct ts_filter_mean_configuration gta02_ts_mean_config = {
 +
        .bits_filter_length = 2, /* 4 points */
 +
};
 +
 
 +
static struct s3c2410_ts_mach_info gta02_ts_cfg = {
 +
        .delay = 10000,
 +
        .presc = 0xff, /* slow as we can go */
 +
        .filter_sequence = {
 +
                [0] = &ts_filter_group_api,
 +
                [1] = &ts_filter_median_api,
 +
                [2] = &ts_filter_mean_api,
 +
[3] = &ts_filter_linear_api,
 +
        },
 +
        .filter_config = {
 +
                [0] = &gta02_ts_group_config,
 +
                [1] = &gta02_ts_median_config,
 +
                [2] = &gta02_ts_mean_config,
 +
[3] = &gta02_ts_linear_config,
 +
        },
 +
};
 +
</pre>
 +
 
 +
== Discussions ==
 +
 
 +
Let's link the discussions of the filters in mailing lists, tickets or forums.
 +
 
 +
* [http://lists.openmoko.org/pipermail/devel/2008-December/003888.html How do I make Xglamo read directly from the touchscreen?]
 +
 
 +
[[Category:Screen]]

Latest revision as of 16:02, 19 July 2009

Contents


In progress: This Work in progress. documents one or more features whose implementation are in progress.


[edit] Intro

In this document we describe the algorithms we use to make the touchscreen behave well ( Check http://www.youtube.com/watch?v=4oPKaVno3jQ for an example ). By "well" we mean that we can:

  • Get reliable clicks with the stylus and with the finger
  • Avoid further filtering in user space. It might be useful in some scenarios, for instance in a based dual boot menu.
  • Do linear transformation in kernel-space. X (and other programs) should be able to gather data from /dev/input/eventX. This step is optional and you can use tslib for this task if you prefer to do so.

Openmoko developers and contributors have tried different approaches that have helped improve the touchscreen performance. As a result now we have a touchscreen filtering framework that we are using in Linux 2.6. We describe the current working solution as of January 2009 as it is in the andy-tracking branch of the Openmoko GIT repository.

If you think we can improve something please send us feedback.

We include videos showing how things improve as we add filters. In the videos we use a program that plots the points reported by the driver. The videos are not polished (you might want to turn down volume) but they illustrate our points.

[edit] Our hardware

The FreeRunner (GTA02) uses the S3C2442 touchscreen controller, while the Neo1973 (GTA01) uses the S3C2410 one. For both devices we use the same driver.

The driver can be used for other devices:

[edit] We don't use pressure information

The only information we are using from the hardware is the reported X and Y coordinates. We are not using pressure information. We could get the area of the contact to the touchscreen and we could tell between stylus, fingernail or thumb. The area information is reliable but we didn't manage to normalize the results across the screen as they varied widely depending on which corner of the screen is pressed, in a non-linear way.

[edit] Why are we doing filtering in kernel space?

This is not new. An early driver for this device does some filtering (corrections, averaging, etc).

In the latest driver we decided to add a generic filtering framework that could be used by more touchscreen drivers. It also helps keeping the driver small, with no averaging in the same file.

We are aware of tslib and we use it with the current stable kernel (2.6.24) but we also think that doing filtering in the kernel is a good idea, and in the latest kernel (2.6.28 - next stable) we are doing it and as a side effect we no longer need to depend on tslib (but we can use it if we need it).

Let's say we would like to deliver a TS event to user space each 10 milliseconds. In the GTA02 with the current configuration the Analog/Digital conversion time of a sample is 0.4697 milliseconds, thus can get 18 samples for each event that we send to user-space. Sending the event (with 18 samples) takes us about 8.45 milliseconds. Sometimes we even decide that the event should not be sent to user-space (because the hardware didn't provide reliable data), and our tests show that it's the right thing to do. In previous versions of the driver light taps would confuse the driver (that would report bad clicks) and this is no longer an issue.

Briefly stated: We think it's better if the driver sends only good data to the applications. If there are multiple distributions for a device (there are quite a few for the GTA02) they can share a tuned in-kernel configuration. We also save a lot of kernel mode/user mode transitions and data transmission allowing us to consider more samples for each reported event.

[edit] Raw output

This is the raw output of the s3c2410 driver we are using (source).

In this video:

  • We draw a few numbers
  • We draw points while trying to keep the stylus still and we get stain because of the variance of the raw output data. We also try to use some pressure, we are not testing light pressure here.
  • We draw two lines, two curves
  • We draw points with the finger
  • We use the stylus again to draw face

Note that we paint the points after we have lifted the stylus/finger[3] but keep in mind that the kernel is sending events while we press the screen.

Video: http://www.youtube.com/watch?v=-ouBKOETiG8


[edit] Generic filters

Sources:

Generic filters are not useful by themselves, they define an API that that actual filters have to implement. These are the operations that each filter can perform.


	struct ts_filter_api {
		struct ts_filter * (*create)(void *config, int count_coords);
		void (*destroy)(struct ts_filter *filter);
		void (*clear)(struct ts_filter *filter);
		int (*process)(struct ts_filter *filter, int *coords);
		void (*scale)(struct ts_filter *filter, int *coords);
	};

clear: We call this operation when we need the filter to be in a clean state. For instance, when we initialize the filter or when we have a pen-up event.

process: This function is called when we need to send a sample to the filter, in our driver this happens when an A/D conversion ends. After that one of three things can happen:

  1. If the filter needs more samples to be gathered it returns a 0.
  2. If it is ready to return a sample and there are no filters after itself, it returns 1.
  3. If it is ready to return a sample and there are filters after itself it will pass the resulting sample to the next filter and it's up to that filter to decide what it returns. If a filter reaches an error it returns -1. In the GTA01/GTA02 this happens when many inputs are discarded because they are not good samples and we decide to give up trying to get good samples.

scale: This function is called for each filter after the filter chain returns 1. It is used to save expensive operations (such as divisions) in those cases when you can get away with doing them with aggregated data.

[edit] Group filter

Source:

This filter is useful to reject samples that are not reliable. We consider that a sample is not reliable if it deviates form the Majority.

(1) First we collect a set of S samples. (2) We make a copy of the values of each coordinate and process each dimension separately. For each dimension (X,Y):

  • Sort the points
  • Points that are "close enough" are considered to be in the same set.
  • Choose the set with more elements. If more than "threshold" points are in this set we use the first and the last point of the set to define the valid range for this dimension [min, max], otherwise we discard all the points and go to step 1.
  • We consider the unsorted S samples and try to feed them to the next filter in the chain in the same order they arrived. If one of the points of each sample is not in the allowed range for its dimension, we discard the sample.

http://www.youtube.com/watch?v=fEDjQLDkd-U

With this filter get rid of most of the noise and we make up for the absence of pressure information. Light pressure means bad data, and the bad data is discarded.

[edit] Median averaging filter

In this video we use both the group filter and the median filter. http://www.youtube.com/watch?v=17gempMRhBs

We sort incoming raw samples into an array of MEDIAN_SIZE length, discarding the oldest sample each time once we are full. We then return the sum of the middle three samples for X and Y.

When this filter is cleared (via the clear function) it consumes MEDIAN_SIZE points before the first output takes place. After that it consumes a few samples for each output and this number of samples depends on how fast the pointer is moving. The idea is to discard more samples from the array if the pointer is moving fast.

This strongly rejects brief excursions away from a central point that is sticky in time compared to the excursion duration.

[edit] Mean filter

In this video we include all the filters that are in this page. http://www.youtube.com/watch?v=4oPKaVno3jQ

Mean works well if the input data is already good quality, reducing + / - 1 sample jitter when the stylus is still, or moving very slowly, without introducing abrupt transitions or reducing ability to follow larger movements. If you set the threshold higher than the dynamic range of the coordinates, you can just use it as a simple mean average.

This filter has no effect if the samples are changing by more that the threshold set by averaging_threshold in the configuration. However while samples come in that don't go outside this threshold from the last reported sample, Mean replaces the samples with a simple mean of a configurable number of samples (set by bits_filter_length in config, which is 2^n, so 5 there makes 32 sample averaging).

In the current configuration we are using a mean with 4 points. It means that when the filter is cleared we need 4 samples for each output and then we only use 1 sample for each output until the filter is cleared.

[edit] Linear calibration filter

Source :

This filter allow us to scale touchscreen values using calibration data. It also exposes the calibration constants using sysfs. By default it does not scale the coordinates.

[edit] How things fit together

The following configuration that we are using for the GTA02 lives in the file arch/arm/mach-s3c2440/mach-gta02.c. This configuration is not in the touchscreen driver, and the same driver can be used with different settings in other devices. The filters can also be used in other drivers.

/* touchscreen configuration */

static struct ts_filter_linear_configuration gta02_ts_linear_config = {
	.constants = {1, 0, 0, 0, 1, 0, 1},	/* don't modify coords */
	.coord0 = 0,
	.coord1 = 1,
};

static struct ts_filter_group_configuration gta02_ts_group_config = {
        .extent = 12,
        .close_enough = 10,
        .threshold = 6,         /* at least half of the points in a group */
        .attempts = 10,
};

static struct ts_filter_median_configuration gta02_ts_median_config = {
        .extent = 20,
        .decimation_below = 3,
        .decimation_threshold = 8 * 3,
        .decimation_above = 4,
};

static struct ts_filter_mean_configuration gta02_ts_mean_config = {
        .bits_filter_length = 2, /* 4 points */
};

static struct s3c2410_ts_mach_info gta02_ts_cfg = {
        .delay = 10000,
        .presc = 0xff, /* slow as we can go */
        .filter_sequence = {
                [0] = &ts_filter_group_api,
                [1] = &ts_filter_median_api,
                [2] = &ts_filter_mean_api,
		[3] = &ts_filter_linear_api,
        },
        .filter_config = {
                [0] = &gta02_ts_group_config,
                [1] = &gta02_ts_median_config,
                [2] = &gta02_ts_mean_config,
		[3] = &gta02_ts_linear_config,
        },
};

[edit] Discussions

Let's link the discussions of the filters in mailing lists, tickets or forums.

Personal tools

Work in progress.

TODO: Complete the document. TODO: Improve videos (with no music, please).

In this document we describe the algorithms we use to make the touchscreen behave well. By "well" we mean that we can:

  • Get reliable clicks with the stylus and with the finger
  • Avoid further filtering in user space
  • Avoid calibration in user-space. We believe X (and other programs) should be able to gather reliable data from /dev/input/eventX.

Openmoko developers and contributors have tried different approaches that have helped improve the touchscreen performance. As a result now we have a touchscreen filtering framework that we are using in Linux 2.6. We describe the current working solution as of December 2008 as it is in the andy-tracking branch of the Openmoko GIT repository.

If you think we can improve something please send us feedback.

We include videos showing how things improve as we add filters. In the videos we use a program that plots the points reported by the driver[3].