60 min
Saito
  1. 1. 概要
    1. 1.1. 材料
    2. 1.2. 機能
  2. 2. アプリの作成
    1. 2.1. Frontend
    2. 2.2. Backend
  3. 3. アプリの作成
    1. 3.1. Frontend
    2. 3.2. Backend
  4. 4. アプリのデプロイ
    1. 4.1. Backend
    2. 4.2. Frontend
  5. 5. ダウンロード
  6. 6. Version

端末のカメラを使って写真を撮影したり、様々な画像解析を行ってクラウドのストレージに保存したりするアルバムアプリケーションをK5 Playgroundのカメラテンプレートで作ります。

概要

材料

  • テンプレート: Web Camera
  • APIロジック: Google Cloud Storage, Google Vision API
  • サービス: Google Cloud Storage, Google Vision API

機能

  • カメラの画像をプレビュー・撮影する。
  • Google Vision APIで様々な画像分析を行う。
    • Text detection
    • Face detection
    • Logo detection
    • Landmark detection
    • Label detection
    • Safe search detection
    • Image property
  • 分析した画像をGoogle Cloud Storageに格納する。
  • 分析した画像を一覧表示する。

アプリの作成

K5 Playgroundにアクセスします。

Frontend

  1. Web Cameraテンプレートを選択します。

Backend

  1. WebAPIsを選択して、次の2つのWebAPIを新規に追加します。

    1. google_storage/filesを追加してGETメソッドを有効化します。

    2. google_storage/files/{id}を追加してDELETEメソッドを有効化します。

  2. GET google_storage/filesを選択します。

    1. 右ペインのStorageからGoogle Cloud Storage Get File Listを追加します。
  3. DELETE google_storage/files/{id}を選択します。

    1. 右ペインのStorageからGoogle Cloud Storage Delete Filesを追加して次のように修正します。

      1
      var fileName = req.swagger.params.id.value;
      1
      next(apiResponse);
  4. POST /sample_annnotate_imagedata/imageを選択します。

    1. 右ペインのStorageからGoogle Cloud Storage Upload Filesを追加して次のように修正します。

      1
      var file = req.swagger.params.file.value;
      1
      2
      public: true,
      private: false,
      1
      contentType: file.mimetype,
  5. POST /sample_annnotate_imagedata/jsonのAPIロジックを修正します。

    1. POST /sample_annnotate_imagedata/jsonをクリックします。
  6. 次のエンドポイントのAPIロジックからbucketNameという変数を探して、Google Cloud Storageで利用しているバケット名を設定します。

    • POST /sample_annnotate_imagedata/image
    • POST /sample_annnotate_imagedata/json
    • GET /google_storage/files
    • DELETE /google_storage/files/{id}
  7. アプリケーションをダウンロードします。

アプリの作成

