読者です 読者をやめる 読者になる 読者になる

Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

post-checkoutフックを使ってブランチ切り替え時にちょっとだけ自動でDB環境をセットアップできるようにした

こんにちは、杉山です。

最近、ブランチ切り替えの度にDBの構造が違ってたりするので、切り替えるたびにDBマイグレーションしたりダンプとって読み込んだりとかいうことをやってました。手動でやるのがそろそろだるくなってきたのでこれを機に自動化したいと思います。

続きを読む

ECSのcloudformationのtemplateを作成

こんにちは、貴子です。
dockerを使ったコンテナ化をしたいというプロジェクトがあったので、ECSの導入検討をしました。
その際に作成したcloudformationのtemplateを載せます。

ECSを選んだ理由は?

AWSでdockerのサービスは2つ、AWS Elastic BeanstalkとECSがあります。
何がどう違うの?というところが解らなかったので、AWSのソリューションアーキテクトの方に弊社へ来ていただいてサービスのご紹介を頂きました。
Beanstalkの中でECSが動いているので大きな違いはありませんが、Beanstalkの方は元々PaaSなので、色々元々組み込まれて便利に使え、.ebextensionsというYAMLで管理できるようになっていいます。
ECSはdockerに特化したもので、最新機能はECSの方が組み込まれるのが早いそうです。
.ebextensionsを使っていくと何でもできちゃうので運用めっちゃ大変そうだなーと思って、シンプルにECSを試しました。

ECSについては、公式サイトの紹介の動画 Amazon EC2 Container Service (Docker コンテナ管理) | AWS が解りやすいです。

ECSのtemplate

cloudformation

弊社ではcloudformationは1枚のjsonファイルではなく、base.template・dev.template・dev.elb.template・staging.template…とのように分けて作って管理しているので、ecsのtemplateも同様に管理します。 cloudformationのstack policyは、意図せずに削除とかリプレースがかかると嫌なので、基本的にそれをDenyにするものを設定しています。
全て同じtemplateに書くとdevのインスタンスをリプレースしたいだけなのに、
productionやstagingのstack policyも変わってしまうので、それを避けるためにある程度分けて管理しています。

ECSのtemplate

VPCやSubnet・InternetGateway等は作成済みとします。

1. ecs.cluster.template

ECSのClusterの登録です。今回はdevサーバーを作成し、そこにnginxのコンテナを起動させます。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "ecs cluster stack",
  "Resources" : {
    "Dev01": {
      "Type": "AWS::ECS::Cluster"
    }
  }
}
2. ecs.task.definitions.template

TaskDefinitions = コンテナ情報を登録します。
docker hubからの取得ではなく、弊社で立てたPrivateのdocker registryから取得します。
ECRも出ましたが、オレゴンバージニアアイルランドに繋ぎに行くよりかは
東京リージョン内に自前で立てたほうが早かったので、こちらにしました。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "ecs task stack",
  "Parameters" : {
    "NginxContainerName" : {
      "Type" : "String",
      "Default": "nginx"
    },
    "NginxImage" : {
      "Type" : "String",
      "Default": "【Private docker registryドメイン】/【コンテナ名】:【コンテナバージョン】"
    },
  "Resources" : {
    "Nginx": {
      "Type": "AWS::ECS::TaskDefinition",
      "Properties" : {
        "ContainerDefinitions" : [ {
          "Name": { "Ref" : "NginxContainerName" },
          "Image": { "Ref" : "NginxImage" },
          "PortMappings":[ {
            "ContainerPort": 80,
            "HostPort": 80
          } ],
         "Cpu": "1024",
         "Memory":"500"
        } ]
      }
    }
  }
}
3. ecs.role.template

