20190728のC#に関する記事は7件です。

【C#】背景色から黒文字か白文字の見やすい方を自動判定 (WCAG 2.0 利用版)

ボタンやラベルなどの背景色を自由に変更できるアプリを作成したときに、見やすい文字色を選ぶのはけっこう大変です。
また、背景色と文字色をそれぞれ設定値に持たせるのも良いですが、自動で見やすい文字色を設定できれば楽ですよね。

見やすい文字色とは

背景色と文字色のコントラスト比が高いものが見やすい文字となります。
今回は、W3C が提唱する見やすいウェブサイトの規格 WCAG 2.0 を参考にしました。

WCAG 2.0 とは?

公式Webサイト ( https://waic.jp/docs/WCAG20/Overview.html ) から抜粋した内容です。

Web Content Accessibility Guidelines (WCAG) 2.0 は、ウェブコンテンツをよりアクセシブルにするための広範囲に及ぶ推奨事項を網羅している。 このガイドラインに従うことで、全盲又はロービジョン、ろう又は難聴、学習障害、認知障害、運動制限、発話困難、光過敏性発作及びこれらの組合せ等を含んだ、様々な障害のある人に対して、コンテンツをアクセシブルにすることができる。又、このガイドラインに従うと、多くの場合、ほとんどの利用者にとってウェブコンテンツがより使いやすくなる。

特に最後の行が重要です。

又、このガイドラインに従うと、多くの場合、ほとんどの利用者にとってウェブコンテンツがより使いやすくなる。

今回はWebではありませんが、『コントラストの達成基準』をこちらの規格で判定し、誰にでも見やすい文字色を目指してみます。

ざっくり仕様

  • 文字色は黒文字か白文字のどちらかを選ぶケースがほとんどだと思います。なので、黒文字か白文字のどちらが見やすいか判定します。
  • 背景色は単一色を想定してます。グラデーションのときは代表色として中間色あたりを選んでください。
  • WCAG 2.0 の変換式を使い、背景色と黒文字・白文字でより高コントラストの方を採用します。
    ( https://waic.jp/docs/WCAG-TECHS/G17.html )

コード

using System.Drawing;

// RGB から相対輝度を算出(0.0 ~ 1.0)
public static double RelativeLuminance(byte R, byte G, byte B)
{
    // RGB の各値を相対輝度算出用に変換
    Func<byte, double> toRgb = (rgb) => {
        double srgb = (double)rgb / 255;
        return srgb <= 0.03928 ? srgb / 12.92 : Math.Pow((srgb + 0.055) / 1.055, 2.4);
    };

    return 0.2126 * toRgb(R) + 0.7152 * toRgb(G) + 0.0722 * toRgb(B);
}

// 2つの相対輝度値から、相対輝度比率を算出(0.0 ~ 21.0)
// 相対輝度比率が 7.0 以上の値だと見やすい
public static double RelativeLuminanceRatio(double relativeLuminance1, double relativeLuminance2)
{
    // 相対輝度比率 = (大きい値 + 0.05) / (小さい値 + 0.05)
    return (Math.Max(relativeLuminance1, relativeLuminance2) + 0.05) / (Math.Min(relativeLuminance1, relativeLuminance2) + 0.05);
}

// 背景色から白文字か黒文字を判定
public static Color chooseTextColor(byte R, byte G, byte B)
{
    // 背景色の相対輝度
    double background = RelativeLuminance(R, G, B);

    const double white = 1.0D;  // 白の相対輝度
    const double black = 0.0D;  // 黒の相対輝度

    // 文字色と背景色のコントラスト比を計算
    double whiteContrast = RelativeLuminanceRatio(white, background);   // 文字色:白との比
    double blackContrast = RelativeLuminanceRatio(black, background);   // 文字色:黒との比

    // コントラスト比が大きい文字色を採用
    return whiteContrast < blackContrast ? Color.Black : Color.White;
}

使用方法

rgb(125, 40, 80) であれば、白文字が選択されます。

// textColor = Color.White が選ばれる
Color textColor = chooseTextColor(125, 40, 80);

rgb(200, 160, 180) であれば、黒文字が選択されます。

// textColor = Color.Black が選ばれる
Color textColor = chooseTextColor(200, 160, 180);

補足1

引数 Color のオーバーロードを追加すると、コントロールの背景色プロパティから設定ができて便利です。

public static Color chooseTextColor(Color color)
{
    return chooseTextColor(color.R, color.G, color.B);
}

利用時は以下の通りです。

button1.ForeColor = chooseTextColor(button1.BackColor);

補足2

下記の部分はただのローカル関数なので、

// RGB の各値を相対輝度算出用に変換
Func<byte, double> toRgb = (rgb) => {
    double srgb = (double)rgb / 255;
    return srgb <= 0.03928 ? srgb / 12.92 : Math.Pow((srgb + 0.055) / 1.055, 2.4);
};

toRgb 関数として別の関数にしてもOKです。
ラムダ式やデリゲートがわかっていない人には、こちらのほうがわかりやすいかもしれません。

// RGB の各値を相対輝度算出用に変換
private static double toRgb(byte rgb)
{
    double srgb = (double)rgb / 255;
    return srgb <= 0.03928 ? srgb / 12.92 : Math.Pow((srgb + 0.055) / 1.055, 2.4);
}

// RGB から相対輝度を算出(0.0 ~ 1.0)
public static double RelativeLuminance(byte R, byte G, byte B)
{
    return 0.2126 * toRgb(R) + 0.7152 * toRgb(G) + 0.0722 * toRgb(B);
}

ラムダ式使ってローカル関数化しているのは、ただの僕の好みです。
RelativeLuminance 関数でしか使用しない関数 toRgb を別の関数とにすると、改修などの際に「他の関数でも使用しているかもしれない」というチェックが必要になります。
RelativeLuminance 関数内でのみ使用する小さな関数は、ローカル関数にしておいたほうが RelativeLuminance 関数の独立性が高く、メンテナンスもしやすくなります。
toRgb 関数を他の関数と共有する状態になったときに、初めて別関数へ切り出せば良いと思ってます。

会社ではラムダ式が浸透していない&他の制約で使えないですが(悲)

最後に

この方法であれば、誰が設定しても見やすい文字になるかと思います。
一部見にくい背景色と文字色の組み合わせがあると思います。
文字色が黒文字か白文字という条件なので、相対輝度比率が 7.0 未満の背景色がどうしても出てきてしまいます。
例えば、赤 rgb(255,0,0)は黒文字も白文字も相対輝度比率が 7.0 未満です。
そのときは背景色の選定が間違っていると考えてください。(逃げ)

参考

W3C - WCAG 2.0 - G17: テキスト (及び文字画像) とその背景の間に、少なくとも 7:1 のコントラスト比を確保する
https://waic.jp/docs/WCAG-TECHS/G17.html

意識の高い時に雑記 - JavascriptでWC3のコントラスト計算式を書いてみる
https://lifehackdev.xsrv.jp/ZakkiBlog/articles/detail/web15

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】コンポーネントのアタッチ時にデフォルト値を自動で設定する

環境

  • Unity 2019.1.12f1 (Unity 5系 のような古いバージョンでも大丈夫です)

デフォルト値の設定について

コンポーネントのデフォルト値を設定する際、私はインスペクタを開いて手動で設定していました。この操作、割と面倒ですしヒューマンエラーを起こします。
イラスト57.png

手間を減らせないかと探したところ、Reset() という関数が MonoBehaviour クラスには存在しているようです。

Reset() について

リファレンス
(古いバージョン (Unity 5.0) のリファレンスにも存在しています。)

Reset はインスペクターのコンテキストメニューにある Reset ボタンやコンポーネントを初めて追加するときに呼び出されます。 この関数はエディターモードのみで呼び出されます。Reset はインスペクターでデフォルト値を設定するときにもっともよく使用されます。

Reset()は、コンポーネントをアタッチするときに呼ばれる関数です。この関数内でデフォルト値を設定する処理を書けば、アタッチ時にデフォルト値が自動で設定されるわけです。

使い方

アタッチするコンポーネントに Reset() を定義します。関数内でデフォルト値を設定します。

HogeComponent.cs
using UnityEngine;
using UnityEngine.UI;

public class HogeComponent : MonoBehaviour
{
    [SerializeField] private Image image;
    [SerializeField] private Text text;

    void Reset()
    {
        if(!text){ text = GetComponentInChildren<Text>(); }
        text.text = "Hoge";
        if(!image){ image = GetComponentInChildren<Image>(); }
        image.color = Color.red;
    }
}

後はこのコンポーネントをアタッチするだけです。

s2.gif

上のように、アタッチした瞬間に参照をつけることが出来ました。
また、参照先のコンポーネントのパラメータの変更もできます。

なお、コンポーネントのアタッチ後に、そのコンポーネントの Reset() を定義しても、アタッチ済みのコンポーネントのデフォルト値は設定されません。
アタッチ済みのコンポーネントのデフォルト値を設定する場合は、右クリックメニューからResetを選択することで、Reset()が実行されます。

s3.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.Net Core2.2におけるWebAPIのログ出力について

.NET Coreを始めたのですが、理解が浅かったのでこまめにまとめたいと思います。
ここではWebAPIによるプロジェクトのログ出力についてまとめます。

環境

Tools Version
.NET Core SDK 2.2.401

初めに

.NET CoreにおけるLoggerの基本的な使い方はDIによる利用になります。

SampleController.cs
    [Route("api/[action]")]
    [ApiController]
    public class SamplesController : ControllerBase
    {
        private readonly ILogger<SamplesController> _logger;
        public SamplesController(ILogger<SamplesController> logger)
        {
            _logger = logger;
        }
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Sample>>> GetSamples()
        {
            _logger.LogTrace("trace");
            _logger.LogDebug("debug");
            _logger.LogInformation("info");
            _logger.LogWarning("warning");
            _logger.LogError("error");
            _logger.LogCritical("critical");
            return new List<Sample> { new Sample() };
        }
    }

TraceIdの扱いをどうするか

例えば、GET: api/samples/{id}のようなエンドポイントを考えます。
以下のようなコードを用意します。

SampleController.cs
    [Route("api/[action]")]
    [ApiController]
    public class SamplesController : ControllerBase
    {
        private readonly ISampleService _sampleService;
        private readonly ILogger<SamplesController> _logger;
        public SamplesController(ISampleService sampleService, ILogger<SamplesController> logger)
        {
            _sampleService = sampleService;
            _logger = logger;
        }
        [HttpGet("{id}")]
        public async Task<ActionResult<Sample>> GetSample(int id)
        {
          var sample = await _sampleService.FindById(id);
          if (sample == null) {
            return NotFound();
          }
          return  sample;
        }
    }

指定したIdが存在しない場合、NotFoundResultを返します。

この時のレスポンスは以下のようになります。


    {
        "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
        "title": "Not Found",
        "status": 404,
        "traceId": "0HLOJ3836502B:00000001"
    }

ここで気になるのはtraceIdの存在です。ログの出力をDebugレベルで確認すると、リクエストの開始とレスポンスの完了のタイミングでtraceIdの出力があります。しかし、Debugレベルでの出力になるので、不要なのでは?と思ってしまいます。

悩んだ結果、TraceIdをログに出すことにしました。

SampleController.cs
        [HttpGet("{id}")]
        public async Task<ActionResult<Sample>> GetSample(int id)
        {
          var sample = await _sampleService.FindById(id);
          if (sample == null) {
            _logger.LogError($"traceId: {HttpContext.TraceIdentifier}. Sample is not found.");
            return NotFound();
          }
          return  sample;
        }

Controllerからであれば、HttpContextプロパティのTraceIdentifierをログ出力すれば良いです。

最後に

そもそもレスポンスをtraceIdを返さないようにすることもできるんですが、どちらがいいんでしょうか ?

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無料で アジャイルツールが使える Azure DevOpsはいいぞ

チームビルディングに使えるアジャイルツールがかなり一般的になってきました。

個人的にはJiraを使ってみたいのですが、
うちのチームでは使わないのと
私の会社ではBacklogが使われていますので、今回は無料で使えるものを紹介しようと思います。

https://azure.microsoft.com/ja-jp/services/devops/

こういったツールはエンタープライズものが多く、個人から簡単に使えそうなものは
実はなくて困ってました。

ただ多少(我慢すれば)使いづらさもありますが、AzureDevOpsの能力が最近向上してきたので
おすすめできるレベルになりました

昔は英語表記だけだったのですが、ようやくHPも日本語表記になりました。
オープンソースprojectなら実質無料で使えるという素晴らしさです。

GitHubアカウントからとれるようになったのも大きいです。

早速ログインすると、プロジェクトを作れます

例)私の場合
image.png

さらにプロジェクトの中に入ると以下の表示が出てきます
image.png

  • 概要
  • ダッシュボード
  • backlog
  • かんばん

恐らくこの機能を(無料~五名まで)を使えるのはここしかないかなと思います

さらにCICD機能の
AzurePipelinesも少ないプロダクトなら無料で使えます。

azurepilelinesについてはこちら
https://qiita.com/yamuuuuuun/items/8ceaae59bea3837c57e1

難点

そんなAzurePilelinesにも難点はあります。
使えれば機能は申し分ありませんが以下2つが残念過ぎます

  • UIが貧弱
  • 日本語化がほとんどされていない

私は カンバンとバックログ機能が付いたproject管理を無料で使いたかったので
これを使いました。かつ想定人数~3人だったので

これがJiraやbacklogぐらい綺麗だったらなー・・・
Microsoftさんお願いします

といいつつJira使ってみたいので Jira使っている人の感想お待ちしてます

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ASP.NET Core + Vue.js (TypeScript)で開発する時の覚書

ASP.NET Core + Vue開発時まとめ

概要

きっとみんなこのスタックで開発したいはずなのに、
個別の情報は散在していてまとまった情報がないのでまとめてみた
(AngularとReactはVSやCore SDKのテンプレートがあるのにVueはなぜかない...)

できるようになること

  • C#(Server side) + TypeScript + Vue(Client)での開発
  • Hot Module Reloadingによるデバッグ時でのフロントエンドコードの修正・リアルタイム反映
  • リリース・デプロイ時のWebpackの設定

この記事で触れないもの

  • Vue CLI : 結局Webpackを触らないとやってられないし、サーバはASP.NET Coreを使えばいいからあまりメリットがない
  • 認証周り : ASP.NET Coreサーバ単体で運用するんだったらASP.NET Core Identity, バックエンドを分けてCORS設定してJWTをVuexに入れるとか考えられるがここでは割愛。

環境

Windows

  • Visual Studio
  • .Net Core SDK
  • Node開発モジュール

Mac, Linux, WSL

  • .Net Core SDK
  • Node.js
  • Visual Studio Code
    • Vetur
    • TS Lint
    • C# Extension

これらがインストール済みであることが前提。
Node, Net Core SDK、VS Codeなどは出来れば最新のものが望ましい。

準備

VSはASP.NET Coreのテンプレから構築。GUIでどれを選んでもいい。
CLIベースのツールで以降は説明。

新規のprojectを作成し、パッケージを追加する

dotnet new webapp --name QiitaArticle
cd QiitaArticle
dotnet add package Microsoft.AspNetCore.SpaServices
dotnet add package Microsoft.Typescript.MSBuild
dotnet add package Microsoft.VisualStudio.Web.BrowserLink
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install --global dotnet-aspnet-codegenerator

Nodeの設定とモジュールのインストール

npm init # デフォルトの設定でOK
npm i vue axios
npm i -D webpack webpack-cli webpack-hot-middleware clean-webpack-plugin aspnet-webpack webpack-dev-middleware
npm i -D typescript ts-loader css-loader vue-loader vue-template-compiler webpack-merge terser-webpack-plugin
npm i -D vue-class-component vue-property-decorator vuex vue-router

各種設定ファイルを配置する

mkdir Frontend

ここに以下を配置していく

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "experimentalDecorators": true,
    "sourceMap": true,
    "moduleResolution": "node",
    "lib": ["dom", "es5"]
  },
  "exclude": [
    "node_modules"
  ]
}

