Changeset View
Changeset View
Standalone View
Standalone View
kstars/fitsviewer/stretch.cpp
Show All 20 Lines | |||||
21 | { | 21 | { | ||
22 | const int middle = values.size() / 2; | 22 | const int middle = values.size() / 2; | ||
23 | std::nth_element(values.begin(), values.begin() + middle, values.end()); | 23 | std::nth_element(values.begin(), values.begin() + middle, values.end()); | ||
24 | return values[middle]; | 24 | return values[middle]; | ||
25 | } | 25 | } | ||
26 | 26 | | |||
27 | // Returns the rough max of the buffer. | 27 | // Returns the rough max of the buffer. | ||
28 | template <typename T> | 28 | template <typename T> | ||
29 | T sampledMax(T *values, int size, int sampleBy) | 29 | T sampledMax(T const *values, int size, int sampleBy) | ||
30 | { | 30 | { | ||
31 | T maxVal = 0; | 31 | T maxVal = 0; | ||
32 | for (int i = 0; i < size; i+= sampleBy) | 32 | for (int i = 0; i < size; i+= sampleBy) | ||
33 | if (maxVal < values[i]) | 33 | if (maxVal < values[i]) | ||
34 | maxVal = values[i]; | 34 | maxVal = values[i]; | ||
35 | return maxVal; | 35 | return maxVal; | ||
36 | } | 36 | } | ||
37 | 37 | | |||
38 | // Returns the median of the sample values. | 38 | // Returns the median of the sample values. | ||
39 | // The values are not modified. | 39 | // The values are not modified. | ||
40 | template <typename T> | 40 | template <typename T> | ||
41 | T median(T *values, int size, int sampleBy) | 41 | T median(T const *values, int size, int sampleBy) | ||
42 | { | 42 | { | ||
43 | const int downsampled_size = size / sampleBy; | 43 | const int downsampled_size = size / sampleBy; | ||
44 | std::vector<T> samples(downsampled_size); | 44 | std::vector<T> samples(downsampled_size); | ||
45 | for (int index = 0, i = 0; i < downsampled_size; ++i, index += sampleBy) | 45 | for (int index = 0, i = 0; i < downsampled_size; ++i, index += sampleBy) | ||
46 | samples[i] = values[index]; | 46 | samples[i] = values[index]; | ||
47 | return median(samples); | 47 | return median(samples); | ||
48 | } | 48 | } | ||
49 | 49 | | |||
▲ Show 20 Lines • Show All 168 Lines • ▼ Show 20 Line(s) | 217 | stretchOneChannel(input_buffer, output_image, stretch_params, input_range, | |||
218 | image_height, image_width, sampling); | 218 | image_height, image_width, sampling); | ||
219 | else if (num_channels == 3) | 219 | else if (num_channels == 3) | ||
220 | stretchThreeChannels(input_buffer, output_image, stretch_params, input_range, | 220 | stretchThreeChannels(input_buffer, output_image, stretch_params, input_range, | ||
221 | image_height, image_width, sampling); | 221 | image_height, image_width, sampling); | ||
222 | } | 222 | } | ||
223 | 223 | | |||
224 | // See section 8.5.7 in above link https://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html | 224 | // See section 8.5.7 in above link https://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html | ||
225 | template <typename T> | 225 | template <typename T> | ||
226 | void computeParamsOneChannel(T *buffer, StretchParams1Channel *params, | 226 | void computeParamsOneChannel(T const *buffer, StretchParams1Channel *params, | ||
227 | int inputRange, int height, int width) | 227 | int inputRange, int height, int width) | ||
228 | { | 228 | { | ||
229 | // Find the median sample. | 229 | // Find the median sample. | ||
230 | constexpr int maxSamples = 500000; | 230 | constexpr int maxSamples = 500000; | ||
231 | const int sampleBy = width * height < maxSamples ? 1 : width * height / maxSamples; | 231 | const int sampleBy = width * height < maxSamples ? 1 : width * height / maxSamples; | ||
232 | 232 | | |||
233 | T medianSample = median(buffer, width * height, sampleBy); | 233 | T medianSample = median(buffer, width * height, sampleBy); | ||
234 | // Find the Median deviation: 1.4826 * median of abs(sample[i] - median). | 234 | // Find the Median deviation: 1.4826 * median of abs(sample[i] - median). | ||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Line(s) | |||||
311 | { | 311 | { | ||
312 | image_width = width; | 312 | image_width = width; | ||
313 | image_height = height; | 313 | image_height = height; | ||
314 | image_channels = channels; | 314 | image_channels = channels; | ||
315 | dataType = data_type; | 315 | dataType = data_type; | ||
316 | input_range = getRange(dataType); | 316 | input_range = getRange(dataType); | ||
317 | } | 317 | } | ||
318 | 318 | | |||
319 | void Stretch::run(uint8_t *input, QImage *outputImage, int sampling) | 319 | void Stretch::run(uint8_t const *input, QImage *outputImage, int sampling) | ||
320 | { | 320 | { | ||
321 | Q_ASSERT(outputImage->width() == (image_width + sampling - 1) / sampling); | 321 | Q_ASSERT(outputImage->width() == (image_width + sampling - 1) / sampling); | ||
322 | Q_ASSERT(outputImage->height() == (image_height + sampling - 1) / sampling); | 322 | Q_ASSERT(outputImage->height() == (image_height + sampling - 1) / sampling); | ||
323 | recalculateInputRange(input); | 323 | recalculateInputRange(input); | ||
324 | 324 | | |||
325 | switch (dataType) | 325 | switch (dataType) | ||
326 | { | 326 | { | ||
327 | case TBYTE: | 327 | case TBYTE: | ||
328 | stretchChannels(reinterpret_cast<uint8_t*>(input), outputImage, params, | 328 | stretchChannels(reinterpret_cast<uint8_t const*>(input), outputImage, params, | ||
329 | input_range, image_height, image_width, image_channels, sampling); | 329 | input_range, image_height, image_width, image_channels, sampling); | ||
330 | break; | 330 | break; | ||
331 | case TSHORT: | 331 | case TSHORT: | ||
332 | stretchChannels(reinterpret_cast<short*>(input), outputImage, params, | 332 | stretchChannels(reinterpret_cast<short const*>(input), outputImage, params, | ||
333 | input_range, image_height, image_width, image_channels, sampling); | 333 | input_range, image_height, image_width, image_channels, sampling); | ||
334 | break; | 334 | break; | ||
335 | case TUSHORT: | 335 | case TUSHORT: | ||
336 | stretchChannels(reinterpret_cast<unsigned short*>(input), outputImage, params, | 336 | stretchChannels(reinterpret_cast<unsigned short const*>(input), outputImage, params, | ||
337 | input_range, image_height, image_width, image_channels, sampling); | 337 | input_range, image_height, image_width, image_channels, sampling); | ||
338 | break; | 338 | break; | ||
339 | case TLONG: | 339 | case TLONG: | ||
340 | stretchChannels(reinterpret_cast<long*>(input), outputImage, params, | 340 | stretchChannels(reinterpret_cast<long const*>(input), outputImage, params, | ||
341 | input_range, image_height, image_width, image_channels, sampling); | 341 | input_range, image_height, image_width, image_channels, sampling); | ||
342 | break; | 342 | break; | ||
343 | case TFLOAT: | 343 | case TFLOAT: | ||
344 | stretchChannels(reinterpret_cast<float*>(input), outputImage, params, | 344 | stretchChannels(reinterpret_cast<float const*>(input), outputImage, params, | ||
345 | input_range, image_height, image_width, image_channels, sampling); | 345 | input_range, image_height, image_width, image_channels, sampling); | ||
346 | break; | 346 | break; | ||
347 | case TLONGLONG: | 347 | case TLONGLONG: | ||
348 | stretchChannels(reinterpret_cast<long long*>(input), outputImage, params, | 348 | stretchChannels(reinterpret_cast<long long const*>(input), outputImage, params, | ||
349 | input_range, image_height, image_width, image_channels, sampling); | 349 | input_range, image_height, image_width, image_channels, sampling); | ||
350 | break; | 350 | break; | ||
351 | case TDOUBLE: | 351 | case TDOUBLE: | ||
352 | stretchChannels(reinterpret_cast<double*>(input), outputImage, params, | 352 | stretchChannels(reinterpret_cast<double const*>(input), outputImage, params, | ||
353 | input_range, image_height, image_width, image_channels, sampling); | 353 | input_range, image_height, image_width, image_channels, sampling); | ||
354 | break; | 354 | break; | ||
355 | default: | 355 | default: | ||
356 | break; | 356 | break; | ||
357 | } | 357 | } | ||
358 | } | 358 | } | ||
359 | 359 | | |||
360 | // The input range for float/double is ambiguous, and we can't tell without the buffer, | 360 | // The input range for float/double is ambiguous, and we can't tell without the buffer, | ||
361 | // so we set it to 64K and possibly reduce it when we see the data. | 361 | // so we set it to 64K and possibly reduce it when we see the data. | ||
362 | void Stretch::recalculateInputRange(uint8_t *input) | 362 | void Stretch::recalculateInputRange(uint8_t const *input) | ||
363 | { | 363 | { | ||
364 | if (input_range <= 1) return; | 364 | if (input_range <= 1) return; | ||
365 | if (dataType != TFLOAT && dataType != TDOUBLE) return; | 365 | if (dataType != TFLOAT && dataType != TDOUBLE) return; | ||
366 | 366 | | |||
367 | float mx = 0; | 367 | float mx = 0; | ||
368 | if (dataType == TFLOAT) | 368 | if (dataType == TFLOAT) | ||
369 | mx = sampledMax(reinterpret_cast<float*>(input), image_height * image_width, 1000); | 369 | mx = sampledMax(reinterpret_cast<float const*>(input), image_height * image_width, 1000); | ||
370 | else if (dataType == TDOUBLE) | 370 | else if (dataType == TDOUBLE) | ||
371 | mx = sampledMax(reinterpret_cast<double*>(input), image_height * image_width, 1000); | 371 | mx = sampledMax(reinterpret_cast<double const*>(input), image_height * image_width, 1000); | ||
372 | if (mx <= 1.01f) input_range = 1; | 372 | if (mx <= 1.01f) input_range = 1; | ||
373 | } | 373 | } | ||
374 | 374 | | |||
375 | StretchParams Stretch::computeParams(uint8_t *input) | 375 | StretchParams Stretch::computeParams(uint8_t const *input) | ||
376 | { | 376 | { | ||
377 | recalculateInputRange(input); | 377 | recalculateInputRange(input); | ||
378 | StretchParams result; | 378 | StretchParams result; | ||
379 | for (int channel = 0; channel < image_channels; ++channel) | 379 | for (int channel = 0; channel < image_channels; ++channel) | ||
380 | { | 380 | { | ||
381 | int offset = channel * image_width * image_height; | 381 | int offset = channel * image_width * image_height; | ||
382 | StretchParams1Channel *params = channel == 0 ? &result.grey_red : | 382 | StretchParams1Channel *params = channel == 0 ? &result.grey_red : | ||
383 | (channel == 1 ? &result.green : &result.blue); | 383 | (channel == 1 ? &result.green : &result.blue); | ||
384 | switch (dataType) | 384 | switch (dataType) | ||
385 | { | 385 | { | ||
386 | case TBYTE: | 386 | case TBYTE: | ||
387 | { | 387 | { | ||
388 | auto buffer = reinterpret_cast<uint8_t*>(input); | 388 | auto buffer = reinterpret_cast<uint8_t const*>(input); | ||
389 | computeParamsOneChannel(buffer + offset, params, input_range, | 389 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
390 | image_height, image_width); | 390 | image_height, image_width); | ||
391 | break; | 391 | break; | ||
392 | } | 392 | } | ||
393 | case TSHORT: | 393 | case TSHORT: | ||
394 | { | 394 | { | ||
395 | auto buffer = reinterpret_cast<short*>(input); | 395 | auto buffer = reinterpret_cast<short const*>(input); | ||
396 | computeParamsOneChannel(buffer + offset, params, input_range, | 396 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
397 | image_height, image_width); | 397 | image_height, image_width); | ||
398 | break; | 398 | break; | ||
399 | } | 399 | } | ||
400 | case TUSHORT: | 400 | case TUSHORT: | ||
401 | { | 401 | { | ||
402 | auto buffer = reinterpret_cast<unsigned short*>(input); | 402 | auto buffer = reinterpret_cast<unsigned short const*>(input); | ||
403 | computeParamsOneChannel(buffer + offset, params, input_range, | 403 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
404 | image_height, image_width); | 404 | image_height, image_width); | ||
405 | break; | 405 | break; | ||
406 | } | 406 | } | ||
407 | case TLONG: | 407 | case TLONG: | ||
408 | { | 408 | { | ||
409 | auto buffer = reinterpret_cast<long*>(input); | 409 | auto buffer = reinterpret_cast<long const*>(input); | ||
410 | computeParamsOneChannel(buffer + offset, params, input_range, | 410 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
411 | image_height, image_width); | 411 | image_height, image_width); | ||
412 | break; | 412 | break; | ||
413 | } | 413 | } | ||
414 | case TFLOAT: | 414 | case TFLOAT: | ||
415 | { | 415 | { | ||
416 | auto buffer = reinterpret_cast<float*>(input); | 416 | auto buffer = reinterpret_cast<float const*>(input); | ||
417 | computeParamsOneChannel(buffer + offset, params, input_range, | 417 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
418 | image_height, image_width); | 418 | image_height, image_width); | ||
419 | break; | 419 | break; | ||
420 | } | 420 | } | ||
421 | case TLONGLONG: | 421 | case TLONGLONG: | ||
422 | { | 422 | { | ||
423 | auto buffer = reinterpret_cast<long long*>(input); | 423 | auto buffer = reinterpret_cast<long long const*>(input); | ||
424 | computeParamsOneChannel(buffer + offset, params, input_range, | 424 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
425 | image_height, image_width); | 425 | image_height, image_width); | ||
426 | break; | 426 | break; | ||
427 | } | 427 | } | ||
428 | case TDOUBLE: | 428 | case TDOUBLE: | ||
429 | { | 429 | { | ||
430 | auto buffer = reinterpret_cast<double*>(input); | 430 | auto buffer = reinterpret_cast<double const*>(input); | ||
431 | computeParamsOneChannel(buffer + offset, params, input_range, | 431 | computeParamsOneChannel(buffer + offset, params, input_range, | ||
432 | image_height, image_width); | 432 | image_height, image_width); | ||
433 | break; | 433 | break; | ||
434 | } | 434 | } | ||
435 | default: | 435 | default: | ||
436 | break; | 436 | break; | ||
437 | } | 437 | } | ||
438 | } | 438 | } | ||
439 | return result; | 439 | return result; | ||
440 | } | 440 | } |