{"cells":[{"cell_type":"markdown","metadata":{"_cell_guid":"4f598afd-dd42-23df-464b-2f3c0310c565"},"source":"theeplusone's sea lion coordinate markeddown version"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"abf2813b-9ddc-a22d-e549-e09b4ffc2c98"},"outputs":[],"source":"\"\"\"Sea Lion Prognostication Engine\n\nhttps://www.kaggle.com/c/noaa-fisheries-steller-sea-lion-population-count\n\"\"\"\n\nimport sys\nimport os\n\n"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"db3d7f98-2617-f8d6-042f-30c22957918b"},"outputs":[],"source":"from collections import namedtuple\nimport operator\nimport glob\nimport csv \nfrom math import sqrt\n\nimport numpy as np\n\nimport PIL\nfrom PIL import Image, ImageDraw, ImageFilter\n\nimport skimage\nimport skimage.io\nimport skimage.measure\n\nimport shapely\nimport shapely.geometry\nfrom shapely.geometry import Polygon\n\n# Notes\n# cls -- sea lion class \n# tid -- train, train dotted, or test image id \n# _nb -- short for number\n# x, y -- don't forget image arrays organized row, col, channels\n#\n# With contributions from @bitsofbits ...\n#\n\n\n# ================ Meta ====================\n__description__ = 'Sea Lion Prognostication Engine'\n__version__ = '0.1.0'\n__license__ = 'MIT'\n__author__ = 'Gavin Crooks (@threeplusone)'\n__status__ = \"Prototype\"\n__copyright__ = \"Copyright 2017\"\n\n# python -c 'import sealiondata; sealiondata.package_versions()'\ndef package_versions():\n    print('sealionengine \\t', __version__)\n    print('python        \\t', sys.version[0:5])\n    print('numpy         \\t', np.__version__)\n    print('skimage       \\t', skimage.__version__)\n    print('pillow (PIL)  \\t', PIL.__version__)\n    print('shapely       \\t', shapely.__version__)\n\n\nSOURCEDIR = os.path.join('..', 'input')\n\nDATADIR = '.'\n\nVERBOSITY = namedtuple('VERBOSITY', ['QUITE', 'NORMAL', 'VERBOSE', 'DEBUG'])(0,1,2,3)\n"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"b88af81d-371b-e552-3f67-710f8eb4d1b8"},"outputs":[],"source":"SeaLionCoord = namedtuple('SeaLionCoord', ['tid', 'cls', 'x', 'y'])\n\n\n\nclass SeaLionData(object):\n    \n    def __init__(self, sourcedir=SOURCEDIR, datadir=DATADIR, verbosity=VERBOSITY.NORMAL):\n        self.sourcedir = sourcedir\n        self.datadir = datadir\n        self.verbosity = verbosity\n        \n        self.cls_nb = 5\n        \n        self.cls_names = (\n            'adult_males',\n            'subadult_males',\n            'adult_females',\n            'juveniles',\n            'pups',\n            'NOT_A_SEA_LION')\n            \n        self.cls = namedtuple('ClassIndex', self.cls_names)(*range(0,6))\n    \n        # backported from @bitsofbits. Average actual color of dot centers.\n        self.cls_colors = (\n            (243,8,5),          # red\n            (244,8,242),        # magenta\n            (87,46,10),         # brown \n            (25,56,176),        # blue\n            (38,174,21),        # green\n            )\n    \n            \n        self.dot_radius = 3\n        \n        self.train_nb = 947\n        \n        self.test_nb = 18636\n       \n        self.paths = {\n            # Source paths\n            'sample'     : os.path.join(sourcedir, 'sample_submission.csv'),\n            'counts'     : os.path.join(sourcedir, 'Train', 'train.csv'),\n            'train'      : os.path.join(sourcedir, 'Train', '{tid}.jpg'),\n            'dotted'     : os.path.join(sourcedir, 'TrainDotted', '{tid}.jpg'),\n            'test'       : os.path.join(sourcedir, 'Test', '{tid}.jpg'),\n            # Data paths\n            'coords'     : os.path.join(datadir, 'coords.csv'),  \n            }\n        \n        # From MismatchedTrainImages.txt\n        self.bad_train_ids = (\n            3, 7, 9, 21, 30, 34, 71, 81, 89, 97, 151, 184, 215, 234, 242, \n            268, 290, 311, 331, 344, 380, 384, 406, 421, 469, 475, 490, 499, \n            507, 530, 531, 605, 607, 614, 621, 638, 644, 687, 712, 721, 767, \n            779, 781, 794, 800, 811, 839, 840, 869, 882, 901, 903, 905, 909, \n            913, 927, 946)\n            \n        self._counts = None\n\n        \n    @property\n    def trainshort_ids(self):\n        return (0,1,2,4,5,6,8,10)  # Trainshort1\n        #return range(41,51)         # Trainshort2\n        \n    @property \n    def train_ids(self):\n        \"\"\"List of all valid train ids\"\"\"\n        tids = range(0, self.train_nb)\n        tids = list(set(tids) - set(self.bad_train_ids) )  # Remove bad ids\n        tids.sort()\n        return tids\n                    \n    @property \n    def test_ids(self):\n        return range(0, self.test_nb)\n    \n    def path(self, name, **kwargs):\n        \"\"\"Return path to various source files\"\"\"\n        path = self.paths[name].format(**kwargs)\n        return path        \n\n    @property\n    def counts(self) :\n        \"\"\"A map from train_id to list of sea lion class counts\"\"\"\n        if self._counts is None :\n            counts = {}\n            fn = self.path('counts')\n            with open(fn) as f:\n                f.readline()\n                for line in f:\n                    tid_counts = list(map(int, line.split(',')))\n                    counts[tid_counts[0]] = tid_counts[1:]\n            self._counts = counts\n        return self._counts\n\n    def rmse(self, tid_counts) :\n        true_counts = self.counts\n        \n        error = np.zeros(shape=[5] )\n        \n        for tid in tid_counts:\n            true_counts = self.counts[tid]\n            obs_counts = tid_counts[tid]\n            diff = np.asarray(true_counts) - np.asarray(obs_counts)\n            error += diff*diff\n        #print(error)\n        error /= len(tid_counts)\n        rmse = np.sqrt(error).sum() / 5\n        return rmse \n        \n\n    def load_train_image(self, train_id, border=0, mask=False):\n        \"\"\"Return image as numpy array\n         \n        border -- add a black border of this width around image\n        mask -- If true mask out masked areas from corresponding dotted image\n        \"\"\"\n        img = self._load_image('train', train_id, border)\n        if mask :\n            # The masked areas are not uniformly black, presumable due to \n            # jpeg compression artifacts\n            dot_img = self._load_image('dotted', train_id, border).astype(np.uint16).sum(axis=-1)\n            img = np.copy(img)\n            img[dot_img<40] = 0\n        return img\n   \n\n    def load_dotted_image(self, train_id, border=0):\n        return self._load_image('dotted', train_id, border)\n \n \n    def load_test_image(self, test_id, border=0):    \n        return self._load_image('test', test_id, border)\n\n\n    def _load_image(self, itype, tid, border=0) :\n        fn = self.path(itype, tid=tid)\n        img = np.asarray(Image.open(fn))\n        if border :\n            height, width, channels = img.shape\n            bimg = np.zeros( shape=(height+border*2, width+border*2, channels), dtype=np.uint8)\n            bimg[border:-border, border:-border, :] = img\n            img = bimg\n        return img\n    \n\n    def coords(self, train_id):\n        \"\"\"Extract coordinates of dotted sealions and return list of SeaLionCoord objects)\"\"\"\n        \n        # Empirical constants\n        MIN_DIFFERENCE = 16\n        MIN_AREA = 9\n        MAX_AREA = 100\n        MAX_AVG_DIFF = 50\n        MAX_COLOR_DIFF = 32\n       \n        src_img = np.asarray(self.load_train_image(train_id, mask=True), dtype = np.float)\n        dot_img = np.asarray(self.load_dotted_image(train_id), dtype = np.float)\n\n        img_diff = np.abs(src_img-dot_img)\n        \n        # Detect bad data. If train and dotted images are very different then somethings wrong.\n        avg_diff = img_diff.sum() / (img_diff.shape[0] * img_diff.shape[1])\n        if avg_diff > MAX_AVG_DIFF: return None\n        \n        img_diff = np.max(img_diff, axis=-1)   \n           \n        img_diff[img_diff<MIN_DIFFERENCE] = 0\n        img_diff[img_diff>=MIN_DIFFERENCE] = 255\n\n        sealions = []\n        \n        for cls, color in enumerate(self.cls_colors):\n            # color search backported from @bitsofbits.\n            color_array = np.array(color)[None, None, :]\n            has_color = np.sqrt(np.sum(np.square(dot_img * (img_diff > 0)[:,:,None] - color_array), axis=-1)) < MAX_COLOR_DIFF \n            contours = skimage.measure.find_contours(has_color.astype(float), 0.5)\n            \n            if self.verbosity == VERBOSITY.DEBUG :\n                print()\n                fn = 'diff_{}_{}.png'.format(train_id,cls)\n                print('Saving train/dotted difference: {}'.format(fn))\n                Image.fromarray((has_color*255).astype(np.uint8)).save(fn)\n\n            for cnt in contours :\n                p = Polygon(shell=cnt)\n                area = p.area \n                if(area > MIN_AREA and area < MAX_AREA) :\n                    y, x= p.centroid.coords[0] # DANGER : skimage and cv2 coordinates transposed?\n                    x = int(round(x))\n                    y = int(round(y))\n                    sealions.append( SeaLionCoord(train_id, cls, x, y) )\n                \n        if self.verbosity >= VERBOSITY.VERBOSE :\n            counts = [0,0,0,0,0]\n            for c in sealions :\n                counts[c.cls] +=1\n            print()\n            print('train_id','true_counts','counted_dots', 'difference', sep='\\t')   \n            true_counts = self.counts[train_id]\n            print(train_id, true_counts, counts, np.array(true_counts) - np.array(counts) , sep='\\t' )\n          \n        if self.verbosity == VERBOSITY.DEBUG :\n            img = np.copy(sld.load_dotted_image(train_id))\n            r = self.dot_radius\n            dy,dx,c = img.shape\n            for tid, cls, cx, cy in sealions :                    \n                for x in range(cx-r, cx+r+1) : img[cy, x, :] = 255\n                for y in range(cy-r, cy+r+1) : img[y, cx, :] = 255    \n            fn = 'cross_{}.png'.format(train_id)\n            print('Saving crossed dots: {}'.format(fn))\n            Image.fromarray(img).save(fn)\n     \n        return sealions\n        \n\n    def save_coords(self, train_ids=None): \n        if train_ids is None: train_ids = self.train_ids\n        fn = self.path('coords')\n        self._progress('Saving sealion coordinates to {}'.format(fn))\n        with open(fn, 'w') as csvfile:\n            writer =csv.writer(csvfile)\n            writer.writerow( SeaLionCoord._fields )\n            for tid in train_ids :\n                self._progress()\n                for coord in self.coords(tid):\n                    writer.writerow(coord)\n        self._progress('done')\n        \n    def load_coords(self):\n        fn = self.path('coords')\n        self._progress('Loading sea lion coordinates from {}'.format(fn))\n        with open(fn) as f:\n            f.readline()\n            return [SeaLionCoord(*[int(n) for n in line.split(',')]) for line in f]\n\n    \n            \n    def save_sea_lion_chunks(self, coords, chunksize=128):\n        self._progress('Saving image chunks...')\n        self._progress('\\n', verbosity=VERBOSITY.VERBOSE)\n        \n        last_tid = -1\n        \n        for tid, cls, x, y in coords :\n            if tid != last_tid:\n                img = self.load_train_image(tid, border=chunksize//2, mask=True)\n                last_tid = tid\n\n            fn = 'chunk_{tid}_{cls}_{x}_{y}_{size}.png'.format(size=chunksize, tid=tid, cls=cls, x=x, y=y)\n            self._progress(' Saving '+fn, end='\\n', verbosity=VERBOSITY.VERBOSE)\n            Image.fromarray( img[y:y+chunksize, x:x+chunksize, :]).save(fn)\n            self._progress()\n        self._progress('done')\n        \n            \n    def _progress(self, string=None, end=' ', verbosity=VERBOSITY.NORMAL):\n        if self.verbosity < verbosity: return\n        if not string :\n            print('.', end='')\n        elif string == 'done':\n            print(' done') \n        else:\n            print(string, end=end)\n        sys.stdout.flush()\n\n# end SeaLionData\n\n"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"37d5acc2-7e0a-59aa-10bb-61db3e3a13d8"},"outputs":[],"source":"# Count sea lion dots and compare to truth from train.csv\nsld = SeaLionData()\nsld.verbosity = VERBOSITY.VERBOSE\nfor tid in sld.trainshort_ids:\n    coord = sld.coords(tid)"}],"metadata":{"_change_revision":0,"_is_fork":false,"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.0"}},"nbformat":4,"nbformat_minor":0}