---
# System prepended metadata

title: Manual Mapping Implementation

---

# Manual Gaze Mapping / Manual Mapping

---

Design: https://www.figma.com/file/rVdulet73OQ65GJAfYdXCL/PC-%3A-Redesign-P2-Development?type=design&node-id=743-108397&mode=design&t=4XkzNvtVjyMTj4qt-4

---

---

## Database Tables

### Postgresql table `manually_mapped_fixations` to store the mapped fixation coordinates and its details

Table `manually_mapped_fixations`

- use `mapping_id` as primary key
- unique index using `BRIN(enrichment_id, recording_id, start_time)`
- `enrichment_id` having foreign key reference on `project_enrichments (id)`
- `recording_id` having foreign key reference on `recordings (id)`

- Generation of `mapping_id`:

  - maximum `fixation_id` in `production` is `41369` and start time is `10583 seconds` or `2.94 hours`, around `4 fixations/sec`
  - maximum `fixation_id` in `staging` is `23725` and start time is `12481 seconds` or `3.4 hours`, around `2 fixations/sec`
  - to encode `fixation_id` to hexadecimal notation, maximum `fixation_id` should be `65535` to get the hexadecimal notation within 4 digits, which means `65535/4 secs` or `16383 secs` or `4.5 hours` of recording considering `4 fixations/sec`, neon supports up to 4 hours of recording time

  ```python

  # generate mapping id from enrichment_id, fixation_id and recording_id
  mapping_id = <group_of_8_digits_from_enrichment_id>-<group_of_4_digits_from_enrichment_id>-<group_of_8_digits_from_hex_notation_of_fixation_id>-<group_of_12_digits_from_recording_id>

  enrichment_id = 5465a465-d245-4fc9-b141-4d5d9dc98c1f
  recording_id = 13d8ab9e-491e-48d7-986a-6ee86bd4fbc7
  fixation_id = 1048575 (0x000fffff)

  mapping_id = 5465a465-d245-000f-ffff-6ee86bd4fbc7

  ```

- Retrieve `enrichment_id`, `recording_id`, `fixation_id` from `mapping_id`

  - Since mapping_id is `<group_of_8_digits_from_enrichment_id>-<group_of_4_digits_from_enrichment_id>-<group_of_8_digits_from_hex_notation_of_fixation_id>-<group_of_12_digits_from_recording_id>`
  - enrichment_id's prefix is <group_of_8_digits_from_enrichment_id>-<group_of_4_digits_from_enrichment_id> of mapping_id
  - fixation_id in hex notation form is <group_of_8_digits_from_hex_notation_of_fixation_id>
  - recording_id's suffix is <group_of_12_digits_from_recording_id>

  ```python

  mapping_id = 5465a465-d245-000f-ffff-6ee86bd4fbc7

  enrichment_id = 5465a465-d245-*
  recording_id = *-6ee86bd4fbc7
  fixation_id = 000fffff

  ```

| column_name   | column_description                                                                                     | column_type |
| ------------- | ------------------------------------------------------------------------------------------------------ | ----------- |
| mapping_id    | unique uuid generated using enrichment_id, fixation_id and recording_id                                | uuid        |
| enrichment_id | enrichment_id having foreign key reference on project_enrichments(id)                                  | uuid        |
| recording_id  | recording_id having foreign key reference on recordings(id)                                            | uuid        |
| fixation_id   | fixation id                                                                                            | integer     |
| x             | fixation x coordinate, scaled to the reference image size                                              | smallint    |
| y             | fixation y coordinate, scaled to the reference image size                                              | smallint    |
| start_time    | fixation start time in nanoseconds, matching with recording's `fixations.csv`'s `start timestamp [ns]` | bigint      |
| end_time      | fixation end time recording offset in nanoseconds) ie. 15s into recording = 15000000000                | bigint      |

| data_type | storage_size | range                                        |
| --------- | ------------ | -------------------------------------------- |
| uuid      | 16 bytes     |                                              |
| integer   | 4 bytes      | -2147483648 to +2147483647                   |
| smallint  | 2 bytes      | -32768 to +32767                             |
| bigint    | 8 bytes      | -9223372036854775808 to +9223372036854775807 |