EC2インスタンスとECSのIAMを登録します。
ECSがEC2インスタンスを利用するので、EC2インスタンスにもIAMが必要と思っていなくて、結構躓きました。  

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "ecs iam role stack",
  "Resources" : {
    "EC2Role": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [ {
            "Effect": "Allow",
            "Principal": {
              "Service": [ "ec2.amazonaws.com" ]
            },
            "Action": [ "sts:AssumeRole" ]
          } ]
        },
        "Path": "/",
        "Policies": [ {
          "PolicyName": "ecs-service",
          "PolicyDocument": {
            "Statement": [ {
              "Effect": "Allow",
              "Action": [
                "ecs:CreateCluster",
                "ecs:DeregisterContainerInstance",
                "ecs:DiscoverPollEndpoint",
                "ecs:Poll",
                "ecs:RegisterContainerInstance",
                "ecs:StartTelemetrySession",
                "ecs:Submit*",
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage"
              ],
              "Resource": "*"
            } ]
          }
        } ]
      }
    },
    "ECSServiceRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [ {
            "Effect": "Allow",
              "Principal": {
                "Service": [ "ecs.amazonaws.com" ]
              },
              "Action": [ "sts:AssumeRole" ]
            }
          ]
        },
        "Path": "/",
        "Policies": [ {
          "PolicyName": "ecs-service",
          "PolicyDocument": {
            "Statement": [ {
               "Effect": "Allow",
               "Action": [
                 "ec2:AuthorizeSecurityGroupIngress",
                 "ec2:Describe*",
                 "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                 "elasticloadbalancing:Describe*",
                 "elasticloadbalancing:RegisterInstancesWithLoadBalancer"
               ],
               "Resource": "*"
            } ]
          }
        } ]
      }
    }
  }
}
4. ecs.dev01.template

インスタンスの作成には2つポイントがあります。
 (a).まずECS用のAMIを使わなければいけないこと
 (b).UserDataで ECS_CLUSTERの名前を設定しなくてはいけないこと
ずっとUserDataをdefaultで作成を試していて、起動してもECSのコンソール画面に表示されなくて、何故なのかを見つけるのが大変でした。。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "dev ecs-ec2 stack",
  "Parameters" : {
    "AvailabilityZone" : {
      "Type" : "AWS::EC2::AvailabilityZone::Name",
      "Default": "ap-northeast-1c"
    },
    "ImageId" : {
      "Type" : "AWS::EC2::Image::Id",
      "Default": "ami-065a6b68"
    },
    "InstanceType" : {
      "Type" : "String",
      "Default": "t2.medium"
    },
    "KeyName" : {
      "Type" : "AWS::EC2::KeyPair::KeyName",
      "Default": "【key name】"
    },
    "SecurityGroupId" : {
      "Type" : "CommaDelimitedList",
      "Default": "【セキュリティグループ id】"
    },
    "SubnetId" : {
      "Type" : "AWS::EC2::Subnet::Id",
      "Default": "【サブネット】"
    },
    "ECSCluster" : {
      "Type" : "String",
      "Default": "【クラスター名】"
    },
    "EC2Role" : {
      "Type" : "String",
      "Default": "【3で作ったEC2 Role】"
    }
  },
  "Resources" : {
    "DevInstance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "AvailabilityZone" : { "Ref" : "AvailabilityZone" },
        "ImageId" : { "Ref" : "ImageId" },
        "InstanceType" : { "Ref" : "InstanceType" },
        "KeyName" : { "Ref" : "KeyName" },
        "BlockDeviceMappings" : [{ "DeviceName" : "/dev/xvda" , "Ebs" : { "DeleteOnTermination" : "false" , "VolumeSize" : "100" }}],
        "InstanceInitiatedShutdownBehavior" : "stop",
        "Monitoring" : "true",
        "SourceDestCheck" : "true",
        "UserData" : { "Fn::Base64" : { "Fn::Join": [ "", [ "#!/bin/bash\n", "echo ECS_CLUSTER=", { "Ref": "ECSCluster" }, " >> /etc/ecs/ecs.config" ] ] } },
        "NetworkInterfaces": [ {
          "AssociatePublicIpAddress": "true",
          "DeviceIndex": "0",
          "GroupSet": { "Ref" : "SecurityGroupId" },
          "SubnetId": { "Ref" : "SubnetId" }
        } ],
        "IamInstanceProfile": { "Ref" : "EC2InstanceProfile" },
        "Tags" : [ {"Key" : "Name", "Value" : "docker-dev01" } ]
      }
    },
    "EC2InstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [ { "Ref": "EC2Role" } ]
      }
    }
  }
}
5. ecs.dev01.elb01.template