互換性はES5とした。APIの通信でPromiseをメソッドチェーンよりもC#書きにとって馴染み深い async / await記法を使いたいのでlibに追加する。
Vueのコンポーネントの各要素の属性を指定するデコレータは現時点ではExperimentalなのでその旨明記。

vue-shims.d.ts
declare module '*.vue' {
    import Vue from 'vue';
    export default Vue;
}

VueファイルをTSとして読み込むために必要

Webpackの設定

開発と本番で設定ファイルを分ける。共通部分は common にいれる。
HTML Loaderなどは特に不要。

webpack.common.js
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
    entry: { main: './Frontend/index.ts' },
    output: {
        path: path.resolve(__dirname, 'wwwroot'),
        filename: 'js/[name].js',
        publicPath: '/'
    },
    resolve: {
        extensions: ['.ts', '.js', '.vue', '.json'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {}
            },
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                exclude: /node_modules/,
                options: {
                    appendTsSuffixTo: [/\.vue$/]
                }
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
    ]
};

Devではソースマップを有効にする。

webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './wwwroot',
    }
});

Prodではminifyする。

webpack.prod.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(common, {
    mode: 'production',
    optimization: {
        minimizer: [
            new TerserPlugin({
                terserOptions: { ecma: 5, compress: true,
                    output: { comments: false, beautify: false }
                }
            })
        ]
    }
});