```sql

CREATE TABLE manually_mapped_fixations (
  mapping_id uuid NOT NULL,
  enrichment_id uuid NOT NULL,
  recording_id uuid NOT NULL,
  fixation_id integer NOT NULL,
  x smallint NOT NULL,
  y smallint NOT NULL,
  start_time bigint NOT NULL,
  end_time bigint NOT NULL,
  CONSTRAINT pk_manually_mapped_fixations PRIMARY KEY (mapping_id),
  CONSTRAINT fk_manually_mapped_fixations_enrichment_id FOREIGN KEY (enrichment_id) REFERENCES public.project_enrichments(id) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT fk_manually_mapped_fixations_recording_id FOREIGN KEY (recording_id) REFERENCES public.recordings(id) ON DELETE RESTRICT ON UPDATE CASCADE
);

CREATE INDEX ix_mapped_fixations_enrichment_id_recording_id_start_time_end_time
ON manually_mapped_fixations
USING BRIN (enrichment_id, recording_id, start_time, end_time);


```

---

### Postgresql table `static_image_mapper` to store `static_image_id` that inherits from table `project_enrichments`

Table `static_image_mapper`

| column_name                      | column_description | column_type |
| -------------------------------- | ------------------ | ----------- |
| columns from project_enrichments |                    |             |
| .                                |                    |             |
| .                                |                    |             |
| .                                |                    |             |
| static_image_id               |                    | uuid        |

```sql

ALTER TYPE public."enrichment_kind" ADD VALUE 'static-image-mapper';


CREATE TABLE static_image_mapper (
static_image_id uuid references files(id),
kind public."enrichment_kind" CHECK (kind = 'static-image-mapper')
) INHERITS (project_enrichments);

```

---

---

## Endpoints

### Endpoint to create/update/get/delete static-image-mapper enrichment

`/v2/workspaces/{workspace_id}/projects/{project_id}/mappers/static/`

- Return `static_image_id` in `args` param of response to be in sync with the current frontend development

Supported methods:

- `POST` on `/mappers/static/` **KARTIKEY**

  Payload

  ```json
  {
    "name": "Untitled",
    "static_image_id": "23d2f16d-564c-274d-234f-00d7c49add32"
    "from_event_name": "recording.begin",
    "to_event_name": "recording.end"
  }
  ```

  Response

  ```json
  {
    "code": 201,
    "message": "201 CREATED",
    "request_id": "9a7ccbec5c8a4f45b0bb8690defd8197",
    "result": 
      "static_image_id": "23d2f16d-564c-274d-234f-00d7c49add32",
      "created_at": "2024-01-01T01:15:29.395379Z",
      "created_by_user_id": "b911f2b3-e9aa-4be5-a3bf-30251f94735d",
      "deleted_by_user_id": null,
      "from_event_name": "recording.begin",
      "id": "c4390f5c-1a3c-47f5-ab87-316996067598",
      "kind": "static-image-mapper",
      "name": "Untitled",
      "project_id": "4b6604f8-6caf-474c-b1be-49ad2cb11903",
      "slices": [],
      "status": {
        "COMPUTING": 0.0,
        "ERROR": 0.0,
        "READY": 1.0,
        "SUCCESS": 0.0
      },
      "to_event_name": "recording.end",
      "updated_at": "2024-01-01T01:15:29.395379Z"
    },
    "status": "success"
  }
  ```

  - Use to create a new `static-image-mapper`
  - Initially, only `name`, `kind`, `from_event_name`, `to_event_name` suffice to create a new enrichment

