Example #1
0
def dataframe_from_tubs(tubs):
    dfs = []
    for tub in tubs:
        df = pd.DataFrame(tub)
        name = Path(tub.base_path).name
        pref = os.path.join(tub.base_path, Tub.images()) + "/"
        df["cam/image_array"] = pref + df["cam/image_array"]
        dfs.append(df)
        #print( f"Tub {name}: {df['user/throttle'].min()} - {df['user/throttle'].max()}" )
    return pd.concat(dfs)
Example #2
0
    def clips_of_tub(self, tub_path):
        tub = Tub(tub_path)

        clips = []
        for record in tub:
            index = record['_index']
            images_relative_path = os.path.join(Tub.images(),
                                                record['cam/image_array'])
            record['cam/image_array'] = images_relative_path
            clips.append(record)

        return [clips]
Example #3
0
    def plot_predictions(self, cfg, tub_paths, model_path, limit, model_type):
        '''
        Plot model predictions for angle and throttle against data from tubs.

        '''
        import matplotlib.pyplot as plt
        import pandas as pd

        model_path = os.path.expanduser(model_path)
        model = dk.utils.get_model_by_type(model_type, cfg)
        # This just gets us the text for the plot title:
        if model_type is None:
            model_type = cfg.DEFAULT_MODEL_TYPE
        model.load(model_path)

        user_angles = []
        user_throttles = []
        pilot_angles = []
        pilot_throttles = []

        from donkeycar.parts.tub_v2 import Tub
        from pathlib import Path

        base_path = Path(os.path.expanduser(tub_paths)).absolute().as_posix()
        tub = Tub(base_path)
        records = list(tub)
        records = records[:limit]
        bar = IncrementalBar('Inferencing', max=len(records))

        for record in records:
            img_filename = os.path.join(base_path, Tub.images(),
                                        record['cam/image_array'])
            img = load_image(img_filename, cfg)
            user_angle = float(record["user/angle"])
            user_throttle = float(record["user/throttle"])
            pilot_angle, pilot_throttle = model.run(img)

            user_angles.append(user_angle)
            user_throttles.append(user_throttle)
            pilot_angles.append(pilot_angle)
            pilot_throttles.append(pilot_throttle)
            bar.next()

        angles_df = pd.DataFrame({
            'user_angle': user_angles,
            'pilot_angle': pilot_angles
        })
        throttles_df = pd.DataFrame({
            'user_throttle': user_throttles,
            'pilot_throttle': pilot_throttles
        })

        fig = plt.figure()

        title = "Model Predictions\nTubs: " + tub_paths + "\nModel: " + model_path + "\nType: " + model_type
        fig.suptitle(title)

        ax1 = fig.add_subplot(211)
        ax2 = fig.add_subplot(212)

        angles_df.plot(ax=ax1)
        throttles_df.plot(ax=ax2)

        ax1.legend(loc=4)
        ax2.legend(loc=4)

        plt.savefig(model_path + '_pred.png')
        plt.show()
