Pull to refresh

Comments 13

Вы только опубликовали, а я сразу вспомнил кусок своей программы, за который мне стыдно)
Github
Histogram shift. This correction makes the background really blank. After the correction
    # numpy clipping is performed to fit the 0-100 range
    hist_shift_0 = parsed_json["hist_shift_0"]
    hist_shift_1 = parsed_json["hist_shift_1"]
    hist_shift_2 = parsed_json["hist_shift_2"]

    image_separated = color.separate_stains(image_original, matrix_dh)
    stain_ch0 = image_separated[..., 0]
    stain_ch1 = image_separated[..., 1]
    stain_ch2 = image_separated[..., 2]

    stain_ch0 = (stain_ch0 + 1) * 200
    stain_ch0 -= hist_shift_0
    stain_ch0 = np.clip(stain_ch0, 0, 100)


Просто я не знаю, как это описать в коде. Функция color.separate_stains возвращает картинку с диапазоном то [-1;-0.5]. Понятия не имею почему, не моя она. А мне надо получить от 0 до 100. Плюс еще абсолютный сдвиг гистограммы, который подбирается эмпирически, но его я хотя бы смог вынести во внешний файл, описывающий свойства конкретной пары красителей.
{
  "channel_0":"Hematoxylin",
  "channel_1":"DAB-chromogen",
  "channel_2":"Supplementary channel",
  "vector": [[0.66504073, 0.61772484, 0.41968665],
            [0.4100872, 0.5751321, 0.70785],
            [0.6241389, 0.53632, 0.56816506]],
  "thresh_0":30,
  "thresh_1":40,
  "hist_shift_0":5,
  "hist_shift_1":16,
  "hist_shift_2":0
}
Функция color.separate_stains возвращает картинку с диапазоном то [-1;-0.5]. Понятия не имею почему, не моя она. А мне надо получить от 0 до 100.
На мой взгляд, в таких случаях достаточно прокомментировать — что делает данная строка кода, причём лучше было бы написать на мой взгляд:
    stain_ch0 = (stain_ch0 + 1) * 2 * 100 # convert range of values from [-1;-0.5] to [0; 100]

Тем самым мы явно показываем что делаем, к тому же умножение на 2 и на 100 будет лучше раскрывать ход мыслей — сначала увеличиваем вдвое, чтобы получить максимум до единицы, а потом уже переводим в проценты, умножая на сто.
Если ты что то не понимаешь то это называется магия. Если ты разбираешся в сути проблемы то это уже не магия.
Вот отличный пример
k[0..63] :=
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2
Это первые 32 бита дробных частей кубических корней первых 64 простых чисел [от 2 до 311]. Да можно написать как они вычисляются, но эти константы для разбирающихся людей. А для остальных это магические цифры.
Это не та 'магия', про которую речь в статье.
Ряду алгоритмов необходим набор каких-то констант, теоретически — произвольных*. Когда алгоритм стандартизируют в стандарт попадает и конкретный набор констант.
(*) но с какими то наборами алгоритм может работать лучше, с какими-то хуже (а с какими-то иметь неочевидную уязвимость) и настоящая криптографическая магия скрыта в том, почему в стандарт попал именно этот набор констант. Про s блоки des целую теорию заговора строили. Подозреваю, что в sha 'первые 32 бита дроб...' взяты как раз за тем, чтобы обеспечить хоть минимальную прозрачность 'почему выбраны именно эти'.
Пример хороший, но при этом вы, или кто-то другой, копировавший изначально этот код, забыл скопировать рядом стоящий комментарий, который и делает это не массивом магических констант, а кодом, с вполне нормальным описанием. Во всех примерах в топе гугла этот код имеет комментарии:
-- Initialize table of round constants
-- (first 32 bits of the fractional parts of the cube roots of the first
-- 64 primes 2..311):
local k = {
   0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
   0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
   0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
   0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
   0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
   0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
   0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
   0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
   0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
   0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
   0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
   0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
   0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
   0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
   0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
   0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}

Таблица констант
(первые 32 бита дробных частей кубических корней первых 64 простых чисел [от 2 до 311]):
k[0..63] :=
   0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
   0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
   0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
   0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
   0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
   0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
   0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
   0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2


И так далее.
Никакой магии бы не было, если бы автор кода удосужился добавить описание применяемого метода или вообще хоть какое-то обоснование выполняемых действий. Но в реальном коде было это:
/*
** float q_rsqrt( float number )
*/
float Q_rsqrt( float number )
{
	long i;
	float x2, y;
	const float threehalfs = 1.5F;

	x2 = number * 0.5F;
	y  = number;
	i  = * ( long * ) &y;			    // evil floating point bit level hacking
	i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
	y  = * ( float * ) &i;
	y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

#ifndef Q3_VM
#ifdef __linux__
	assert( !isnan(y) ); // bk010122 - FPE?
#endif
#endif
	return y;
}

Естественно ничего, кроме возгласа WTF?! такой код и в таком виде вызвать не может и не для математика сравним с магией.
В том и дело, что это не математика, а очень хитрое использование особенностей представления дробных чисел. Кто и каким образом вывел волшебную константу — доподлинно не известно.
И, тем не менее, это чистой воды математика. Так, вместо «evil floating point bit level hacking» там вполне могло бы стоять пояснение, что алгоритм базируется на logₙ(1/√x) = −½ logₙ(x) и данное преобразование в int возвращает значение, достаточно близкое к логарифму от float-числа. Я согласен с тем, что это великолепное применение знаний математики и битовых операций, но уж точно не магия. Просто магией мы называем то, что мы не понимаем.

Способ вывода "волшебной" константы описан в Википедии довольно понятным языком.

Я как-то не понимаю зачем такая статья, проблема то небольшая. Магические числа, это первое про что пишут в книгах. Пиши код, чтобы он читался как текст. Лично я предпочитаю длинные имена классов/методов/параметров, однако не любитель повсеместных комментариев.

Мне в большей степени хотелось привлечь внимание к функциональным зависимостям между параметрами. То есть, если по сути
const int a = b*c; // а - ...
то так и надо написать, а не
const int a = ...; // a = b*c, а - ...
Sign up to leave a comment.

Articles