- `GET on /projects/{project_id}/enrichments/{enrichment_id}`
    - **TODO: return same api response format as current one, ie. with {"args": {"static_image_id": "23d2f16d-564c-274d-234f-00d7c49add32"}**


- `GET`on `/mappers/static/{static_enrichment_id}` **STAVROS**

  Response

  ```json
  {
    "code": 200,
    "message": "200 OK",
    "request_id": "a10c179e44f94ef8b7805bf8ed689387",
    "result": {
      "static_image_id": "23d2f16d-564c-274d-234f-00d7c49add32",
      "created_at": "2024-01-01T01:15:29.395379Z",
      "created_by_user_id": "b911f2b3-e9aa-4be5-a3bf-30251f94735d",
      "deleted_by_user_id": null,
      "from_event_name": "recording.begin",
      "id": "c4390f5c-1a3c-47f5-ab87-316996067598",
      "kind": "static-image-mapper",
      "name": "Untitled",
      "project_id": "4b6604f8-6caf-474c-b1be-49ad2cb11903",
      "slices": [],
      "status": {
        "COMPUTING": 0.0,
        "ERROR": 0.0,
        "READY": 1.0,
        "SUCCESS": 0.0
      },
      "to_event_name": "recording.end",
      "updated_at": "2024-01-01T01:16:49.987532Z"
    },
    "status": "success"
  }
  ```

- `PATCH`on `/mappers/static/{static_enrichment_id}`
**STAVROS**

  Payload

  ```json
  {
  "static_image_id": "23d2f16d-564c-274d-234f-00d7c49add32",
  "name": "new",
  "from_event_name": "recording.begin",
  "to_event_name": "recording.end"
  }
    ```

  Response

  ```json
  {
    "code": 200,
    "message": "200 OK",
    "request_id": "ffa1358280b942bbbe137a29baa5c053",
    "result": {
      "static_image_id": "23d2f16d-564c-274d-234f-00d7c49add32",
      "created_at": "2024-01-01T01:15:29.395379Z",
      "created_by_user_id": "b911f2b3-e9aa-4be5-a3bf-30251f94735d",
      "deleted_by_user_id": null,
      "from_event_name": "recording.begin",
      "id": "c4390f5c-1a3c-47f5-ab87-316996067598",
      "kind": "static-image-mapper",
      "name": "new",
      "project_id": "4b6604f8-6caf-474c-b1be-49ad2cb11903",
      "slices": [],
      "status": {
        "COMPUTING": 0.0,
        "ERROR": 0.0,
        "READY": 1.0,
        "SUCCESS": 0.0
      },
      "to_event_name": "recording.end",
      "updated_at": "2024-01-01T01:28:00.842571Z"
    },
    "status": "success"
  }
  ```

  - Use to update `static_image_id`, `name`, `from_event_name`, etc.

- `DELETE`on `/v2/workspaces/{workspace_id}/projects/{project_id}/enrichments/{static_enrichment_id}` (already implemented) **STAVROS (test it)**

  Response

  ```json
  {
    "code": 200,
    "message": "200 OK",
    "request_id": "b59da74421364f4c89788d6c95635b5b",
    "result": null,
    "status": "success"
  }
  ```

  - **No need** to implement `DELETE` on `/mappers/static/{static_enrichment_id}`

### Endpoint to add/update/get/delete mapped fixations for static-image-mapper enrichment

`/v2/workspaces/{workspace_id}/projects/{project_id}/mappers/static/{static_enrichment_id}/recordings/{recording_id}/fixations/{fixation_id}`

Supported methods:

- `GET` on `/mappers/static/{static_enrichment_id}/recordings/{recording_id}/fixations` **KARTIKEY**


  Response

  ```json
  {
    "code": 200,
    "message": "200 OK",
    "request_id": "281d44c5e27c46248ecd79c8f8f92b9c",
    "result": {
      "fixations": [
        {
          "end_timestamp": 1.984,
          "fixation_id": 1,
          "start_timestamp": 1.979,
          "x": 793,
          "y": 559
        },
        {
          "end_timestamp": 1.989,
          "fixation_id": 2,
          "start_timestamp": 1.984,
          "x": null,
          "y": null
        },
        {
          "end_timestamp": 1.994,
          "fixation_id": 3,
          "start_timestamp": 1.989,
          "x": null,
          "y": null
        }
      ],
      "mapped_fixations": 1,
      "total_fixations": 3
    },
    "status": "success"
  }
  ```

  - `GET` response will provide all the `fixations`, of the particular recording, between `from_event_name` and `to_event_name` of the enrichment, irrespective of whether it's mapped or not.
  - Fixations that are not mapped will be set to `null` by default which mean that there is no entry of these fixations in table `manually_mapped_fixations` and they are returned as null by backend
  -
```mermaid
flowchart LR
    subgraph s3
        fixations.json
    end
    subgraph db
        subgraph manually_mapped_fixations
            exists
            not_exists
        end
    end
    fixations.json
    -->|"id=1,x=null,y=null"|exists
    -->|"id=1,x=4,y=6"|response
    fixations.json
    -->|"id=1,x=null,y=null"|not_exists
    -->|"id=1,x=null,y=null"|response
```

      - Reads `fixation_id`, `start_timestamp`, `end_timestamp` from `recording_id`'s `fixations.json`'s columns `fixation id`, `start timestamp [ns]`, `end timestamp [ns]`
      - Reads `x` and `y` from table `manually_mapped_fixations` otherwise sets to `null`
      - Fetches data from `fixations.json` (s3 bucket)
      - Return data from `fixations.json` with x, y values replaced with the values from db and if db does not return value, replace it with null
      - `null` values in the fixation coordinates can be used by frontend to jump to the fixations that is not mapped yet
      - `mapped_fixations` is the number of mapped fixations, i.e., those have an entry in table `manually_mapped_fixations`
      - `total_fixations` is the total numbers of fixations present in that particular recording
      - `mapped_fixations` and `total_fixations` can be used by frontend to show the progress of the mapped fixations

- `PUT` on `/mappers/static/{static_enrichment_id}/recordings/{recording_id}/fixations/{fixation_id}` **ANYONE**


  Payload

  ```json
  {
    "x": 573,
    "y": 359
  }
  ```

  Response

  ```json
  {
    "end_timestamp": 1994320000,
    "fixation_id": 3,
    "start_timestamp": 1989320000,
    "x": 573,
    "y": 359
  }
  ```

  - No `POST` and `PATCH` is not needed, update/create a new entry in table `manually_mapped_fixations` using same
  - GET endpoint, by default, fetches start and end timestamp, in ns, from the `fixations.json`
  - Use `enrichment_id`, `fixation_id` and `recording_id` to build `mapping_id`
  - Use the `mapping_id` to create/update the row in table `manually_mapped_fixations`
  - We may not need to add `start_timestamp` and `end_timestamp` in table `manually_mapped_fixations` as we already have it in `fixations.json`!

- `DELETE` on `/mappers/static/{static_enrichment_id}/recordings/{recording_id}/fixations/{fixation_id}`
**ANYONE**

  Response

  ```json
  {
    "code": 200,
    "message": "200 OK",
    "request_id": "a69da74421364f4c89788d6c95635b5b",
    "result": null,
    "status": "success"
  }
  ```

  - Use `enrichment_id`, `fixation_id` and `recording_id` to build `mapping_id`
  - Delete entry for that `mapping_id`

- `POST` on `/mappers/static/{static_enrichment_id}/recordings/{recording_id}/fixations:clear` Only implement if needed by ui to clear all fixations

## Enrichment download

**STAVROS**

- Implement endpoint `/mappers/static/{static_enrichment_id}/export`
- `GET` on `/mappers/static/{static_enrichment_id}/export`
- Provides zip stream containing csv file
- CSV contains columns `section id`, `recording id`, `fixation id`, `start timestamp [ns]`, `end timestamp [ns]`, `duration [ms]`, `fixation detected in reference image`, `fixation x [px]`, `fixation y [px]`
- Downloaded csv will provide all the `fixations` of all the recordings of the enrichment irrespective of whether it's mapped or not.
- Fixations that are not mapped will be set to `""` by default which mean that there is no entry of these fixations in table `recording_gaze_on_aoi` and they are sent as empty string by backend
- `"fixation detected in reference image"` column in `fixations.csv` could be renamed to something else as now have three status based on `gaze_on_aoi` value in `recording_gaze_on_aoi` table in clickhouse.
  - if gaze_on_aoi == None, then fixation not mapped
  - if gaze_on_aoi == 0 , then fixation not in reference image
  - if gaze_on_aoi == 1, then fixation in reference image
- Export can be done similar to how RIM enrichment does.
- Files to be returned: `enrichment_info.txt`, `fixations.csv`, `static_image.jpeg`, `sections.csv`, and aoi related files similar to enrichment like RIM.

## Enrichment Progress

- `GET` on `/mappers/static/{static_enrichment_id}/recordings/{recording_id}/fixations` provides `checked_fixations` count and `total_fixations` count that can be used to show progress of the mapping

---