Example #4
0
class Tubv2Format(DriveFormat):
    """ A class to represent a DonkeyCar Tub v2 on disc.

        Current assumptions:
            Tub records are 1 indexed and sequential with no gaps.
            We only care about editing steering and throttle.
            Steering and throttle should be clipped to -1/1.
    """
    def __init__(self, path):
        DriveFormat.__init__(self)

        if not os.path.exists(path):
            raise IOError(
                "Tubv2Format directory does not exist: {}".format(path))
        if not os.path.isdir(path):
            raise IOError(
                "Tubv2Format path is not a directory: {}".format(path))

        self.path = path
        self.tub = Tub(path, read_only=False)
        self.meta = self.tub.manifest.metadata  # Bug. tub.metadata doesn't get updated with info from disc
        self.deleted_indexes = self.tub.manifest.deleted_indexes
        print(f"Deleted: {self.deleted_indexes}")
        self.edit_list = set()
        self.shape = None

    def _load(self, path, image_norm=True, progress=None):

        records = {}
        indexes = []
        images = [
        ]  # Store images separately so we can easily write changed records back to the tub
        total = len(self.tub)
        for idx, rec in enumerate(self.tub):
            img_path = os.path.join(self.path, self.tub.images(),
                                    rec['cam/image_array'])
            try:
                img = Image.open(img_path)
                img_arr = np.asarray(img)
                if self.shape is None:
                    self.shape = img_arr.shape
            except Exception as ex:
                print(f"Failed to load image: {img_path}")
                print(f"   Exception: {ex}")
            records[idx] = rec
            indexes.append(idx)
            images.append(img_arr)
            progress(idx, total)
        self.records = records
        self.indexes = indexes
        self.images = images

    def load(self, progress=None):
        self._load(self.path, progress=progress)
        self.setClean()

    def update_line(self, line_num, new_rec):
        contents = json.dumps(new_rec, allow_nan=False, sort_keys=True)
        if contents[-1] == NEWLINE:
            line = contents
        else:
            line = f'{contents}{NEWLINE}'
        self.tub.manifest.current_catalog.seekable.update_line(
            line_num + 1, line)

    def save(self):
        if self.isClean():
            return

        self.tub.manifest.deleted_indexes = self.deleted_indexes

        for ix in self.edit_list:
            rec = self.records[ix]
            self.update_line(ix, rec)

        self.tub.manifest._update_catalog_metadata(update=True)
        self.edit_list.clear()
        self.setClean()

    def count(self):
        return len(self.records)

    def imageForIndex(self, index):
        idx = self.indexes[index]
        img = self.images[idx]
        if self.isIndexDeleted(index):
            # This grayed out image ends up looking ugly, can't figure out why
            tmp = img.mean(axis=-1, dtype=img.dtype, keepdims=False)
            tmp = np.repeat(tmp[:, :, np.newaxis], 3, axis=2)
            return tmp
        return img

    def get_angle_throttle(self, json_data):
        angle = float(json_data['user/angle'])
        throttle = float(json_data["user/throttle"])

        # If non-valid user entries and we have pilot data (e.g. AI), use that instead.
        if (0.0 == angle) and (0.0 == throttle):
            if "pilot/angle" in json_data:
                pa = json_data['pilot/angle']
                if pa is not None:
                    angle = float(pa)
            if "pilot/throttle" in json_data:
                pt = json_data['pilot/throttle']
                if pt is not None:
                    throttle = float(pt)

        return angle, throttle

    def actionForIndex(self, index):
        idx = self.indexes[index]
        rec = self.records[idx]
        angle, throttle = self.get_angle_throttle(rec)
        return [angle, throttle]

    def setActionForIndex(self, new_action, index):
        idx = self.indexes[index]
        rec = self.records[idx]
        angle, throttle = self.get_angle_throttle(rec)
        old_action = [angle, throttle]
        if not np.array_equal(old_action, new_action):
            if (rec["user/angle"] != new_action[0]) or (rec["user/throttle"] !=
                                                        new_action[1]):
                # Save the original values if not already done
                if "orig/angle" not in rec:
                    rec["orig/angle"] = rec["user/angle"]
                if "orig/throttle" not in rec:
                    rec["orig/throttle"] = rec["user/throttle"]

                rec["user/angle"] = new_action[0]
                rec["user/throttle"] = new_action[1]
                self.edit_list.add(idx)
                self.setDirty()

    def actionForKey(self, keybind, oldAction=None):
        oldAction = copy.copy(oldAction)
        if keybind == 'w':
            oldAction[1] += 0.1
        elif keybind == 'x':
            oldAction[1] -= 0.1
        elif keybind == 'a':
            oldAction[0] -= 0.1
        elif keybind == 'd':
            oldAction[0] += 0.1
        elif keybind == 's':
            oldAction[0] = 0.0
            oldAction[1] = 0.0
        else:
            return None
        return np.clip(oldAction, -1.0, 1.0)

    def deleteIndex(self, index):
        if index >= 0 and index < self.count():
            index += 1
            if index in self.deleted_indexes:
                self.deleted_indexes.remove(index)
            else:
                self.deleted_indexes.add(index)
            self.setDirty()

    def isIndexDeleted(self, index):
        if index >= 0 and index < self.count():
            index += 1
            return index in self.deleted_indexes
        return False

    def metaString(self):
        #{"inputs": ["cam/image_array", "user/angle", "user/throttle", "user/mode"], "start": 1550950724.8622544, "types": ["image_array", "float", "float", "str"]}
        ret = ""
        for k, v in self.meta.items():
            ret += "{}: {}\n".format(k, v)
        return ret

    def actionStats(self):
        stats = defaultdict(int)
        if self.count() > 0:
            actions = []
            for i in range(self.count()):
                act = self.actionForIndex(i)
                actions.append(act)
            stats["Min"] = np.min(actions)
            stats["Max"] = np.max(actions)
            stats["Mean"] = np.mean(actions)
            stats["StdDev"] = np.std(actions)
        return stats

    def supportsAuxData(self):
        return False

    def getAuxMeta(self):
        return None

    def addAuxData(self, meta):
        return None

    def auxDataAtIndex(self, auxName, index):
        return None

    def setAuxDataAtIndex(self, auxName, auxData, index):
        return False

    @classmethod
    def canOpenFile(cls, path):
        if not os.path.exists(path):
            return False
        if not os.path.isdir(path):
            return False

        meta_file = os.path.join(path, "manifest.json")
        if not os.path.exists(meta_file):
            return False

        return True

    @staticmethod
    def defaultInputTypes():
        return [{
            "name": "Images",
            "type": "numpy image",
            "shape": (120, 160, 3)
        }]

    def inputTypes(self):
        res = Tubv2Format.defaultInputTypes()
        if self.shape is not None:
            res[0]["shape"] = self.shape
        return res

    @staticmethod
    def defaultOutputTypes():
        return [{
            "name": "Actions",
            "type": "continuous",
            "range": (-1.0, 1.0)
        }]

    def outputTypes(self):
        res = []
        for act in ["user/angle", "user/throttle"]:
            display_name = act.split("/")[1]
            res.append({
                "name": display_name,
                "type": "continuous",
                "range": (-1.0, 1.0)
            })
        return res