naturerobots / HSOS-SEP-PlantMap-2022

PlantMap Digital Logbook: A tool to support sustainable micro farming :link: https://naturerobots.github.io/HSOS-SEP-PlantMap-2022/
BSD 3-Clause "New" or "Revised" License
4 stars 5 forks source link

Beds and Plants API #171

Closed lsiebels closed 2 years ago

lsiebels commented 2 years ago

The end point for all beds in a garden as well as the end point for all plants in a bed must be adjusted.

We would define the health states as follows:

Health types

Log levels

Example for the beds end point:

{
   "beds":[
      {
         "id":1234,
         "location":1,
         "plant":"Beans",
         "variety":"White Beans",
         "plants":"http://localhost:3000/companies/{company_id}/gardens/{garden_id}/beds/1234/plants",
         //##########
         //Average values, should be calculated in the backend.
         "soil_humidty":70,
         "harvest":"1 week",
         "yield":100,
         "health":[
            {
               "type":"Watering",
               "loglevel":1
            },
            {
               "type":"Nutrient Deficiency",
               "loglevel":3
            },
            {
               "type":"Disease Detection",
               "loglevel":2
            }
         ]
         //##########
      },
      {
         "id":7777,
         "location":3,
         "plant":"Beans",
         "variety":"White Beans",
         "plants":"http://localhost:3000/companies/{company_id}/gardens/{garden_id}/beds/7777/plants",
         "soil_humidty":33,
         "harvest":"5 week",
         "yield":42,
         "health":[
            {
               "type":"Watering",
               "loglevel":3
            },
            {
               "type":"Nutrient Deficiency",
               "loglevel":1
            },
            {
               "type":"Disease Detection",
               "loglevel":3
            }
         ]
      }
   ]
}

Example for the end point of all plants in a bed:

{
   "plants":[
      {
         "id":222,
         "bedid":1234,
         "location":1,
         "plant":"Beans",
         "variety":"White Beans",
         "soil_humidty":70,
         "harvest":"1 week",
         "yield":100,
         "health":[
            {
               "type":"Watering",
               "loglevel":1
            },
            {
               "type":"Nutrient Deficiency",
               "loglevel":3
            },
            {
               "type":"Disease Detection",
               "loglevel":2
            }
         ]
      },
      {
         "id":555,
         "bedid":1234,
         "location":1,
         "plant":"Beans",
         "variety":"Rocket Beans",
         "soil_humidty":70,
         "harvest":"1 week",
         "yield":100,
         "health":[
            {
               "type":"Watering",
               "loglevel":1
            },
            {
               "type":"Nutrient Deficiency",
               "loglevel":3
            },
            {
               "type":"Disease Detection",
               "loglevel":2
            }
         ]
      }
   ]
}
lniehaus commented 2 years ago

The current state of the seerep server does not give multiple warn_levels for health. There is just one. Do we need to extend Seerep or how will the log level be collected? @jarkenau @pbrozi @mklpiening

MicrosoftTeams-image

lsiebels commented 2 years ago

The different log levels are needed for the health status row as it has been designed so far. If we cannot get this data and it is not foreseen that it will be available from SEEREP at a later date, we will have to reconsider the pop-up and the presentation in the table.

image

lsiebels commented 2 years ago

@pbrozi Is there any other data that we cannot obtain as we would need it or is the rest covered by SEEREP apart from the calculation of the average?

pbrozi commented 2 years ago

The rest should be covered by SEEREP.

mklpiening commented 2 years ago

Hi, sry for not responding. We initially imagined something else when we thought about the health status, but i like your approach much better. I'll then implement the health status you described with n entries to allow extensions in the future. So make sure that the types you want to access exist.

I'll additionally keep the warn level and warn message to be used as notifications. These messages will then contain assembled sentences with recommendations for actions. I thunk it would also be useful to show these messages (if they exist) in the popup)

lsiebels commented 2 years ago

@mklpiening How did you imagine the health status? We have simply assumed that a plant can have multiple statuses because, theoretically, a plant needs to be watered and can have a nutrient deficit. 😄

This is our current state as we have imagined the table with the beds and the average health status. If you click on an entry, the table with the corresponding plants of the bed appears. The table is the same. As Gerrit has already mentioned, we need to explain the letters somewhere. The explanation of the status is done as a pop-up by hovering over the respective letter. image

mklpiening commented 2 years ago

Yes, your idea makes sense. We initially (before the revisited mockups) thought of the deficiencies being analyzed by our Pipeline with just the combined result being put as a warn message into seerep. This would allow us to plan actions and send them to the gardeners (as one combined warn level). Our approach, however, would also create kind of a black box around the analysis part, which may be ok for unexpericned garneners, but unacceptable for experienced ones.

So lets go your way :)

lsiebels commented 2 years ago