package.jsonを編集して、Webpackのコマンドを呼び分ける

package.json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "build": "webpack --config webpack.dev.js"
+   "release": "webpack --config webpack.prod.js"

  },

本番デプロイ用にcsprojを編集すうる

QiitaArticle.csproj
    <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.2.0" />
  </ItemGroup>

+ <Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
+   <Exec Command="npm i" />
+   <Exec Command="npm run release" />
+ </Target>
</Project>

ASP.NET CoreでHMRを使えるようにする

Sartup.cs
using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.AspNetCore.SpaServices.Webpack;

//(中略)

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
+               app.UseBrowserLink();
+               app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
+               {
+                   HotModuleReplacement = true,
+                   ConfigFile = @"./webpack.dev.js"
+               });
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

-           app.UseMvc();
+           app.UseMvc(routes =>
+           {
+               routes.MapRoute(
+                   name: "default",
+                   template: "{controller=Home}/{action=Index}/{id?}");
+               routes.MapSpaFallbackRoute(name: "spa-fallback", new { controller = "Home", action = "Index" });
+           });        
        }

Fall back routeはVue RouterでClient Side Renderingした際に必ず必要なので入れる。

VueがHello worldできることを確認する

Frontend
├───Components
│   └───HelloWorld.vue
├───index.ts
├───tsconfig.json
└───vue-shims.d.ts
index.ts
import Vue from 'vue'
import HelloComponent from './Components/HelloWorld.vue'

