Create nested dictionaries from similar value in a column and use value as key for dictionaries contain all rows with that value

Viewed 42

All, I have dataframe of bounding boxes in an image with each box being in a separate row. What I want to do is combine all the rows for a specific image.

                 image  xmin  ymin  xmax  ymax       label
0   bookstore_video0_40.jpg   763   899   806   940  pedestrian
3   bookstore_video0_40.jpg  1026   754  1075   797  pedestrian
4   bookstore_video0_40.jpg   868   770   927   822       biker
5   bookstore_video0_40.jpg   413  1010   433  1040  pedestrian
21  bookstore_video0_80.jpg   866   278   917   328  pedestrian
22  bookstore_video0_80.jpg   761   825   820   865       biker

What I am thinking is maybe make this into a single level nested dictionary, note I am open to any solution and I am not fixed to a dictionary, like this maybe.

{'bookstore_video0_40.jpg': {'xmin': 1066, 'ymin': 802, 'xmax': 1093, 'ymax': 829, 'label': 'pedestrian'}

But with all rows data with the image name as the key. My end goal is to then pass this to a function that will sequentially write each row's data into a file. That all being said I am lost on how to group the data into blocks. I did groupby(‘image’) but I could not figure out how to then turn that data into something like what I want. Does anyone have ideas? I am pretty sure this is easy and I have looked around but most of the responses I have seen were for much more complicated questions. Thanks in advance.

4 Answers

Maybe do You need is use dict with tuple/list applied on your groupby:

images_dict = dict(tuple(df.groupby('image')))

What about this?

new_dict = df.set_index('image').stack().groupby('image').apply(list).to_dict()


print(new_dict)
{'bookstore_video0_40.jpg': [763,
  899,
  806,
  940,
  'pedestrian',
  1026,
  754,
  1075,
  797,
  'pedestrian',
  868,
  770,
  927,
  822,
  'biker',
  413,
  1010,
  433,
  1040,
  'pedestrian'],
 'bookstore_video0_80.jpg': [866,
  278,
  917,
  328,
  'pedestrian',
  761,
  825,
  820,
  865,
  'biker']}

I wanted to do this as a comment and not an answer, but the link was too long:

I've written a voc writer. I just need to be able to pass data in such away that I can loop over it. I have a different data set where I do something similar but the data is already in a easy to consume form. For my project I've spending a lot of time editing, cleaning, transforming etc data. Not fun for me – Robi Sen

How does your voc writer work? Is it similar to the one that I linked to (i.e does is use OPP and has class methods for adding bbox data to the xml writer instance and then another method to save that instance to an xml file?) The idea I had in my last comment was not written out well, here is a better example of what I meant:

import pandas as pd

df = pd.DataFrame(dict(
    image = '40.jpg 40.jpg 40.jpg 40.jpg 80.jpg 80.jpg'.split(),
    xmin = [763, 1026, 868, 413, 866, 761],
    ymin = [899, 754, 770, 1010, 278, 825],
    xmax = [806, 1075, 927, 433, 917, 820],
    ymax = [940, 797, 822, 1040, 328, 865],
    label = 'pedestrian pedestrian biker pedestrian pedestrian biker'.split(),
))


for img in df['image'].unique():
    img_df = df[df['image']==img].drop(columns = 'image').reset_index()
    boxes = range(img_df.shape[0])
    print(img, '\n', img_df)

    # Ideally your custom voc writer can be inited here
    # with something like:

    # v_writer = VocWriter(f'path/{img[:-4]}.xml')
    print('New custom VOC XML Writer instance inited here!')

    for box in boxes:
        xmin = img_df.loc[box,'xmin']
        ymin = img_df.loc[box,'ymin']
        xmax = img_df.loc[box,'xmax']
        ymax = img_df.loc[box,'ymax']
        label = img_df.loc[box,'label']
        print(xmin, ymin, xmax, ymax)

        # Inside of this loop, 
        # you can add each box to your VocWriter object
        # something like:

        # v_writer.addObject(label, xmin, ymin, xmax, ymax)
        print('New bbox object added to writer instance here!')

    # Once you exit that inner loop,
    # you can save your data to your .xml file
    # with something like:

    # v_writer.save(f'path/{img[:-4]}.xml')
    print(f'path/{img[:-4]}.xml file saved here!')

step through the example in python tutor to get a better idea of my idea here

@Phillyclause89 here is a working example based on your example with the exception of reading in the actual XML files. Thank you very much. I suspect your answer will be useful since this is something that people doing machine vision run into when doing things like cutting up 4K images that have already been annotated.

import sys
import glob
import numpy as np
import pandas as pd
from lxml import etree
from pathlib import Path, PurePosixPath
from xml.etree import ElementTree as ET

df = pd.DataFrame(dict(
    image = '40.jpg 40.jpg 40.jpg 40.jpg 80.jpg 80.jpg'.split(),
    xmin = [763, 1026, 868, 413, 866, 761],
    ymin = [899, 754, 770, 1010, 278, 825],
    xmax = [806, 1075, 927, 433, 917, 820],
    ymax = [940, 797, 822, 1040, 328, 865],
    label = 'pedestrian pedestrian biker pedestrian pedestrian biker'.split(),
))


for img in df['image'].unique():
    img_df = df[df['image']==img].drop(columns = 'image').reset_index()
    boxes = range(img_df.shape[0])
    print(img, '\n', img_df)

    # Ideally your custom voc writer can be inited here
    # with something like:
    image = img
    # v_writer = VocWriter(f'path/{img[:-4]}.xml')
    print("New custom VOC Writer instance inited here!")

    depth = 3
    filepath = PurePosixPath('image')
    annotation = ET.Element('annotation')
    ET.SubElement(annotation, 'folder').text = str(image)
    ET.SubElement(annotation, 'filename').text = str(image)
    ET.SubElement(annotation, 'segmented').text = '0'
    size = ET.SubElement(annotation, 'size')
    ET.SubElement(size, 'width').text = str('0')
    ET.SubElement(size, 'height').text = str('0')
    ET.SubElement(size, 'depth').text = str('3')

    for box in boxes:
        xmin = img_df.loc[box,'xmin']
        ymin = img_df.loc[box,'ymin']
        xmax = img_df.loc[box,'xmax']
        ymax = img_df.loc[box,'ymax']
        label = img_df.loc[box,'label']
        print(xmin, ymin, xmax, ymax)

        # Inside of this loop, 
        # you can add each box to your VocWriter object
        # something like:

        ob = ET.SubElement(annotation, 'object')
        ET.SubElement(ob, 'name').text = str(img_df.loc[box,'label'])
        ET.SubElement(ob, 'pose').text = 'Unspecified'
        ET.SubElement(ob, 'truncated').text = '0'
        ET.SubElement(ob, 'difficult').text = '0'
        bbox = ET.SubElement(ob, 'bndbox')
        ET.SubElement(bbox, 'xmin').text = str(img_df.loc[box,'xmin'])
        ET.SubElement(bbox, 'ymin').text = str(img_df.loc[box,'ymin'])
        ET.SubElement(bbox, 'xmax').text = str(img_df.loc[box,'xmax'])
        ET.SubElement(bbox, 'ymax').text = str(img_df.loc[box,'ymax'])

    # Once you exit that inner loop,
    # you can save your data to your .xml file
    # with something like:

    # v_writer.save(f'path/{img[:-4]}.xml')
    print(".xml file saved here!")

    fileName = str(img)
    tree = ET.ElementTree(annotation)
    tree.write("./mergedxml/" + fileName + ".xml", encoding='utf8')

Related