Frontend

  1. constants/AppConstants.jsを編集します。

    3種類のアクションを追加します。

    1
    2
    3
    4
    5
    6
    7
    const ActionTypes = {
    GET_ANNOTATE_IMAGE: 'GET_ANNOTATE_IMAGE',
    CLEAR_ANNOTATE_IMAGE: 'CLEAR_ANNOTATE_IMAGE',
    GET_ANALYZED_IMAGES: 'GET_ANALYZED_IMAGES', // get a list of image stored in Google Storage
    };

    export default ActionTypes;
  2. FluxのStoreを追加します。

    Storestores/AnalyzedImageStore.jsを新たに作成します。このストアにはGoogle Cloud Storageから取得した画像情報が格納されます。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import { ReduceStore } from 'flux/utils';
    import ActionTypes from '../constants/AppConstants';
    import AppDispatcher from '../dispatcher/AppDispatcher';

    class AnalyzedImageStore extends ReduceStore {
    getInitialState() {
    return {
    data: [],
    };
    }

    reduce(state, action) {
    switch (action.type) {
    case ActionTypes.GET_ANALYZED_IMAGES: {
    return {
    data: action.data,
    };
    }
    default: {
    return state;
    }
    }
    }
    }
    export default new AnalyzedImageStore(AppDispatcher);
  3. actions/AnnotateImageActionCreators.jsの編集。

    getImagesdeleteImageを追加します。Google Cloud Storageの画像の取得・削除に使います。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const AnnotateImageActionCreators = {
    // ~~ code omitted ~~
    getImages({ resolve }) {
    api.get(
    '/google_storage/files'
    ).then((response) => {
    AppDispatcher.dispatch({
    type: ActionTypes.GET_ANALYZED_IMAGES,
    data: response.data.map(file => file.metadata),
    });
    resolve();
    }).catch(() => {
    // @todo handle error
    });
    },
    deleteImage(imageId) {
    api.delete(
    `/google_storage/files/${imageId}`
    ).then((response) => {
    this.getImages({
    resolve: () => {},
    });
    }).catch(() => {
    // @todo handle error
    });
    },
  4. アプリにアルバムページを追加します。

    1. FluxのContainerコンポーネントを追加します。Containerコンポーネントは、Storeに格納・更新された情報をリアルタイムに受け取ります。

      components/container/AnalyzedImageAlbumContainer.jsという名前のContainerコンポーネントを新規追加します。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      import React, { Component } from 'react';
      import { Container } from 'flux/utils';
      import AnalyzedImageAlbumPage from '../pages/AnalyzedImageAlbumPage';
      import AnalyzedImageStore from '../../stores/AnalyzedImageStore';

      class AnalyzedImageAlbumContainer extends Component {

      static getStores() {
      return [
      AnalyzedImageStore,
      ];
      }

      static calculateState() {
      return {
      analyzedImages: AnalyzedImageStore.getState(),
      };
      }

      render() {
      const {
      ...other
      } = this.props;

      return (
      <div className="report-container" style={{ height: '100%' }}>
      <AnalyzedImageAlbumPage
      images={this.state.analyzedImages.data}
      {...other}
      />
      </div>
      );
      }
      }

      export default Container.create(AnalyzedImageAlbumContainer);
    2. ページのコンポーネントを追加します。

      components/pages/AnalyzedImageAlbumPage.jsコンポーネントを追加します。このコンポーネントは、react-markdownモジュールを使用しています。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      import React, { Component, PropTypes } from 'react';
      import { Link } from 'react-router';
      import ReactMarkdown from 'react-markdown';
      import { ItemsWrapperResponsive003 } from 'material-ui-fj/sublayouts';
      import { Constants } from '../../constants/AppConstants';
      import AnnotateImageActionCreators from '../../actions/AnnotateImageActionCreators';
      import { VerticalCard003 } from 'material-ui-fj/Card';
      import { HorizontalLayout, VerticalLayout } from 'material-ui-fj/sublayouts';
      import { BorderedFlatButton003 } from 'material-ui-fj/Button';

      export default class AnalyzedImageAlbumPage extends Component {
      static propTypes = {
      images: PropTypes.arrayOf(
      PropTypes.object,
      ).isRequired,
      };

      static defaultProps = {};

      static contextTypes = {
      muiFjTheme: PropTypes.object.isRequired,
      };

      static getStyles(context) {
      const { palette } = context.muiFjTheme;
      const styles = {
      root: {
      },
      center: {
      maxWidth: 1200,
      margin: '24px auto',
      },
      itemsWrapperInner: {
      marginTop: 0,
      },
      cardContent: {
      paddingBottom: 12,
      },
      cardHighlighted: {
      fontSize: 18,
      fontWeight: 300,
      color: palette.primary1Color,
      },
      itemsFooter: {
      margin: '0 24px',
      },
      top: {
      overflow: 'hidden',
      },
      };
      return styles;
      }

      state = { }

      handleDeleteImage = (imageName) => {
      AnnotateImageActionCreators.deleteImage(imageName);
      }

      render() {
      const {
      images,
      } = this.props;

      const styles = AnalyzedImageAlbumPage.getStyles(this.context);

      const deleteButton = (imageName) => (
      <BorderedFlatButton003
      label="DELETE"
      onTouchTap={() => this.handleDeleteImage(imageName)}
      />
      );

      const downloadButton = (downloadUrl) => (
      <BorderedFlatButton003
      label="DOWNLOAD"
      href={downloadUrl}
      />
      );

      const card = value => {
      const imageDescription =
      `
      * size : ${value.size}
      * time created: ${value.timeCreated}
      `;

      const imageSrc = `https://storage.googleapis.com/${value.bucket}/${value.name}`;

      return (
      <VerticalLayout style={styles.container}>
      <VerticalCard003
      key={value.id}
      imageAlt={value.name}
      imageSrc={imageSrc}
      title={value.name}
      subtitle=''
      description={
      <ReactMarkdown
      className="react-markdown"
      source={imageDescription}
      escapeHtml
      />
      }
      linkTo=''
      contentStyle={styles.cardContent}
      highlighted=""
      highlightedStyle={styles.cardHighlighted}
      />
      <VerticalLayout>
      <HorizontalLayout alignX={'center'}>
      {deleteButton(value.name)}
      {downloadButton(value.mediaLink)}
      </HorizontalLayout>
      </VerticalLayout>
      </VerticalLayout>
      );
      };

      const cards = (
      images.map(value => card(value))
      );

      return (
      <div style={styles.center}>
      <ItemsWrapperResponsive003
      itemsWrapperInnerStyle={styles.itemsWrapperInner}
      >
      {cards}
      </ItemsWrapperResponsive003>
      </div>
      );
      }
      }
    3. components/pages/WebCameraPages.jsを修正してアルバムページを追加します。

      1. import文をファイルの上部に追加します。
      1
      2
      import AnalyzedImageAlbumContainer from '../containers/AnalyzedImageAlbumContainer';
      import Divider from 'material-ui/Divider';
      1. 先ほど作成したAnalyzedImageAlbumContainerコンポーネントを次のように追加します。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const contents = [
      <AnnotateImageContainer type="FACE_DETECTION" {...other} />,
      <AnnotateImageContainer type="LABEL_DETECTION" {...other} />,
      <AnnotateImageContainer type="TEXT_DETECTION" {...other} />,
      <AnnotateImageContainer type="LANDMARK_DETECTION" {...other} />,
      <AnnotateImageContainer type="LOGO_DETECTION" {...other} />,
      <AnnotateImageContainer type="SAFE_SEARCH_DETECTION" {...other} />,
      <AnnotateImageContainer type="IMAGE_PROPERTIES" {...other} />,
      <div />,
      <AnalyzedImageAlbumContainer {...other} />,
      ];
      1. メニューに作成したページを追加します。

        1
        2
        3
        4
        5
        6
        7
        8
            const left = (
        // ~~ code omitted ~~
        <MenuItem value={4} primaryText="Logo" />
        <MenuItem value={5} primaryText="Safe Search" />
        <MenuItem value={6} primaryText="Image Properties" />
        <Divider />
        <MenuItem value={8} primaryText="Album" />
        </SidebarMenu>
      2. handleClickを修正します。

      Before showing the album page, the application needs to reflesh image list.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      handleClick = (event, menuItem, index) => {
      AnnotateImageActionCreators.clear();
      if (index === 8) {
      // Reflesh image list
      AnnotateImageActionCreators.getImages({
      resolve: () => this.setState({ displayPage: index }),
      });
      } else {
      this.setState({ displayPage: index });
      }
      }

Backend

編集の必要はありません。

アプリのデプロイ

Backend

1
2
cd backend
cf push [APP_NAME]

Frontend

1
2
3
4
5
6
7
cd frontend
npm install
npm install --save react-markdown
set API_URL=[Backend URL] # for Windows
export API_URL=[Backend URL] # for Mac/Linux
npm run build
cf push [APP_NAME] -p public

ダウンロード

Version

  • Web Camera Template: v1.0.0