let v : Vue = new Vue({
    el: '#app',
    template: `
    <div>
        Name: <input v-model="name" type="text">
        <hello-component :name="name" :initialEnthusiasm="5" />
    </div>
    `,
    data: { name: 'World' },
    components: { HelloComponent }
});
Components/HelloWorld.vue
<template>
    <div>
        <div class="greeting"> Hello {{ name }} {{ exclamationMarks }}</div>
        <button @click="decrement"> -</button>
        <button @click="increment"> +</button>
     </div>
</template>

<script lang = "ts">
import { Vue, Component, Prop } from 'vue-property-decorator';

@Component
export default class HelloDecorator extends Vue {
    @Prop() name!: string;
    @Prop() initialEnthusiasm!: number;

    enthusiasm = this.initialEnthusiasm;

    increment() {
        this.enthusiasm++;
    }
    decrement() {
        if (this.enthusiasm > 1) {
            this.enthusiasm--;
        }
    }

    get exclamationMarks(): string {
        return Array(this.enthusiasm + 1).join('!');
    }
}
</script>
Pages/Shared/_Layout.cshtml
    </environment>
    <script src="~/js/site.js" asp-append-version="true"></script>
+   <script src="~/js/main.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
Pages/Index.cshtml
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
+   <div id="app"></div>
+   <div id="api"></div>
</div>
dotnet run

