{"cells":[{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"d2f177c3-cd3a-9640-cb48-13d23819c255"},"outputs":[],"source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n\nfrom subprocess import check_output\nprint(check_output([\"ls\", \"../input\"]).decode(\"utf8\"))\n\n# Any results you write to the current directory are saved as output.\nimport sys\nimport os\nfrom 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"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"ba573231-2aae-4a10-8ceb-f7a455860990"},"outputs":[],"source":"# ================ 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\n\nSeaLionCoord = namedtuple('SeaLionCoord', ['tid', 'cls', 'x', 'y'])\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\n# 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)"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"ed9660d3-672c-08f3-24a3-82b2e477c209"},"outputs":[],"source":"coord"},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"96272907-0246-84ee-b5af-32a96a2c16db"},"outputs":[],"source":""}],"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}