コンテナで利用するELBを登録します。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "dev ecs-elb stack",
  "Parameters" : {
    "Instance" : {
      "Type" : "CommaDelimitedList",
      "Default": "【4で起動したinstance id】"
    },
    "SubnetId" : {
      "Type" : "CommaDelimitedList",
      "Default": "【サブネット】"
    },
    "SecurityGroupId" : {
      "Type" : "CommaDelimitedList",
      "Default": "【セキュリティグループ】"
    },
    "SSL" : {
      "Type" : "String",
      "Default": "arn:aws:iam::【AWS account id】:server-certificate/【登録している証明書名】"
    }
  },
  "Resources" : {
    "DevFrontendELB" : {
      "Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
      "Properties" : {
        "Instances" : { "Ref" : "Instance" },
        "LoadBalancerName" : "docker-dev-frontend-elb01",
        "ConnectionSettings" : { "IdleTimeout" : 300 },
        "Listeners" : [ { "InstancePort" : 80,
                          "InstanceProtocol" : "HTTP",
                          "LoadBalancerPort" : 443,
                          "Protocol" : "HTTPS",
                          "SSLCertificateId" : { "Ref" : "SSL" }
        } ],
        "SecurityGroups" : { "Ref" : "SecurityGroupId" },
        "Subnets" : { "Ref" : "SubnetId" }
      }
    }
  }
}
6. ecs.service.template

最後に、サービスを登録します。
ここでようやく全てが紐付いて、どのコンテナをどのEC2で起動させて、ELBで外部からそのコンテナにアクセス出来るようにします。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "ecs task stack",
  "Parameters" : {
    "Cluster" : {
      "Type" : "String",
      "Default": "【クラスター名】"
    },
    "NginxELBName" : {
      "Type" : "String",
      "Default": "【5. elb名】"
    },
    "NginxTaskDefinition" : {
      "Type" : "String",
      "Default": "【2.Task Definitions名】:【2.で登録したバージョン】"
    },
    "NginxContainerName" : {
      "Type" : "String",
      "Default": "nginx"
    },
    "ECSRole" : {
      "Type" : "String",
      "Default": "【3.ECS Role 名】"
    }
  },
  "Resources" : {
    "Nginx": {
      "Type" : "AWS::ECS::Service",
      "Properties" : {
        "Cluster" : { "Ref" : "Cluster" },
        "DesiredCount" : 1,
        "LoadBalancers" : [ {
          "ContainerName": { "Ref" : "NginxContainerName" },
          "ContainerPort": "80",
          "LoadBalancerName" : { "Ref" : "NginxELBName" }
        } ],
        "Role" : { "Ref" : "ECSRole" },
        "TaskDefinition" : { "Ref" : "NginxTaskDefinition" }
      }
    }
    }
  }
}

これらを全て登録して、ドメインにアクセスして、コンテナ化した画面が表示されればOKです。 余談ですが、サービスの登録は1~5までのどれか1つでも失敗しているとサービスのtemplate登録には失敗して、「Did not stabilize」のエラーが出ます。
何かが安定しないんだ?これは、こちら側の問題なのか?思って結構悩んだので、
「リソースの作成に失敗しました。」とか出てくれた方が、自分のtemplateが悪いと判断ついて良いなあと思いました。
ECSの公式のsampleだとAutoScalingでインスタンスを立てていますが、この時は導入検討段階だったので、インスタンスだけで立てました。

watershedアルゴリズムを使って画像を領域毎に分割する

0. はじめに

どうも、お久しぶりです。原田@C++大好きです。

本当は、皆様にMonad実装してみた!とかやって見たかったのですが…

タイムアップでして、そのうち実装してみます。

今回は、もうScala関係なしにC++OpenCVネタです。

あーこんなのあるのかー程度に見ていただければ幸いです。

1. watershedって何さ?

watershedの意味 - 英和辞典 Weblio辞書

まずは英語、分水嶺(れい)、分水界、(川の)流域、分岐点、転機という意味です。

まぁ、何かというと画像の上から水を流しこむような形で画像の領域を判断するアルゴリズムです。

詳しくはここらあたりを見てもらえるとちょっとはわかりやすくなるかも。

ウォーターシェッド(領域分割) | 計測 | ヴィスコの画像処理技術 | ヴィスコ・テクノロジーズ株式会社

2. 実際のコード

// 面積計算アルゴリズム(画素数)
int calculateArea(cv::Mat * image) {
    cv::Mat grayMat;
    cv::cvtColor(image, grayMat, CV_BGR2GRAY);
    return cv::countNonZero(grayMat);
}