でエラーが出ないことを確認。またDeveloper ToolのConsole上に [HMR] と表示されていれば、
フロントエンドのコードをデバッグ実行中に編集しても変更が反映される。

APIを追加して、コンポーネントから呼び出して表示させる

dotnet aspnet-codegenerator controller -name SampleController --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
Controllers/SampleCOntroller.cs
using Microsoft.AspNetCore.Mvc;

namespace QiitaArticle.Controllers
{
    [Route("api/sample")]
    [ApiController]
    public class SampleController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return Json(new string[] {"Vue.js", "With", "ASP.NET Core", "Rocks!"});
        }
    }
}
Frontend/Components/CallingApi.vue
<template>
    <div>
        <ul v-for="c in content" :key="c">
            <li>{{c}}</li>
        </ul>
     </div>
</template>

<script lang = "ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
import axios from 'axios';

@Component
export default class CallingApi extends Vue {
    content : string[] = [];
    async mounted() : Promise<void> {
        this.content =  (await axios.get('api/sample')).data;
    }
}
</script>
index.ts
import Vue from 'vue'
import HelloComponent from './Components/HelloWorld.vue'
+ import CallingApi from './Components/CallingApi.vue'

new Vue({
    el: '#app',
    template: `
    <div>
        Name: <input v-model="name" type="text">
        <hello-component :name="name" :initialEnthusiasm="5" />
    </div>
    `,
    data: { name: 'World' },
    components: { HelloComponent }
});
+ new CallingApi({}).$mount('#api');