@mklpiening @fmarahre @pbrozi Another question in general about the naming of health statuses nutrient deficiency. Can a deficiency be good? Actually, nutrients deficiency should be good and the loglevel danger would be something like "deficiency detected".

{
   "type":"Nutrient",
   "loglevel": "1 || 2 || 3"
}
mklpiening commented 2 years ago

Alright, I now added the mock data to the seerep dummy in a way, that we dont need to change the proto files, since seerep will be used for way more project than just our plant mapping ;)

image

I thereby left some fields intentionally empty or just did not add them, to simulate a computation that has not been finished yet - so you just need to show N/A here ^^

I also added some warn messages with further information for you to test the notification widget.

In terms of loglevel: the status good (1) to bad (3) is always meant from the gardeners perspective. So, while i also called it "nutrients" now, a good "nutrient deficiency" would mean, that the plant has little to no deficiency. This applies to all types of health status ("diseases", "nutrients", "water"). Also keep in mind, that we might add new health measurement types after your project has finished :)

lsiebels commented 2 years ago

@mklpiening Thanks a lot! 😎

@fmarahre The way it is displayed now, it doesn't seem to me that it can be expanded very well if there are more than three messages. It is designed for exactly three messages, isn't it? Then we would have to look at how to keep the display basically the same, but the table doesn't look weird with more than three badges.

mklpiening commented 2 years ago

Maybe make it configurable by the user, which status to show, if it isn't too complicated by now (in difference table columns for example). The remaining health measurements could then be shown in the popup, as it was in the mockup ;)

lsiebels commented 2 years ago

A good idea, we'll take a closer look. 🤓

lsiebels commented 2 years ago

@pbrozi Do you need more information or can it be implemented like this? If you need/want to change something in the JSON structure we have proposed so far, please give us feedback.

Anafabula commented 2 years ago

I remade the api as described in the issue in 7f4e955eb8e11fb6f4ce23b181d21e7d8ba8411c.

Some things:

  1. What is "location"? And where do I find it?
  2. The data you want costs the beds endpoint with the current dummy data 600 gRPC requests (100 per bed) and ~20 sec.
lsiebels commented 2 years ago
  1. Maybe @pbrozi knows more about what it is and where to find it.
  2. 20 seconds sounds long. 🤯 The data are all required. 😅 Is it possible to cache the plant data to reduce the polling time for a second call?
    And would it not be possible to stream the beds then bed by bed? These could then be displayed one by one. It would still take 20 seconds, but the user does not have to wait 20 seconds for the result.
Anafabula commented 2 years ago

Managed to make it stream the response for beds

pbrozi commented 2 years ago

As far as I know, location should be the id of the bed the plant is in. The bed-id can be obtained from the database.

jarkenau commented 2 years ago

@Anafabula @lucaslootalot The location should probably only be included with the plants, so that their latitude and longitude is known to display them on the map. I created a python script to convert the local coordinates, it is currently on the feat/plant-location-to-ll Branch. To use the script, the.ply files of the plants have to be download. https://github.com/naturerobots/HSOS-SEP-PlantMap-2022/blob/696f5e3a29b6ab3ed47af46fb6b33d1c0f0d6821/django/conversions/transform_coordinates.py#L28-L34 For the beds it doesn't really make sense to include a location, the use of a primary key makes even less sense.

lsiebels commented 2 years ago

@jarkenau If the coordinates are sent only with the plants, then we cannot display anything on the map until a bed is selected.

Also, if the location is the ID, I would leave that out as well.

jarkenau commented 2 years ago

Then you need some sort of array with all location from plants of a bed right? Could you give @Anafabula some hints of a useful structure for that?

lsiebels commented 2 years ago

The plants simply as an array with the coordinates for each bed.
However, the ID/location is needed to establish a relation between the markers on the map with the bed/plant entry in the table.