// 画像の分割アルゴリズム
void divide(cv::Mat base, cv::Mat & distA, cv::Mat & distB) {
    // Watershed分割
    // グレースケール
    cv::Mat bw;
    cv::cvtColor(base, bw, CV_BGR2GRAY);
    cv::threshold(bw, bw, 40, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    cv::imshow("dist", bw);

    cv::Mat dist;
    // 輪郭から内側にいけばいくほど濃くする
    cv::distanceTransform(bw, dist, CV_DIST_L2, 3);
    cv::normalize(dist, dist, 0, 1., cv::NORM_MINMAX);
    cv::imshow("Distance Transform Image", dist);

    // 二値化してゴミ取り
    cv::threshold(dist, dist, .4, 1., CV_THRESH_BINARY);
    cv::Mat kernel1 = cv::Mat::ones(3, 3, CV_8UC1);
    cv::dilate(dist, dist, kernel1);
    imshow("Peaks", dist);

    cv::Mat dist_8u;
    dist.convertTo(dist_8u, CV_8U);

    // すべてのマーカーを取得
    cv::vector < cv::vector < cv::Point > > contours;
    cv::findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    // watershedに流し込む用のマーカー画像作成
    cv::Mat markers = cv::Mat::zeros(dist.size(), CV_32SC1);
    // マーカーを描画
    for (size_t i = 0; i < contours.size(); i++) {
        cv::drawContours(markers, contours, static_cast < int > (i), cv::Scalar::all(static_cast < int > (i) + 1), -1);
    }
    // 背景用のマーカー作成
    circle(markers, cv::Point(5, 5), 3, CV_RGB(255, 255, 255), -1);
    imshow("Markers", markers * 10000);

    // ベース画像を8ビット変換
    cv::Mat stage_8bit_3channel;
    cv::cvtColor(base, stage_8bit_3channel, CV_BGR2RGB, 3);
    cv::watershed(stage_8bit_3channel, markers);
    imshow("watershed ", markers * 10000);

    cv::Mat dividA = cv::Mat::zeros(base.size(), CV_8UC4);
    cv::Mat dividB = cv::Mat::zeros(base.size(), CV_8UC4);

  // 分割した画像をそれぞれの画像に書き込む
    for (int i = 0; i < markers.rows; i++) {
        for (int j = 0; j < markers.cols; j++) {
            // 白固定してますが、色わけしたい場合は適当にいじってください。
            int r = 255;
            int g = 255;
            int b = 255;
            int index = markers.at < int > (i, j);
            if (index == 1) {
                dividA.at < cv::Vec4b > (i, j) = cv::Vec4b(r, g, b, 255);
            } else if (index == 2) {
                dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(r, g, b, 255);
            } else {
                dividA.at < cv::Vec4b > (i, j) = cv::Vec4b(0, 0, 0, 0);
                dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(0, 0, 0, 0);
            }
        }
    }
    imshow("dividA", dividA);
    imshow("dividB", dividB);

    distA = dividA;
    distB = dividB;

    // dividBに画像が存在したら
    if (calculateArea(distB) != 0) {
        return;
    }
  // 分水流の欠点であまりにも小さすぎると破棄されてしまうことがあるので、
  // ベース画像から他の画像を抜く処理
    for (int i = 0; i < base.rows; i++) {
        for (int j = 0; j < markers.cols; j++) {
            auto stagePixel = base.at < cv::Vec4b > (i, j);
            auto divAPixel = dividA.at < cv::Vec4b > (i, j);
            int r = 255;
            int g = 255;
            int b = 255;
            if (stagePixel[0] == 0 &&
                stagePixel[1] == 0 &&
                stagePixel[2] == 0 &&
                stagePixel[3] == 255) {
                dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(0, 0, 0, 0);
                continue;
            }
            if (divAPixel[0] == r && divAPixel[1] == g && divAPixel[2] == b) {
                dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(0, 0, 0, 0);
                continue;
            }
            dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(r, g, b, 255);
        }
    }
    imshow("dividBv2", dividB);
    distB = dividB;
}

詳細な動作

まずは画像を二値化します。

    // グレースケール
    cv::Mat bw;
    cv::cvtColor(base, bw, CV_BGR2GRAY);
    cv::threshold(bw, bw, 40, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    cv::imshow("dist", bw);

f:id:y_harada:20160405002916p:plain

distanceTransformで輪郭からの距離がながくなると濃くなるフィルターかけます。

    cv::Mat dist;
    // 輪郭から内側にいけばいくほど濃くする
    cv::distanceTransform(bw, dist, CV_DIST_L2, 3);
    cv::normalize(dist, dist, 0, 1., cv::NORM_MINMAX);
    cv::imshow("Distance Transform Image", dist);

f:id:y_harada:20160405002920p:plain

二値化してさっきのdistanceTransformから薄い部分を取り除き、膨張させます

    // 二値化してゴミ取り
    cv::threshold(dist, dist, .4, 1., CV_THRESH_BINARY);
    cv::Mat kernel1 = cv::Mat::ones(3, 3, CV_8UC1);
    cv::dilate(dist, dist, kernel1);
    imshow("Peaks", dist);

f:id:y_harada:20160405002922p:plain

膨張させた画像からマーカーを生成します。

この時背景もマーカーにいれておきます。

    cv::Mat dist_8u;
    dist.convertTo(dist_8u, CV_8U);

    // すべてのマーカーを取得
    cv::vector < cv::vector < cv::Point > > contours;
    cv::findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    // watershedに流し込む用のマーカー画像作成
    cv::Mat markers = cv::Mat::zeros(dist.size(), CV_32SC1);
    // マーカーを描画
    for (size_t i = 0; i < contours.size(); i++) {
        cv::drawContours(markers, contours, static_cast < int > (i), cv::Scalar::all(static_cast < int > (i) + 1), -1);
    }
    // 背景用のマーカー作成
    circle(markers, cv::Point(5, 5), 3, CV_RGB(255, 255, 255), -1);
    imshow("Markers", markers * 10000);

f:id:y_harada:20160405002927p:plain

画像を適切なチャンネルに変更し、watershedにかけます。

    // ベース画像を8ビット変換
    cv::Mat stage_8bit_3channel;
    cv::cvtColor(base, stage_8bit_3channel, CV_BGR2RGB, 3);
    cv::watershed(stage_8bit_3channel, markers);
    imshow("watershed ", markers * 10000);

f:id:y_harada:20160405002933p:plain

最後に、各々のマーカーカラーから分離された画像を取り出します。

  // 分割した画像をそれぞれの画像に書き込む
    for (int i = 0; i < markers.rows; i++) {
        for (int j = 0; j < markers.cols; j++) {
            // 白固定してますが、色わけしたい場合は適当にいじってください。
            int r = 255;
            int g = 255;
            int b = 255;
            int index = markers.at < int > (i, j);
            if (index == 1) {
                dividA.at < cv::Vec4b > (i, j) = cv::Vec4b(r, g, b, 255);
            } else if (index == 2) {
                dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(r, g, b, 255);
            } else {
                dividA.at < cv::Vec4b > (i, j) = cv::Vec4b(0, 0, 0, 0);
                dividB.at < cv::Vec4b > (i, j) = cv::Vec4b(0, 0, 0, 0);
            }
        }
    }
    imshow("dividA", dividA);
    imshow("dividB", dividB);

    distA = dividA;
    distB = dividB;

f:id:y_harada:20160405002938p:plainf:id:y_harada:20160405002935p:plain

最後に

今回はしょっぱい矩形の分割でしたが、実際の写真とか垂れ流してみると物体検知とかができます。

OpenCVにはこれの他にも、顔認識やらいろいろなフィルターが詰まっています。

画像処理を本気でやってみたい人にはいいライブラリです。(Javaでも書けるしねってことはScalaでも…)

ちなみに、ScalaOpenCVとかだと… ScalaでOpenCVを使って画像処理 « Rest Term

是非是非、触ってみてはいかがでしょうか?

戯言

最近、C++ラムダ式楽しいです。 C++ でのラムダ式

あと型推論が便利 自動 (C++

参考にさせていただいたサイト様

OpenCV 2.2 C++ リファレンス — opencv 2.2 documentation

OpenCV: Image Segmentation with Watershed Algorithm

OpenCV: Image Segmentation with Distance Transform and Watershed Algorithm

ScalaMatsuriで同人誌を配布した話《PDF有り》 / 今週末にイベント開催します

Scala お知らせ

f:id:ysugitani:20160324195656j:plain

 

こんにちは、杉谷です。 

少し前に開催されたScalaMatsuri2016に将軍スポンサーとして参加させていただいたいた際、スポンサーノベルティとしてScala同人誌の配布をさせていただきました。その際に得られた知見を共有します。

なぜ作ろうと思ったのか

ノベルティの定番といえばシール・ボールペン(最近だとフリクションが多め?)・メモ帳といったところでしょうか?

知名度のあるプロダクトや会社であればグッズとして嬉しさもあるとおもいますが、ぶっちゃけ知名度が全くない我々がそのような物を作って誰が喜ぶのか?と疑問に思ったので、もうすこし糧になりそうなものを、ということで同人誌を作ることにしました。

 

制作環境・制作フロー

制作ツールGitbookを採用しました。 MarkDownでさくさくと書けることと、ブラウザでの閲覧も想定されていて制作がしやすいことが素敵でした。

ChefとVagrantを使って、数行叩けば以下の要件を備えたVMを立てられるように整えました

  • 基本原稿ディレクトリにMarkdownファイルを書き足すだけ
  • http://sepbook/ を開くとリアルタイムプレビューが表示される
  • GitbookとNginxと、PDF出力用のフォントやライブラリが入ってる
  • GitbookはVM起動時に自動起動

あとは執筆者のマシンとCI用の機材にこれをセットアップし

  1. 執筆者の名前でブランチを切る
  2. 執筆する
  3. masterにプルリクエストをする
  4. 各種レビュー(添削・内容の検証…)
  5. マージ
  6. CIがPDFを自動作成
  7. Slackに通知 & 回覧

というフローで進めました。 

注意が必要だったこと

GitbookのPDFはなかなか上等な出力をしてくれるのですが、物理本化するにあたり以下に気をつける必要がありました。

  • コードが横に広すぎるとはみ出てしまうので、適度に改行する
  • 改ページがほどよくなるよう調整する
  • リンク( [hoge](http://〜) )は使わず、URLを注釈等で記載する

編集方針

動機の一つに"我々の等身大を知って欲しい"ということで、各チーム一人執筆者を募り、それぞれのチームで実際に採用しているScalaネタで制作していくことにしました。 できあがった記事は以下の通りです。

  • PYXIS事業でのデータパーティション設計事例
  • IntelliJのショートカット+便利機能集
  • Incoming WebHooksを使ってScalaでSlackに投稿してみる
  • Beanstalk for Dockerを使ったPlay Framework実行環境構築
  • Python学生からプロScalaエンジニアに
  • GANMA!でのCache実装例
  • Play Framework入門者向けプロジェクト分割
  • ヘキサゴナルアーキテクチャを採用したプロジェクトの実装例
  • Akka Schedulerの定期実行を使って自動的にInstagramから画像を取得し、Tumblrへ投稿
  • scala DDD 依存性逆転の原則を採用してみた
  • 今すぐ使える!Android開発に役立つKotlinの拡張関数10選

なぜかKotlinネタも混ざるくらい自由な品揃えとなりました。

レビューでは"てにおは"や誤字脱字、コードの正しさを確認するほか、"具体的な知識・知見が得られる記事であること"を重視しました。 

合計ページ数は93ページでした(+表紙・裏表紙)。

お値段

キンコーズで700部刷りまして

一冊当たりのお値段は 590円/冊 でした。(ワーオ

流石に毎年やるにはグッとくるお値段ですが、「みたよ!よかったよ!!!」的なコメントをどしどし戴けると次回も頑張れるかもしれません!

評判

直接伺えた範囲では好評でした!(なぜかKotolinがよく刺さってたような)

ネット上ではそれほどご意見を見かけなかったので、アンケートフォームへのQRコードを張っておくとよかったかもしれません。

ダウンロード 

配布した物とほぼ同等なものがこちらからダウンロード戴けます。

f:id:ysugitani:20160324200058j:plainDownload - セプテーニ・オリジナル 技術読本

 

Scala将軍達の後の祭り

今週末の 3/26(土)に将軍スポンサー4社合同(サイバーエージェント・マーベリック・チャットワーク・セプテーニオリジナル)でイベントを開催します!

scala-syogun-matsuri.connpass.com

まだまだお席はありますので、よろしければ是非ご参加ください!

 

ElasticsearchとKibanaを使ってTwitterのトレンドワードを可視化してみた

大久保です。

最近、会社でElasticsearch+Kibana+Fluentdという定番の組み合わせを使ってログ解析する機会があったので、ついでにいろいろ勉強してみました。

触ってみておもしろかったのが、Elasticsearchがログ解析だけじゃなくてちょっとしたKVSのようにも振る舞えることです。

ElasticsearchはKibanaと組み合わせることで、もっといろいろおもしろいことできそう感あります!!

本記事では、その一例としてTwitterのトレンドワードをリアルタイムに集計するプログラムを組んでみました。

Kobito.ZqzBFh.png 完成形:トレンドワードごとのツイート数をグラフ化

続きを読む

PHP7をためしてみた

こんにちは。村井です。
さて、今回はphpです。

昨年末にphp7が出ました。
php5からかなり間の空いたメジャーバージョンアップです。

最新のRedMonkのランキングでもphpは3位に入ってます。まだまだ使ってる人口は多いようですね。
redmonk.com

私が最初に触ったプログラム言語はphpなのですが、そのせいか今だに好きな言語の一つです。
とかいいつつ、php4以降触ってませんでしたがw

いい機会なので、php7の新機能を見つつ触れてみたいと思います。

続きを読む

誰でも簡単にWebGLで高速なアニメーションを作る!

JavaScript HTML5

こんにちは。菅野です。

突然ですが、WebGLというものをご存知でしょうか?
GPUを使用した3DグラフィックをHTMLページ内にガンガン描画できる仕組みです。

3Dだけしかできないという訳ではなく、2Dでの描画もすることが出来ます。
この場合もGPUの恩恵を受けられ、より高速に描画できます。

今回はHTMLのCanvasタグでWebGLで手軽に高速な2Dのアニメーションを作る方法について紹介したいと思います。

CanvasWebGLの対応状況

HTML5では自由にお絵描きが出来るCanvasタグというものがあり、ここに描画するための仕組みとして2dコンテキストがあります。
基本的にはCanvasタグではこれを使って2D画像を描画します。

さらにGPUをバリバリ使って描画が出来るwebglコンテキストがあるのですが、これが使用できるかどうかは環境に依ります。

Can I use... Support tables for HTML5, CSS3, etc
対応状況は上記のリンクを参照してください。

一昔前、canvasで2Dアニメーションを作った時にはWebGLはモバイル環境では全滅でした。
でも、現在はiOSならほぼ確実に使えて、Androidはもう少しで安心して使えるようになるといった状況だと思います。

CreateJS

直接2dコンテキストやwebglコンテキストを使用すると、大変すぎて爆死するのでもう少し楽にやる方法を紹介します。

createjs.com
CreateJSというライブラリを使用することで割と簡単に2Dアニメーションを作れます!

早速使ってみましょう。

とりあえず使う
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>canvas</title>
        <meta name="viewport" content="width=630px, user-scalable=0">
        <style>
            body {
                margin: 0;
            }
        </style>
        <script src="http://code.createjs.com/createjs-2015.11.26.combined.js"></script>
        <script>
            function run() {
                // Stageは全てのオブジェクトのルートになるオブジェクトでcanvasを保持している
                var stage = new createjs.Stage("canvas");
                var height = stage.canvas.height;
                var width = stage.canvas.width;
                // 画像はBitmapクラスを使う
                var logo = new createjs.Bitmap("http://www.septeni-original.co.jp/img/common/logo.gif");
                // 位置調整
                logo.regX = 110;
                logo.regY = 72;
                logo.x = 110;
                logo.y = 100;
                // Tweenというものを使うと、簡単にアニメーションっぽいのが作れる
                createjs.Tween.get(logo, {loop: true, paused: false})
                        .to({y: height}, 600, createjs.Ease.sineIn)
                        .to({y: 100}, 600, createjs.Ease.sineOut);
                createjs.Tween.get(logo, {loop: true, paused: false})
                        .wait(500)
                        .to({scaleY : 0.4}, 100, createjs.Ease.sineIn)
                        .to({scaleY : 1}, 100, createjs.Ease.sineIn)
                        .wait(500);
                createjs.Tween.get(logo, {loop: true, paused: false})
                        .to({x: width - 110}, 1370)
                        .to({x: 110}, 1370);
                // 描画したいオブジェクトは、Stageに配置すると描画される
                stage.addChild(logo);
                // フレームレートを指定する
                createjs.Ticker.framerate = 60;
                // 毎フレームごとの処理を扱うハンドラーを登録
                createjs.Ticker.addEventListener("tick", handleTick);
                function handleTick(event) {
                    // updateメソッドで、Stageの状態が描画させる。
                    // 毎フレームごとに状態を描画することでアニメーションが出来上がる!
                    stage.update();
                }
            }
        </script>
    </head>
    <body onload="run()">
        <canvas id="canvas" width="630" height="550"></canvas>
    </body>
</html>

いきなりサンプルの完成形を貼付けましたが、一つずつ解説していきます。

まず、CreateJSのライブラリを読み込みます。CDNで配信されているのでscriptタグを追加するだけです。
<script src="http://code.createjs.com/createjs-2015.11.26.combined.js"></script>

次に必要なのは、canvasタグです。適当に大きさを指定しておきます。また、bodyのonloadイベントハンドラにメイン処理を行う関数を指定しておきます。

<body onload="run()">
        <canvas id="canvas" width="630" height="550"></canvas>
</body>

これで準備は完了で、これからCreateJSを使っていきます。下記のようにしてStageオブジェクトを作成します。引数はcanvasタグのid属性の値です。
new createjs.Stage("canvas");
このStageオブジェクトがすべてのDisplayObjectのルート要素になり、この下に表示したい子要素を追加します。
何らかのGUIライブラリを使ったことがある方なら、よくありがちな、いつものノリで使えます。

new createjs.Bitmap("http://www.septeni-original.co.jp/img/common/logo.gif");
画像を追加するためにはBitmapクラスを使います。引数は画像のパスです。
作成したインスタンスはStageにaddChildします。
これでOKです。簡単ですね。サンプルでは見栄えのために位置調整もしています。

表示ですが、Stageオブジェクトのupdateメソッドを呼び出すと実際にcanvasタグに描画されます。
これを忘れると真っ白なままです。

動かないとつまらないと思う方が大多数だと思うので、それも解説します。
動かしたいオブジェクトのxとかy座標のプロパティを書き換えてupdateメソッドを呼ぶだけです!
が、滑らかに動かすのはコツがいるのでここはTweenというものを使います。

createjs.Tween.get(logo, {loop: true, paused: false})
                        .to({y: height}, 600, createjs.Ease.sineIn)
                        .to({y: 100}, 600, createjs.Ease.sineOut);

TweenのgetメソッドにTweenさせたい対象を渡してTweenオブジェクトを取得します。
取得したオブジェクトのtoメソッドをチエーンさせてアニメーションを作っていきます。
toメソッドの引数は、変更するプロパティと値、変化に要する時間、イージング(どんな風に滑らかに動かすか)の指定です。

これでy(上下方向)のプロパティはスムーズに変化するのですが、stageのupdateメソッドを呼び続けないと画面が更新されません。
これをどうにかするためにTickerを使います。

// フレームレートを指定する
createjs.Ticker.framerate = 60;
// 毎フレームごとの処理を扱うハンドラーを登録
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
    // updateメソッドで、Stageの状態が描画させる。
    // 毎フレームごとに状態を描画することでアニメーションが出来上がる!
    stage.update();
}

フレームレートを設定し、毎フレームごとに登録した関数を呼び出してくれる便利な仕組みです。
stageのupdateだけではなく、毎フレーム毎に行う計算ロジックを動作させるのにも使えます。

動かすとこんな感じになります。 http://zakknak.github.io/webgl-test/sample1

その2

画像だけではなく、図形も描画できます。
http://zakknak.github.io/webgl-test/sample2

ソースは以下参照。
https://github.com/zakknak/webgl-test/blob/gh-pages/sample2/index.html

ほかにいろいろなことをしようと思っても、CreateJSはドキュメントがしっかりしているので使用方法に困ることはありません。
これで2dコンテキストで、自由に描画できるようになりました!

で、WebGL

今までは2dコンテキストで描画をしていました。 滑らかに動いているのですが、いろいろ作り込むとどんどん動作が重くなってきます。(特に大量のオブジェクトを描画するとき)

そこでWebGLの力に頼ります。

CreateJSでwebglを使用するためには、Stageの代わりにSpriteStageを使います。
SpriteStageはStageを継承したクラスでStageと使い方は一緒です。ですが、webglで描画しやすいオブジェクトしかaddChildできないという制約があります。
Bitmapは普通にaddChild出来るので何の問題もありません。

このSpriteStageですが、CDNで配信されているものには含まれていないので別のjsファイルを読み込む必要があります。
どこにもホスティングされていなくて面倒ですが、cdn.rawgit.comを使うとgithubリポジトリから読み込めます。
<script src="//cdn.rawgit.com/CreateJS/EaselJS/0.8.2/lib/webgl-0.8.2.min.js"></script>

これらを使って簡単にWebGLの有り無しでの比較が出来るものを作ってみました。

Shower of Septeni Original Logo

圧倒的にWebGLの方が描画速度が速いのがわかると思います。
また、iPhone6で動作するのも確認しています。

ソース
https://github.com/zakknak/webgl-test/blob/gh-pages/webgl/index.html

ソースに関しての説明は割愛します。 見所は、SpriteStageの制約を避けるために一回Stageで描画したものをSpriteStageに貼付けているところとか、予めPhotoShopとかで作ればいいのにJS上でスプライトシートを作成しているところとか?

まとめ

このように、2DでWebGLを使うのは今すぐにでも始められるのでやってみよう。