これでControllerから取り出した内容が表示されるはず

本番デプロイ

環境変数を変えてProductionモードで見てみる。

dotnet publish -c Release
export ASPNETCORE_ENVIRONMENT=Production
cd ./bin/Release/aspnetcoreapp2.0/publish
dotnet QiitaArticle.dll

これで起動した後、F12を押してConsoleを見てみると、minifyされたJSが読み込まれていて、 HMR が消えている。
実際はこれをサーバに置けばOK.

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET Coreのappsettings.jsonのLoggerの設定を理解する

.NET Coreを始めたのですが、理解が浅かったのでこまめにまとめたいと思います。
appsettings.jsonの扱いとLoggerの設定についてまとめます。

環境

Tools Version
.NET Core SDK 2.2.401

初めに

$dotnet new webapp -o SampleAppを実行してプロジェクトを作成します。
以下のようなappsettings.jsonとappsettings.Development.jsonが生成されているはずです。

appsettings.json
    {
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
appsettings.Development.json
    {
      "Logging": {
        "LogLevel": {
          "Default": "Debug",
          "System": "Information",
          "Microsoft": "Information"
        }
      }
    }

以降では、appsettings.jsonやappsettings.Environment.jsonの設定がどのように反映されているかという話をしていきます。

複数環境での実行について

.NET Coreでのランタイム環境は環境変数ASPNETCORE_ENVIRONMENTを利用します。
例えば、$ASPNETCORE_ENVIRONMENT=Development dotnet runとして実行した場合、appsettings.jsonの設定値を読み込み、appsettings.Development.jsonの設定で上書きをします。
当然、$ASPNETCORE_ENVIRONMENT=Production dotnet runとして実行した場合、appsettings.Development.jsonの設定値ではなく、appsettings.Production.jsonの設定を使用します。

Program.csではWebHost.CreateDefaultBuilder(args)を実行していますが、手動で設定する場合は以下のようなコードになります。ConfigureAppConfigurationの部分のようにappsettings.jsonが読み込まれているため、ランタイム環境に応じた設定が適用されています。

Program.cs
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                var env = hostingContext.HostingEnvironment;
                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", 
                          optional: true, reloadOnChange: true);
                config.AddEnvironmentVariables();
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                // Requires `using Microsoft.Extensions.Logging;`
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                logging.AddDebug();
                logging.AddEventSourceLogger();
            })
            .UseStartup<Startup>()
    }

そのため、appsettings.jsonでは各ランタイム環境で共通の設定を記載し、固有の設定はappsettings.Environment.jsonに行うことになります。

Loggingの設定について

上記コードのConfigureLoggingを抜粋すると、AddConsole()AddDebug()が実行されています。
これにより、通常出力とデバック出力をそれぞれ設定することができます。
例えば、appsettings.jsonを以下のような設定にします。