{
   "beds":[
      {
         "id":1234,
         "plant":"Beans",
         "variety":"White Beans",
         "plants":"http://localhost:3000/companies/{company_id}/gardens/{garden_id}/beds/1234/plants",
         "soil_humidty":70,
         "harvest":"1 week",
         "yield":100,
         "health":[
            {
               "type":"Watering",
               "loglevel":1
            },
            {
               "type":"Nutrient Deficiency",
               "loglevel":3
            },
            {
               "type":"Disease Detection",
               "loglevel":2
            }
         ],
         "plant_coords":[
            {
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
            {
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
            {
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
         ]
      }
   ]
}
jarkenau commented 2 years ago

So actually like this? :grin:

 "plant_coords":[
            {
               "id": 5
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
            {
               "id": 7
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
            {
               "id": 9
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
         ]
lsiebels commented 2 years ago

You can also add the bed ID to the plant coordinates. But then it must be the ID of the bed.

{
   "beds":[
      {
         "id":1234,
         "plant":"Beans",
         "variety":"White Beans",
         "plants":"http://localhost:3000/companies/{company_id}/gardens/{garden_id}/beds/1234/plants",
         "soil_humidty":70,
         "harvest":"1 week",
         "yield":100,
         "health":[
            {
               "type":"Watering",
               "loglevel":1
            },
            {
               "type":"Nutrient Deficiency",
               "loglevel":3
            },
            {
               "type":"Disease Detection",
               "loglevel":2
            }
         ],
         "plant_coords":[
            {
               "id": 1234,
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
            {
               "id": 1234,
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
            {
               "id": 1234,
               "lat": "52.31703822683545",
               "lng": "7.630155086517335"
            },
         ]
      }
   ]
}
lsiebels commented 2 years ago

The individual beds are streamed now, then it would look like this.

{
   "id":1234,
   "plant":"Beans",
   "variety":"White Beans",
   "plants":"http://localhost:3000/companies/{company_id}/gardens/{garden_id}/beds/1234/plants",
   "soil_humidty":70,
   "harvest":"1 week",
   "yield":100,
   "health":[
      {
         "type":"Watering",
         "loglevel":1
      },
      {
         "type":"Nutrient Deficiency",
         "loglevel":3
      },
      {
         "type":"Disease Detection",
         "loglevel":2
      }
   ],
   "plant_coords":[
      {
         "id":1234,
         "lat":"52.31703822683545",
         "lng":"7.630155086517335"
      },
      {
         "id":1234,
         "lat":"52.31703822683545",
         "lng":"7.630155086517335"
      },
      {
         "id":1234,
         "lat":"52.31703822683545",
         "lng":"7.630155086517335"
      }
   ]
}
Anafabula commented 2 years ago

Finally got this working for beds at least.

Does the plants endpoint need the coordinates as well? Since the pointcloud needed for the coordinates and the measurement for the rest are separate entries in the same list with different IDs idk how to find the pointcloud and measurement that belong to each other.

Is the id with the coordinates necessary? When you are iterating over the list, you probably also have the bed object the list belongs to, right? And it's the same for all entries.

{
    "id": 1,
    "plant": "mangold",
    "variety": "Lucullus",
    "plants": "http://localhost:8000/companies/1/gardens/1/beds/1/plants",
    "soil_humidty": 0.65,
    "harvest": "6 week",
    "yield": 451.1587423100285,
    "health": [
        {
            "type": "water",
            "loglevel": 1
        },
        {
            "type": "nutrients",
            "loglevel": 1
        },
        {
            "type": "diseases",
            "loglevel": 1
        }
    ],
    "plant_coords": [
        {
            "id": 1,
            "lat": 52.317118374885005,
            "lon": 7.630641577424838
        },
        {
            "id": 1,
            "lat": 52.317126563936476,
            "lon": 7.63064192673892
        },
        {
            "id": 1,
            "lat": 52.31712302636109,
            "lon": 7.630644944039591
        },
        {
            "id": 1,
            "lat": 52.31711997964676,
            "lon": 7.63064731009461
        },
        {
            "id": 1,
            "lat": 52.31712704329051,
            "lon": 7.63063668011326
        },
        {
            "id": 1,
            "lat": 52.31712378354576,
            "lon": 7.630640134315161
        },
        {
            "id": 1,
            "lat": 52.31712132599438,
            "lon": 7.630639366710759
        },
        {
            "id": 1,
            "lat": 52.317120526347296,
            "lon": 7.630643721642699
        },
        {
            "id": 1,
            "lat": 52.31712956545423,
            "lon": 7.630638593156597
        },
        {
            "id": 1,
            "lat": 52.31712744848517,
            "lon": 7.630632615014217
        },
        {
            "id": 1,
            "lat": 52.31712970346751,
            "lon": 7.6306337274749865
        },
        {
            "id": 1,
            "lat": 52.31713088086202,
            "lon": 7.630629429031545
        },
        {
            "id": 1,
            "lat": 52.31713306869006,
            "lon": 7.6306323772616125
        },
        {
            "id": 1,
            "lat": 52.317132238143095,
            "lon": 7.630635885026056
        },
        {
            "id": 1,
            "lat": 52.317135574785546,
            "lon": 7.630632900208948
        },
        {
            "id": 1,
            "lat": 52.317133874274475,
            "lon": 7.6306279124885075
        },
        {
            "id": 1,
            "lat": 52.317136018238564,
            "lon": 7.630627624787251
        },
        {
            "id": 1,
            "lat": 52.31713649371331,
            "lon": 7.630624918934556
        },
        {
            "id": 1,
            "lat": 52.31713860499551,
            "lon": 7.630625582818097
        },
        {
            "id": 1,
            "lat": 52.31713836176265,
            "lon": 7.630630193253661
        },
        {
            "id": 1,
            "lat": 52.31714137213531,
            "lon": 7.630627615585415
        },
        {
            "id": 1,
            "lat": 52.317139281590286,
            "lon": 7.630621477632502
        },
        {
            "id": 1,
            "lat": 52.31714197526744,
            "lon": 7.630622819624421
        },
        {
            "id": 1,
            "lat": 52.317142630980015,
            "lon": 7.630618189745578
        },
        {
            "id": 1,
            "lat": 52.31714439973367,
            "lon": 7.630624162514495
        },
        {
            "id": 1,
            "lat": 52.31714502480713,
            "lon": 7.630619321029704
        },
        {
            "id": 1,
            "lat": 52.317147296770386,
            "lon": 7.630621315776511
        },
        {
            "id": 1,
            "lat": 52.31714564428777,
            "lon": 7.630615467562744
        },
        {
            "id": 1,
            "lat": 52.31713485770003,
            "lon": 7.630633316716556
        },
        {
            "id": 1,
            "lat": 52.317119309585074,
            "lon": 7.630641707613764
        },
        {
            "id": 1,
            "lat": 52.31712554620787,
            "lon": 7.630640460191728
        },
        {
            "id": 1,
            "lat": 52.317119309585074,
            "lon": 7.630641707613764
        },
        {
            "id": 1,
            "lat": 52.317129860384526,
            "lon": 7.630633846824412
        }
    ]
}
lsiebels commented 2 years ago

Does the plants endpoint need the coordinates as well? Since the pointcloud needed for the coordinates and the measurement for the rest are separate entries in the same list with different IDs idk how to find the pointcloud and measurement that belong to each other.

Considering that it should also be possible to display the beds individually, I would say that the plants of Plants Endpoint also hold the coordinates. I understand your question though, it feels a bit duplicated.

Is the id with the coordinates necessary? When you are iterating over the list, you probably also have the bed object the list belongs to, right? And it's the same for all entries.

The coordinates are not absolutely necessary. The markers can also be created with the ID of the bed. If you have a better idea, go for it.

Anafabula commented 2 years ago

Considering that it should also be possible to display the beds individually, I would say that the plants of Plants Endpoint also hold the coordinates. I understand your question though, it feels a bit duplicated.

It's not about it being duplicated. We get a list like

geometries: [
    GeometryInfo {
        name: "crop 29",
        uuid: "e79b844a5f9e429ea84e1a9f5a700d40",
        r#type: "pointcloud",
        frame_id: "ground",
        stamp: Some(
            Timestamp {
                seconds: 1626967568,
                nanos: 0,
            },
        ),
        labels: [
            "5cc606b4b4904f1aaae9516844d7ec38",
            "b45b402cab6c47caba94176b5767c16b",
        ],
    },
    GeometryInfo {
        name: "crop 29",
        uuid: "4476adeaf36541408c042c71753b746b",
        r#type: "measurement",
        frame_id: "ground",
        stamp: Some(
            Timestamp {
                seconds: 1626967568,
                nanos: 0,
            },
        ),
        labels: [
            "5cc606b4b4904f1aaae9516844d7ec38",
            "b45b402cab6c47caba94176b5767c16b",
        ],
    },
    GeometryInfo {
        name: "crop 30",
        uuid: "f892521817554bf7a500e495b395e9ec",
        r#type: "pointcloud",
        frame_id: "ground",
        stamp: Some(
            Timestamp {
                seconds: 1626967568,
                nanos: 0,
            },
        ),
        labels: [
            "_8dcb2a39d81c476db37b5a3077200245",
            "b45b402cab6c47caba94176b5767c16b",
        ],
    },
    GeometryInfo {
        name: "crop 30",
        uuid: "fbeac53874034f3f8abe378ee495c875",
        r#type: "measurement",
        frame_id: "ground",
        stamp: Some(
            Timestamp {
                seconds: 1626967568,
                nanos: 0,
            },
        ),
        labels: [
            "_8dcb2a39d81c476db37b5a3077200245",
            "b45b402cab6c47caba94176b5767c16b",
        ],
    },
    GeometryInfo {
        name: "crop 31",
        uuid: "5162fef543f24b1b822533e5588bb8b9",
        r#type: "pointcloud",
        frame_id: "ground",
        stamp: Some(
            Timestamp {
                seconds: 1626967568,
                nanos: 0,
            },
        ),
        labels: [
            "7c6ba0354bbe4cb6a1404002498c1c7d",
            "8f460e87b27e4fb385e84f4a7dba677f",
        ],
    }
]

With no ids indicating for sure which pointcloud and measurement belong to each other. Idk if "name" is definite.

lsiebels commented 2 years ago

You can customize the structure as you think is easiest for you to generate the JSON. Also you can drop the ID from the plant coordinates.

The bed ID at the Plants endpoint is not needed, because we know the ID of the bed.