appsettings.json
    {
      "Logging": {
        "LogLevel": {
          "Default": "Warning",
          "System": "Warning",
          "Microsoft": "Warning"
        },
        "Console": {
          "LogLevel": {
            "Default": "Information",
            "System": "Information",
            "Microsoft": "Information"
          }
        },
        "Debug": {
          "LogLevel": {
            "Default": "Debug",
            "System": "Debug",
            "Microsoft": "Debug"
          }
        }
      }
    }

Loglevelに関しては以下のようになっており、設定した値以降のログが全て表示されます

  • Trace
  • Debug
  • Information
  • Warning
  • Error
  • Critical

この場合は、通常出力ではInformationからログが表示され、デバッグ出力ではDebugからログが表示されます。ConsoleDebugの設定は任意で、設定しない場合、LogLevelの設定が適用されます。

最後に

以前に色々検証したコードを久しぶりに動かしたら、コンソールにログが表示されず、あれこれ悩んでしまいました。まとめてみるとすごーく当たり前のことだったと認識しました?

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

生産性向上へのショートカット

業務プロセスなどの効率化ではなく、キーボードショートカットで生産性向上を図る話です。
業務プロセスなどでの生産性向上をご検討の方も、戻るボタンを押す前にご一読ください。


この記事は リンク情報システム の「Tech Connect Summer 夏のアドベントカレンダー」のリレー記事です。
Tech Connect Summer は勝手に始める真夏のアドベントカレンダーとして、engineer.hanzomon という勝手に作ったグループによってリレーされます。
(リンク情報システムのFacebookはこちらから)


目指すもの

働き方改革が叫ばれる中、労働時間は短くしながら成果物は増やすか現状維持が期待されます。
要は定時に帰ったり、有休をちゃんと消化しながら、生産性は上げていく必要があります。

生産性向上なんて、もうやることないよぅ!

いえ、あります。
人のPC操作を見ていると気になるあれです。

ショートカットです。

一般的なショートカットを全ての人が使えるようにすることで、会社全体、ひいては社会全体の生産性を底上げします。

また、ショートカットを多用する人は、自分で調べるなどして体に染み込ましているかと思いますが、まだ馴染んでいないアプリを使っていく中でショートカットがリコメンドされると、結構うれしいですよね。

ショートカットを使わないことによるダメージ

仮にマウス操作でなんらかの操作を行ったとします。これが2秒かかるとします。
1日50回これが行われるとします。

 100秒/日 = 2回 x 50秒

月20日の営業日として、年に換算すると、一人6時間の時間をロスします。

 6時間/年 = 100秒 x 20営業日 x 12カ月

ショートカットを多用しない要員が300名存在する会社の場合、1,800時間。

 1,800時間/年 = 6時間 x 300名

いわゆる工数にすると年間12人月のロスになります。(1日7.5時間として)

 12人月 = 1,800時間 ÷ 7.5時間 ÷ 20営業日

驚愕です。なかなかです。ちょうど社員1人分ですね。
数字で示してみても、ショートカットを多用しない場合の経済損失と地球環境に与える影響は、もう無視できません。

どのようにショートカットをお勧めするか

普段通りPCを操作しているときに、「これのショートカットないんかい!」って時や、ふとお勧めされたりするのが良いです。
またその時、ショートカットを使うことで得する時間を教えます

以下、仕様の概要です。

  • マウスクリックを監視
  • クリックされたら周辺の画像をキャプチャ
  • 画像分類の機械学習で、クリック操作した機能(操作)を推論
  • 3回クリック操作したら、当該操作のショートカットを画面右下に通知
  • ショートカットを使うと得する時間も一緒に表示(固定的に1回の操作で2秒とする)
  • 毎回お勧めされるとウザったいので、おすすめ不要にできる

技術要素

当社はWindowsが基本なので C# & WindowsForm でツールを作成します。

  • Accord
      C#でメジャーどころの機械学習ライブラリです。
      Bag Of Visual-words で画像パターンを求め、
      Support Vector Machine で分類します。


  •   

UI

UIで以下が出来るようします。

  • 学習データとする画像を収集する
  • 収集した画像を選別する
  • 選別した画像を操作と紐づける(教師データを作成する)
  • 学習する
  • ショートカットをお勧めする(マウス監視の開始)

以下の感じのUIになりました。

学習系の画面

image.png

おすすめ通知

03_おすすめ通知.png

コード

ポイントとなるコードです。

学習の準備

画像をBOVWでベクトル化します。
そのベクトルと画像のラベル番号(操作の種類)を紐づける形でListに入れておく感じです。
最後にアプリを学習状態で再起動可能とするため、BOVWのデータを保存します。

/// <summary>
/// 学習の準備
/// </summary>
private void Prepare()
{
    var trainImgList = new List<Bitmap>();
    this.labelList = new List<int>();

    // 画像が置かれているディレクトリ名をクラス名としている
    // ディレクトリ配下の画像に、分類しておいた学習データが格納されている
    string[] dirs = Directory.GetDirectories(TRAIN_PATH + "/img", "*", SearchOption.TopDirectoryOnly);
    foreach (string dirPath in dirs)
    {
        // ディレクトリ配下の画像ファイルを学習用のリストに入れていく
        string[] files = Directory.GetFiles(dirPath, "*", SearchOption.TopDirectoryOnly);
        foreach (string trainImgPath in files)
        {
            trainImgList.Add(new Bitmap(trainImgPath));

            // 一緒にクラス名をラベル番号としてclassLabelNoListに設定する
            var classification = Path.GetFileName(dirPath);
            this.labelList.Add(classDict[classification]);
        }
    }

    // Bag of Visual-wordsで画像パターンでベクトル化
    this.bovw.Learn(trainImgList.ToArray());

    // 各画像のベクトルをリストに設定する(学習時に使用する)
    this.vectorList = new List<double[]>();
    for (int i = 0; i < trainImgList.Count; i++)
    {
        this.vectorList.Add(this.bovw.Transform(trainImgList[i]));
    }

    // BOVWのデータを学習済みデータとして保存
    Serializer.Save(this.bovw, BOVW_MODEL_PATH);
}
学習

Accordのリファレンスを参考にして組みます。

/// <summary>
/// 学習
/// </summary>
private void Train()
{
    // SVM
    this.msvm = new MulticlassSupportVectorMachine<ChiSquare>(0, new ChiSquare(), this.classDict.Count);

    // 一対一多の学習アルゴリズム
    var smo = new MulticlassSupportVectorLearning<ChiSquare>()
    {
        // 逐次最小問題最適化法というのを用いて2次計画問題に対応する(理解薄い。。)
        Learner = (param) => new SequentialMinimalOptimization<ChiSquare>()
        {
            UseComplexityHeuristic = true,
            UseKernelEstimation = true
        }
    };

    // 学習データ
    var inputs = this.vectorList.ToArray();
    var outputs = this.labelList.ToArray();

    // 学習する
    this.msvm = smo.Learn(inputs, outputs);

    // キャリブレーション
    var calibration = new MulticlassSupportVectorLearning<ChiSquare>()
    {
        // キャリブレーションで使用するアルゴリズムを設定
        Model = this.msvm, Learner = (param) => new ProbabilisticOutputCalibration<ChiSquare>()
        {
            Model = param.Model
        }
    };

    calibration.ParallelOptions.MaxDegreeOfParallelism = 4;
    calibration.Learn(inputs, outputs);

    textBox1.Text += "学習が完了しました\r\n";
}

実際の動作

以下のようにマウス操作で行の挿入を繰り返すと、、、
01_クリック.png

ショートカットのお勧めが通知されます。
同時に、得することが出来なかった時間の推測値を教えます。

02_2_おすすめ.png

このツールを従業員のPCで動かすことにより、全体的な生産性向上を図る算段です。
当然、学習データの収集、学習済みモデルの共有など、会社で利用する際に欲しくなる構成にすることも可能です。


本ツールに興味を持たれた方は、製品版のご相談歓迎いたします。
(本記事はプロトタイプについての記事となります)
利用用途、環境に合わせたカスタマイズも可能ですので、お気軽にリンク情報システムへご相談ください。
また、「Tech Connect Summer 夏のアドベントカレンダー」の他の記事においても、様々なアプリやツールを紹介していますので、ご参照ください。


リンク情報システム株式会社では一緒に働く仲間を随時募集しています!
また、お仕事のご依頼、ビジネスパートナー様も募集しております。お気軽にご連絡ください。
 
 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む